How to build a login page in SwiftUI #1 – Mastering Text Fields and understanding @State

Share on facebook
Share on twitter
Share on pinterest

This is the first of two parts of our tutorial on how to build a login page just with SwiftUI, here is the second one!

Welcome to this two-part tutorial series on how to create a full-functional login page by just using SwiftUI. In this part, we will compose a more complex UI and implement features such a Text Fields and Buttons and get to know the basic data flow concepts of SwiftUI.

Here is what we are going to create:

What you will learn 💡

What you will learn in this part of the tutorial:

  • How to compose a complex interface in SwiftUI
  • How to use Text Fields
  • How to use Secure Fields for more user data privacy
  • Understanding data flow in SwiftUI including @State properties
  • How to implement Buttons in SwiftUI

Project creation 🆕

Let’s start with creating a new project for our app. Open Xcode 11 and create a new Xcode project. Then select Single View App and click on Next. Give the project a suitable name and make sure “Use SwiftUI” is selected. Then click on Create.

We want to display the user’s profile image in the app, so let’s import a sample image into the Assets folder. Make sure you name the file appropriately.

We will also use a certain grey color multiple times. So before starting with creating the UI, copy the variable below and insert it above your ContentView struct inside your ContentView.swift file.

import SwiftUI
let lightGreyColor = Color(red: 239.0/255.0, green: 243.0/255.0, blue: 244.0/255.0, opacity: 1.0)
struct ContentView : View {
    var body: some View {
        Text("Hello World")
    }
}

Start creating the UI ✍️

Let’s start creating the interface of our login page! As you saw in the demo video, all elements are aligned vertically, so let’s delete the sample Text object and insert a VStack instead. In this stack, we put in all the objects we need for our UI. These objects automatically get stacked, you guessed it, vertically.

struct ContentView : View {
    var body: some View {
        VStack {
            
        }
    }
}

Let’s start with the “Welcome!” text at the top of our login page. For this, just insert a Text object containing a “Welcome!” string. We also apply some modifiers to it including a .padding modifier for extending the space between the Text and the Image below it which we will create in a moment.

struct ContentView : View {
    var body: some View {
        VStack {
            Text("Welcome!")
                .font(.largeTitle)
                .fontWeight(.semibold)
                .padding(.bottom, 20)
        }
    }
}

To outsource the created Text view, CMD-click on the Text object and select “Extract subview”. Let’s name this view WelcomeText.

Under your WelcomeText place an Image object. The Image should display the profile image of the user. To do this, enter the name of the image which we imported into the Asset folder earlier.

struct ContentView : View {
    var body: some View {
        VStack {
            WelcomeText()
            Image("userImage")
        }
    }
}

We also add some modifiers for scaling the Image and making it round (Check this Instagram post for more details!). Again, we also want a bottom padding.

struct ContentView : View {
    var body: some View {
        VStack {
            WelcomeText()
            Image("userImage")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(width: 150, height: 150)
                .clipped()
                .cornerRadius(150)
                .padding(.bottom, 75)
        }
    }
}

We outsource this view too and call it UserImage.

This is how your preview should look now:

Understanding @State and mastering Text Fields 🤓

Now comes the first one of our two Text Fields. The Text fields enable the user to write down their credentials in order to log in. To create the first one, insert a TextField object into your ContentView’s body. For now, we need two parameters for our TextField: the text binding and the title parameter, which provides us with a placeholder when the TextField is empty.

import SwiftUI
struct ContentView : View {
    var body: some View {
        VStack {
            WelcomeText()
            UserImage()
            TextField(title:, text:)
        }
    }

Creating a TextField without feeding the necessary parameters causes errors. We will get rid of these in a moment! But first of all, let’s talk about these parameters:

The second parameter accepts a binding. What does this mean?

In a nutshell, SwiftUI needs a property which it can use to store the information that the user enters. This property then processes the information and displays it again to the user.

Let’s create such a property by declaring a variable above our ContentView’s body called username which accepts a String. By default, i.e. when the user does not have anything entered, this variable should contain an empty String.

struct ContentView : View {
	var username: String = “"
    var body: some View {
        
        VStack {
            WelcomeText()
            UserImage()
            TextField(title:, text:)
        }
    }
}

But: Currently, this property is only able to read and store the information which an object such as the created TextField passes to it. But it must also be able to automatically render our view depending on its stored content. To do this, mark the variable as a @State.

struct ContentView : View {
	@State var username: String = “"
    var body: some View {
        
        VStack {
            //...
        }
    }
}

A @State property is connected to the view. A @State property is permanently being read by the view. That means that every time the @State property gets changed/updated, the view gets rerendered and eventually displays the content depending on the @State’s data.

To make this more clear, take a look at our TextField example. The TextField is connected to the username State property which contains an empty string as default. When the user enters a character, for example, the letter “A”, this character is passed to the @State property that is connected to the TextView. Because the State gets updated from an empty String to a String “A” it automatically triggers rerendering the view. The TextField then updates itself by reading out the content of the State property, which is now the string “A” and displays it to the User. This is how the user eventually sees what he has entered!

Every time the State gets updated, it causes the view to rerender. Every time the view (re)renders, the State’s content gets read

“State: A persistent value of a given type, through which a view reads and monitors the value.”

Apple

Back to our TextView. Since we created an appropriate State property, we can bind it to our TextField. To bind objects to a State property we have to insert the properties name with a dollar sign.

struct ContentView : View {
	@State var username: String = “"
    var body: some View {
        
        VStack {
            WelcomeText()
            UserImage()
            TextField(title: , text: $username)
        }
    }
}

When the TextField is empty (because the String of the State is empty, like it is at default) we don’t want the TextField to display nothing. Instead, we want to display a placeholder. We can do this by passing a Text object as the text argument. We can then delete the parameter’s name.

import SwiftUI
struct ContentView : View {
    
    @State var username: String = ""
    
    var body: some View {
        
        VStack {
            WelcomeText()
            UserImage()
            TextField("Username", text: $username)
        }
    }
}

The TextField itself doesn’t have any border or background by default. To change this we have to add some modifiers to it. We also add a .padding modifier again for extending the space between this TextField and the one we will create in a moment.

struct ContentView : View {
    
    @State var username: String = ""
    
    var body: some View {
        
        VStack {
            WelcomeText()
            UserImage()
            TextField("Username", text: $username)
                .padding()
                .background(lightGreyColor)
                .cornerRadius(5.0)
                .padding(.bottom, 20)
        }
    }
}

Great, we are finished with setting up our first Text View! Outsourcing objects which are connected to a State is a little bit special. Thus, we will do this later.

Next, we want a similar TextField for the user’s password. We could use a “normal” TextField object again. But we want to provide our login form with a little bit of privacy by changing the fields content to dots when the user enters his password.

Doing this is really simple, instead of inserting a TextField object we insert an object called SecureField. The SecureField needs the same arguments as a TextField. Therefore we create another @State property called password and bind it to the SecureField. We also insert an appropriate placeholder. For styling the SecureField like the TextField, just copy & paste the modifiers of the TextField to the SecureField.

struct ContentView : View {
    
    @State var username: String = ""
    @State var password: String = ""
    
    var body: some View {
        
        VStack {
            WelcomeText()
            UserImage()
            TextField("Username", text: $username)
                .padding()
                .background(lightGreyColor)
                .cornerRadius(5.0)
                .padding(.bottom, 20)
            SecureField("Password", text: $password)
                .padding()
                .background(lightGreyColor)
                .cornerRadius(5.0)
                .padding(.bottom, 20)
        }
    }
}

Also, add an overall .padding modifier to the VStack itself for the fields not touching the edge of the screen.

struct ContentView : View {
    
    @State var username: String = ""
    @State var password: String = ""
    
    var body: some View {
        
        VStack {
           //[…]        
		}
        .padding()
    }
}

This is what your preview should look like now:

Finish the UI by creating the Login button 🛠

Last but not least, let’s insert the Login Button. As described here, to create a button we start with creating the content inside the button. This should be a Text containing “LOGIN”. To modify the Text’s style and insert a green background apply the following modifiers to it.

struct ContentView : View {
    
    @State var username: String = ""
    @State var password: String = ""
    
    var body: some View {
        
        VStack {
	    //[…]
            Text("LOGIN")
                .font(.headline)
                .foregroundColor(.white)
                .padding()
                .frame(width: 220, height: 60)
                .background(Color.green)
                .cornerRadius(15.0)
        }
            .padding()
    }
}

Next, outsource this Text object and call it LoginButtonContent. Now we need to wrap the LoginButtonContent view into a Button. The button needs to know what to do when the user taps it. For now, we just want to print something out (for testing purposes). We will implement our authentication logic in the next part of this tutorial.

struct ContentView : View {
    
    @State var username: String = ""
    @State var password: String = ""
    
    var body: some View {
        
        VStack {
			//[…]
            Button(action: {print("Button tapped")}) {
               LoginButtonContent()
            }
        }
        .padding()
    }
}

Conclusion 🎊

Let’s see where we are. To check out the full functionality, run the app in the “real” simulator, not the preview one. We can now use the text fields to type in the user’s credentials! Note how the password entered by the user gets invisible.

Hint: If you want to use the simulator keyboard and it doesn’t open automatically when clicking on the text fields, click on the simulator and choose on Hardware -> Keyboard -> Toggle Software Keyboard in the macOS toolbar. If you then click on a TextField, the keyboard should open automatically.


This is a great point to remember how the State data flow works: If you enter something it gets passed to the @State property. Because the State’s content gets updated, it automatically triggers rerendering the view. Then, the TextField reads out the State’s content and displays it to the user. This cycle gets executed every time the user enters something.

I uploaded to project’s progress to GitHub if you want to check it out!

Great, we accomplished a lot so far! We composed a complex UI containing a TextField, a SecureField and a Button. We also learned what states are and how the basics of data flow in SwiftUI!

In the next part we will implement our apps functionality including user authentication and displaying different views depending on whether the authentication was successful or not. We also apply some minor improvements like moving the content up when the keyboard opens.

I hope you enjoyed this tutorial. Make sure you subscribe to our newsletter to not miss any updates, a lot of stuff about SwiftUI is coming. Also visit us on Facebook and Instagram. If you have any questions do not hesitate to contact us or leave a comment below!

10 replies on “How to build a login page in SwiftUI #1 – Mastering Text Fields and understanding @State”

Hey Jim, thanks for your kind feedback! Could you specify what you mean by that? Are you talking about this topic?

Do you have an example of a func that is called each time a characeter is entered into a textfield. I want to do some custom validation

Leave a Reply

Your email address will not be published. Required fields are marked *

small_c_popup.png

Covid-19 Forces you into quarantine?

Start Mastering swiftUI Today save your 33% discount