How to create a login screen in SwiftUI – Pablo Blanco
https://pabloblan.co/swiftUI_login/
1. Layout 1. Layout The layout to create consists of: We start by creating a new SwiftUI view class called LoginView (File > New > File... > SwiftUI View):
struct LoginView: View { var body: some View { }
} So, following the layout, let’s add the elements: struct LoginView: View { //0 @State private var email: String = "" @State private var password: String = "" @State private var agreedToTerms: Bool = false var body: some View { // 1. Vertical stack VStack() { // 2. Text Text("Introduce your credentials") // 3. Email field TextField("Email", text: $email) // 4. Password field SecureField("Password", text: $password) // 5. Toogle to agree T&C Toggle(isOn: $agreedToTerms) { Text("Agree to terms and conditions") } // 6. Log in button Button(action: { print("Logged in!") }){ Text("Log in") } // 7. Bottom spacer Spacer() } }
} Don’t worry about the //0 variables declared on the top, we will go through them later. // 1. Vertical stack
VStack() In SwiftUI there are many ways to distribute our content on the screen.
One of them is by using VStack, that means a vertical stack of elements. Every element declared inside the container VStack will be included in a stack, following the declaration order. In this case, our stack will be integrated by a text, two textfields, a toogle and a button (//2 to //6) // 2. Text:
Text("Introduce your credentials") This adds a text, in this case our screen description. // 3. Email field
TextField("Email", text: $email) A textfield allows users to add input data. In this case, it is used for capturing the user’s email. The text Email corresponds to the textfield placeholder. We will talk later about the text: $email declaration. // 4. Password field
SecureField("Password", text: $password) A SecureField is a textfield that allows secure data input, replacing text display with bullets. The string Password is its placeholder. We will talk later about the text: $password declaration. // 5. Toogle to agree T&C
Toggle(isOn: $agreedToTerms) { Text("Agree to terms and conditions")
} Toogle allows users to set on/off a property. In our case, this toogle represents if the user accepts the Terms and conditions. The toogle is composed by two elements: Left side: Any view needed. It should be included inside the brackets. In this case is the text Agree to terms and conditions Right side: The toogle control. It is enabled/disabled depending on the variable included after the word isOn on Toggle(isOn: ...) We will talk later about the $agreedToTerms declaration. // 6. Log in button
Defined as:
Button(action: { print("Logged in!")
}){ Text("Log in") .frame(minWidth: 0, maxWidth: .infinity)
} The button has two parts: Action: Indicates the action taken when the button is pressed. action: { print("Logged in!")
} Body of the button: The view that the button will display. In this case, we are displaying the text Log in. { Text("Log in")
} // 7. Bottom spacer (more info on another episode…) Once we have filled the vertical stack, how does the compiler know the elements’ vertical position? The element Spacer() tells the compiler that a space must be created on the defined position, so the other elements must be readapted to fit the layout to the screen. If no Spacer() is provided, so the position of the elements cannot be properly calculated, the compiler set by default the stack vertically centered. In this case we want the elements to be aligned to the top, so we are saying with Spacer() that the bottom includes a free space, so the elements will go up on the screen. Note: Place Spacer() between elements or in the first place on the stack. Run the app to check how the elements are reorganized on the screen.2. Input data saving - @State variables 2. Input data saving - @State variables At the beginning, three variables were declared in the class: @State private var email: String = ""
@State private var password: String = ""
@State private var agreedToTerms: Bool = false These variables are created to save the input state of the elements:
email is a string that contains the input data for the email textfield
password is a string that contains the input data for the password textfield
agreedToTerms is a boolean value that saves the toogle state To save the input data, these variables are referenced from the UI elements declarations:
TextField("Email", text: $email)
SecureField("Password", text: $password)
Toggle(isOn: $agreedToTerms) When the user interacts with the UI elements, these variables automatically get their content updated. In the other hand, if one of these variables is modified, the associated UI element will be updated too. In this case, the toogle control needs a initial state, so we include the false value to agreedToTerms.
@State private var agreedToTerms: Bool = false When the element is loaded, it will take the current state from it:3. Input data validation 3. Input data validation In this example, the Log in button will be enabled when: The email textfield and the password textfield are not empty The toogle is enabled A way to validate whether they are valid could be:
let isValidData = !email.isEmpty && !password.isEmpty && agreedToTerms == true Modifying operators, we can also check if the data is not valid:
let isInvalidData = email.isEmpty || password.isEmpty || agreedToTerms == false4. Button update - View modifiers 4. Button update - View modifiers Once the input data gets validated, the button needs to be updated in order to enable/disable the login button.
// 6. Log in button
Button(action: { print("Logged in!")
}){ Text("Log in")
} SwiftUI allows the developer to modify an element appearance or logic by using View modifiers. These modifiers must be declared after the element declaration. // 6. Log in button
Button(action: { print("Logged in!")
}){ Text("Log in")
}
.disabled(email.isEmpty || password.isEmpty || !agreedToTerms) In this case, the button will be disabled when the input data is not valid. According to the action defined, when the button is enabled and pressed, the message Logged in in the console will be displayed.5. Title and layout fixes 5. Title and layout fixes Now we know about view modifiers, we can resolve two pending problems related to the layout: No title provided To fix this, we will add a navigation view. That means that if we add new independent views and we connect them, we are ready to navigate from one to another. To achieve this, we will wrap the entire VStack container into a NavigationView container: var body: some View { // Navigation container view NavigationView { // 1. Vertical stack VStack() { // 2. Text Text("Introduce your credentials") // 3. Email field TextField("Email", text: $email) // 4. Password field SecureField("Password", text: $password) // 5. Toogle to agree T&C Toggle(isOn: $agreedToTerms) { Text("Agree to terms and conditions") } // 6. Log in button Button(action: { print("Logged in!") }){ Text("Log in") } // 7. Bottom spacer Spacer() } .padding(16) }
} Now we have the navigation, we can add a navigation title to the screen, adding the view modifier for including the title.
navigationBarTitle("Log in") Elements are too close to the screen bounds
Let’s add the modifier .padding(16) below the VStack container for adding a 16pt extra padding to the whole stack view.
// 1. Vertical stack
VStack() { // 2. Text Text("Introduce your credentials") ... // 7. Bottom spacer Spacer()
}
.padding(16)
DA: 72 PA: 93 MOZ Rank: 45