Onboarding screen part 2: Log in​, Signup and Password recovery UIs

SwiftUI Dec 18, 2019

Last week, I showed how to create an onboarding screen using the UIViewControllerRepresentable protocol. In this week’s swift iOS app tutorial, I will extend the onboarding screen to include login, signup and password recovery screens which is a normal iOS app flow or any kind of app. With that intro out of the way, let’s now write some swift code.

First, make sure to download the completed iOS example app project. Take a look at what we want to build.

You can continue with the previous iOS project if you were following along.

Login View.

Once the user has scrolled to the last PageViewController, we want to show the Swift UI Login View when is clicks the Get started button.

Now in your Xcode project, add a swift file named LoginView in the Views folder containing the following swift code:


import SwiftUI

struct LoginView: View {
    
    @State private var email = ""
    @State private var password = ""
    @State private var formOffset: CGFloat = 0
    @State private var presentSignupSheet = false
    @State private var presentPasswordRecoverySheet = false

    
    var body: some View {
        VStack(spacing: 40) {
            Image("Logo")
            Text("Login").font(.title).bold()
            VStack {
                LCTextfield(value: self.$email, placeholder: "Email", icon: Image(systemName: "at"), onEditingChanged: { flag in
                    withAnimation {
                        self.formOffset = flag ? -150 : 0
                    }
                })
                LCTextfield(value: self.$password, placeholder: "Password", icon: Image(systemName: "lock"), isSecure: true)
                LCButton(text: "Login") {
                    
                }
            }
            
            Button(action: {
                self.presentSignupSheet.toggle()
            }) {
              HStack {
                Text("Don't have an account? Sign up.").accentColor(Color.accentColor)
                  }
              }.sheet(isPresented: self.$presentSignupSheet) {
                  SignupView()
              }
            
            Button(action: {
                self.presentPasswordRecoverySheet.toggle()
            }) {
              HStack {
                Text("Forgot your password?").accentColor(Color.purple)
                  }
              }.sheet(isPresented: self.$presentPasswordRecoverySheet) {
                RecoverPasswordView(presentPasswordRecoverySheet: self.$presentPasswordRecoverySheet)
              }
            
        }.padding().offset(y: self.formOffset)
    }
}

struct LoginView_Previews: PreviewProvider {
    static var previews: some View {
        LoginView()
    }
}

The above code does the following:

  1. Put all the views into a VStack view container
  2. Handles the animation of the entire VStack container when in editing mode to accommodate the keyboard using the formOffset state property.
  3. Shows the sheets for the sign up view and password recovery view.

You will get some compiler errors because you haven’t created the LCTextfield , SignupView and PasswordRecoveryView swift UI view yet. So let’s do that. In the same Views, add a swift ui file named LCTextfield with the following swift code inside:


import SwiftUI

struct LCTextfield: View {
    
    @Binding var value: String
    var placeholder = "Placeholder"
    var icon = Image(systemName: "person.crop.circle")
    var color = Color("offColor")
    var isSecure = false
    var onEditingChanged: ((Bool)->()) = {_ in }
    
    var body: some View {
        HStack {
            
            if isSecure{
                SecureField(placeholder, text: self.$value, onCommit: {
                    self.onEditingChanged(false)
                }).padding()
            } else {
                TextField(placeholder, text: self.$value, onEditingChanged: { flag in
                    self.onEditingChanged(flag)
                }).padding()
            }
            
            icon.imageScale(.large)
                .padding()
                .foregroundColor(color)
        }.background(color.opacity(0.2)).clipShape(Capsule())
    }
}

struct LCTextfield_Previews: PreviewProvider {
    static var previews: some View {
        LCTextfield(value: .constant(""))
    }
}

The above swift code creates a reusable swift ui Textfield which can also be used as a password Secure field depending on the isSecure flag.

Signup View.

You will not be able to run the app until we create the 2 remaining screens. In the Views folder, add a swift ui file named SignupView.swift and put the following swift code inside:

 
import SwiftUI

struct SignupView: View {
       
    @State private var email = ""
    @State private var password = ""
    @State private var confirmedPassword = ""
    
    @State private var formOffset: CGFloat = 0
    
    var body: some View {
        
       return VStack(spacing: 40) {
            Image("Logo")
            Text("Sign Up").font(.title).bold()
            VStack {
                LCTextfield(value: self.$email, placeholder: "Email", icon: Image(systemName: "at"), onEditingChanged: { flag in
                    withAnimation {
                        self.formOffset = flag ? -150 : 0
                    }
                    
                })
                LCTextfield(value: self.$password, placeholder: "Password", icon: Image(systemName: "lock"), isSecure: true)
                LCTextfield(value: self.$confirmedPassword, placeholder: "Confirm Password", icon: Image(systemName: "lock.rotation"), isSecure: true)
                LCButton(text: "Sign up") {
                    
                }
            }
            
            Button(action: {
            }) {
              HStack {
                Text("Already have an account?").accentColor(Color.accentColor)
                  }
              }
            
       }.padding().offset(y: self.formOffset)
    }
}

struct SignupView_Previews: PreviewProvider {
    static var previews: some View {
        SignupView()
    }
}

The following code does the same thing as the LoginView.

Password recovery View.

Create a new swift ui file in the same folder named RecoverPasswordView and put the following code inside:

import SwiftUI

struct RecoverPasswordView: View {
    @State private var email = ""
    @Binding var presentPasswordRecoverySheet: Bool
    
    var body: some View {
        VStack(spacing: 40) {
            Image("Logo")
            Text("Recover Password").font(.title).bold()
            VStack {
                LCTextfield(value: self.$email, placeholder: "Email", icon: Image(systemName: "at"))
                LCButton(text: "Reset Password") {}
            }
            
            Button(action: {
                self.presentPasswordRecoverySheet.toggle()
            }) {
              HStack {
                Text("Cancel").accentColor(Color.accentColor)
                  }
              }
            
        }.padding()
    }

}

struct RecoverEmailView_Previews: PreviewProvider {
    static var previews: some View {
        RecoverPasswordView(presentPasswordRecoverySheet: .constant(false))
    }
}

Putting it all together

Now open the ContentView.swift and add the following properties to the top one the struct:

  @State var show = false
  private let initialLaunchKey = "isInitialLaunch"

The first property will handle the animation of the login view and the second will keep track of whether it’s the first we launch the app. In a normal iOS app, we wouldn’t want to show the onboarding screen every time the user opens our app. So we will use the UserDefault class to persist a boolean value indicating whether this is the first launch on this device or not.

Replace the body with the following:

    
    var body: some View {
        VStack {
            if show || UserDefaults.standard.bool(forKey: initialLaunchKey){
                LoginView().transition(.move(edge: .bottom))
            } else {
                PageViewContainer( viewControllers: Page.getAll.map({  UIHostingController(rootView: PageView(page: $0) ) }), presentSignupView: {
                    withAnimation {
                        self.show = true
                    }
                    UserDefaults.standard.set(true, forKey: self.initialLaunchKey)
                }).transition(.scale)
            }
        }.frame(maxHeight: .infinity).background(Color.backgroundColor).edgesIgnoringSafeArea(.all)
    }

Now run the app twice and the second time will not show the onboarding screen.

Further Keyboard management

Although the user can dismiss the keyboard by clicking the return key, he might also want to do the most natural thing that is to click anywhere on the screen to dismiss the keyboard. So let me show how to dismiss the keyboard in swift UI when the user click anywhere on the screen.

In the Extensions folder, create a swift file named UIApplicationExt.swift and put the following code inside:


extension UIApplication {
    func endEditing() {
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

In the above code, we extend the UIApplication class , then send a resign first responder event to the responder chain to be picked up and handled by the textfield. Read more about the responder chain in iOS app development here and here.

That’s it guys… Feel free to share this article to whoever may need it and subscribe for more.

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