LastPass clone - part 10: Generate password Service

lastpass redesign May 18, 2020

Hey guys, last week’s part was all about creating the UI for our password generator view. We created a beautiful neumorphic design for the entire view. We also made a reusable custom modifier that creates an inner shadow to all circles that uses it, of course this can be any type of shape if we tweak it a little bit.  Let’s get started.

Preparations

You should have the source code link in your email inbox if you are subscribed, otherwise click here to subscribe and get it.

Password Generator Service

Create a new folder inside the root folder of your project named Services, then add a new swift file named PasswordGeneratorService.swift. This is the file in which we will put all of our logic for the password generation process.

Next, add the following code inside:

import SwiftUI
import Combine

class PasswordGeneratorService: ObservableObject {
    
    
}

This class needs to conform to the ObservableObject protocol because some of its properties will be reactive.

Next, add the following struct inside the class you’ve just created:

struct Options {
        var lowercase = true
        var uppercase = false
        var specialCharacters = true
        var digits = false
        var length: CGFloat = 6
    }

As you can see, the above struct will hold the options that one can use to customise the password when generating a new one.

Add the following below the Options struct:

     @Published var passowrd: String = ""
       private let uppercaseCharacters = "abcdefghijklmnopqrstuvwxyz".uppercased()
       private let lowercaseCharacters = "abcdefghijklmnopqrstuvwxyz"
       private let digits = "0123456789"
       private let specialCharacters = "[email protected]#$%^&*(){}[]:?/\\|"
       @Published var options = Options()

Most of those properties are private, we are only interested in the 2 @Published ones. The password will be used to update the password field and the options to configure the generated password.

Now add the following function below everything:

    func generatePassword(with options: Options) -> String {
           var charOptions = ""
           
           if options.lowercase {
               charOptions += lowercaseCharacters
           }
           
           if options.uppercase {
               charOptions += uppercaseCharacters
           }
           
           if options.digits {
               charOptions += digits
           }
           
       
           if options.specialCharacters {
               charOptions += specialCharacters
           }
           
           if charOptions.count <= 0 {
               return ""
           }
           
           var newPassword = ""
           for _ in 0...Int(options.length){
              let char = Array(charOptions)[Int.random(in: 0..<charOptions.count)]
               newPassword.append(char)
           }

            return newPassword

       }

This is our basic logic for generating password. Although there’s room for improvement, this one will do the trick. Take a moment to go through the code, it’s simpler than it seems.

Next, add the following below the above function:

    var generatePassowordPublisher: AnyPublisher<String, Never> {
        self.$options.debounce(for: 0.3, scheduler: RunLoop.main)
            .map {[unowned self] options in
            return self.generatePassword(with: options)
        }.eraseToAnyPublisher()
    }

The sole purpose of this publisher is to wait for 1/3 of a second before spitting out a new password after changing one or more of the options especially for the slider as we don’t want to generate a new password for each value while the user is sliding.

Last, add the following below or above everything:

init() {
        self.cancellable = generatePassowordPublisher
                   .assign(to: \.passowrd, on: self)
        
  }

Here we use the assign operator to subscribe to our publisher, and set the password with the value coming from the pipeline. With this, we are ready to modify the PasswordGeneratorView in order to use our newly created service.

Go back in the PasswordGeneratorView.swift. Replace the following properties:

@State private var lowercase = true
@State private var uppercase = false
@State private var specialCharacters = true
@State private var digits = false
@State private var length: CGFloat = 6

With the following:

@ObservedObject private var passwordService = PasswordGeneratorService()

Isn’t this fantastic? We’ve just replaced 5 line with 1 line, cool!… Next, you will certainly get errors after that genocide. So, everywhere you have an error, you will need to replace the $ with the following except for the password.:

$passwordService.options.

For eg. The following: self.$lowercase should be self.$passwordService.options.lowercase.

Do this for the password:

Change this self.$password with this self.$passwordService.passowrd

You should now be able to CMD+B to build the project, resume the preview and click play to run it. You will now be able to switch on and off the options. We need a way to see the length of the password when the slider’s values changes. So, replace the slider with the following:

HStack {
                    Slider(value: self.$passwordService.options.length, in: 1...30, step: 1)
                        .accentColor(Color.accent)
                    
                    Text("\(Int(self.passwordService.options.length))")
                        .font(.system(size: 20, weight: .bold))
                        .foregroundColor(.gray)
                    .frame(width: 30, height: 30)
                    
                }

So the above code just put the slider inside a HStack container with a Text beside it that will display the current value of the slider. And with this done, we can say that the PasswordGeneratorView is complete.

Putting the HomeView, EditFormView and PasswordGeneratorView together.

Now that we have all these 3 component complete, we can proceed to putting the all of them together, but for that to work we still need one more thing, a button on the home screen that will open the EditFormView. This button will use a custom style like the one on the toggles. In the HomeView, add the following function:

fileprivate func createFloatingButton() -> some View {
	EmptyView()
}

We will implement that function shortly. Next, replace the content of the body with the following:

ZStack(alignment: .bottomTrailing) {
            VStack {
                HeaderView { filter in }
                createList()
            }
            
            createFloatingButton()
        }

It’s still the same content, we’ve only added to it. We’ve surrounded the existing content with a ZStack container, then called the createFloatingButton() to display the button. So, in order for the button to be aligned to the bottom right of the screen, we set the alignment on the ZStack container to .bottomTrailing.

In the createFloatingButton() function, replace the EmptyView with the following:

 Button(action: {
             HapticFeedback.generate()
         }) {
             
             Image(systemName: "plus")
                 .resizable()
                 .frame(width: 20, height: 20)
                 .foregroundColor(Color.text)
                 .padding()
                 .background(Color.background)
                 .cornerRadius(35)
                .neumorphic()
         }.padding(30)
     

Resume and run the preview, you should see this:

The button looks good, right? However, if you click on it, it does not do anything. Let’s change that. Add the following property to the of the struct:

@State var showEditFormView = false

Next, add this below the haptic feedback generation:

self.showEditFormView.toggle()

Now we are able to toggle the state of showEditFormView. Next, add the following exactly on the button, after the padding(30) modifier:

.sheet(isPresented: self.$showEditFormView) {
     self.createEditFormView()
 }

To silence that error, add the following function below the function the one that creates the button:

fileprivate func createEditFormView() -> some View {
    EmptyView()
}

Now if you resume the preview and run it, you should be able to present the sheet when you click the button.

Next, replace the EmptyView inside the above function with the following:

EditFormView(showDetails: .constant(false))

Now, resume the preview and run it again. You should now see the EdittFormView on the presented sheet.

Next, let’s present the password generator view. In the EditFormView.swift file, add the following property to the top of the struct:

 @State private var showPasswordGeneratorView = false

Then find the createPasswordForm function, and add the following inside the generate password action closure:

self.showPasswordGeneratorView.toggle()

Next, add the following modifier to the same button:

.sheet(isPresented: self.$showPasswordGeneratorView) {
                            PasswordGeneratorView(generatedPassword: self.$password)
                       }

Here is a brief explanation of what we’ve just done:

  1. We first declare a state variable that we will use to show and hide the sheet.
  2. Inside the generate password button’s action, we toggle the state showPasswordGeneratorView to show or hide the sheet.
  3. We then attach the sheet modifier to the button. We add set the content of the sheet to be a PasswordGeneratorView instance. Note that we bind the password to the generated password from the PasswordGeneratorView.

Resume the preview and run it. You should be able to present the sheet containing PasswordGeneratorView. However, when you dismiss the sheet, the password is still empty, the reason being that we haven’t wired the generated password binding property to the actual generated password from the password generation service. Let’s do that:

Now, inside the PasswordGeneratorView, add the following modifier to the outer VStack container:

  .onDisappear {
                self.generatedPassword = self.passwordService.passowrd
        }

This will set the current generated password to the generatedPassword property which in turn will set the password field in the EdittFormView.

Resume and run the preview again, and everything will now work fine. You will now be able to present the PasswordGeneratorView, generate a new password, dismiss the view and still see the new generated password auto-populated in the password field in the EdittFormView.

This is it for this part folks, hope you’ve enjoyed it. Stay tuned for more. Please feel free to share this article, subscribe if you haven’t done so already. If you have any question, send me an email. Stay tuned and happy coding.

John K

I am a software developer and code enthusiast. Do you want to work with me, have a suggestion or a request? Feel free to contact me at [email protected] or https://twitter.com/liquidcoder