Creating a login screen is a fundamental task in mobile app development. SwiftUI, Apple’s modern UI framework, makes this process straightforward and enjoyable. In this comprehensive guide, we’ll walk through building a stylish and functional login screen using SwiftUI. We’ll cover UI design, state management, data validation, and secure password handling, providing code snippets and best practices along the way.
What is SwiftUI?
SwiftUI is a declarative UI framework developed by Apple for building user interfaces across all Apple platforms, including iOS, macOS, watchOS, and tvOS. Its key benefits include:
- Declarative Syntax: UI is described using code that represents the desired state, rather than step-by-step instructions.
- Live Preview: See real-time updates in the Xcode canvas as you code.
- Cross-Platform: Share code across different Apple platforms.
- Data Binding: Seamless integration with data and state management.
Why Use SwiftUI for Login Screens?
- Modern Approach: Embraces contemporary UI development practices.
- Simplified Code: Reduces boilerplate code compared to UIKit.
- Easy to Understand: Clear and concise syntax makes UI code more readable.
- Improved Maintainability: Promotes better separation of concerns.
Setting Up the Project
Before we start, make sure you have Xcode installed. Create a new Xcode project, select the “App” template, choose SwiftUI for the interface, and Swift as the language.
Designing the UI
Our login screen will consist of the following elements:
- App Logo
- Email Text Field
- Password Text Field
- Login Button
- Optional: Forgot Password Link, Sign Up Link
Here’s the code to set up the basic UI structure:
import SwiftUI
struct LoginView: View {
@State private var email = ""
@State private var password = ""
var body: some View {
NavigationView {
VStack {
// App Logo
Image(systemName: "lock.shield.fill")
.resizable()
.scaledToFit()
.frame(width: 120, height: 120)
.padding(.bottom, 30)
// Email Field
TextField("Email", text: $email)
.padding()
.background(Color(.secondarySystemBackground))
.cornerRadius(10)
.padding(.bottom, 20)
// Password Field
SecureField("Password", text: $password)
.padding()
.background(Color(.secondarySystemBackground))
.cornerRadius(10)
.padding(.bottom, 30)
// Login Button
Button(action: {
// Perform login action
print("Login tapped with email: \(email), password: \(password)")
}) {
Text("Login")
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.cornerRadius(10)
}
Spacer() // Push content to top
}
.padding()
.navigationTitle("Login")
}
}
}
struct LoginView_Previews: PreviewProvider {
static var previews: some View {
LoginView()
}
}
In this code:
@State private var email
and@State private var password
: Declare state variables to hold the email and password inputs.NavigationView
: Wraps the content to provide a navigation bar.VStack
: Vertically stacks the UI elements.Image
: Displays an image for the app logo.TextField
: Allows users to enter their email.SecureField
: Allows users to enter their password, hiding the input.Button
: Triggers the login action.Spacer()
: Pushes the content to the top of the screen.
Adding Styles and Polish
Let’s enhance the UI with custom styling and theming for a more appealing look.
import SwiftUI
struct LoginView: View {
@State private var email = ""
@State private var password = ""
@State private var isPasswordVisible = false // Added password visibility state
var body: some View {
NavigationView {
ZStack { // Added ZStack for background color
Color(.systemBackground).edgesIgnoringSafeArea(.all) // Setting background color
VStack {
// App Logo
Image(systemName: "lock.shield.fill")
.resizable()
.scaledToFit()
.frame(width: 120, height: 120)
.foregroundColor(.blue) // Adjusted color
.padding(.bottom, 30)
// Email Field
TextField("Email", text: $email)
.padding()
.background(Color(.secondarySystemBackground))
.cornerRadius(10)
.padding(.bottom, 20)
.keyboardType(.emailAddress) // Keyboard type for email
.autocapitalization(.none) // Disable auto-capitalization
// Password Field with Password Visibility Toggle
HStack {
if isPasswordVisible {
TextField("Password", text: $password)
} else {
SecureField("Password", text: $password)
}
Button(action: {
isPasswordVisible.toggle() // Toggle password visibility
}) {
Image(systemName: isPasswordVisible ? "eye.slash" : "eye") // Eye icon
.foregroundColor(.gray)
}
}
.padding()
.background(Color(.secondarySystemBackground))
.cornerRadius(10)
.padding(.bottom, 30)
// Login Button
Button(action: {
// Perform login action
print("Login tapped with email: \(email), password: \(password)")
}) {
Text("Login")
.foregroundColor(.white)
.font(.headline) // Headline font for button
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.cornerRadius(10)
}
// Forgot Password Link
Button(action: {
// Navigate to forgot password view
print("Forgot password tapped")
}) {
Text("Forgot Password?")
.foregroundColor(.gray)
.padding(.top, 10)
}
Spacer() // Push content to top
// Sign Up Link
HStack {
Text("Don't have an account?")
.foregroundColor(.gray)
Button(action: {
// Navigate to sign-up view
print("Sign up tapped")
}) {
Text("Sign Up")
.foregroundColor(.blue)
}
}
.padding(.bottom, 20) // Add padding at the bottom
}
.padding()
.navigationTitle("Login")
.alert(isPresented: .constant(false)) { // Sample alert
Alert(title: Text("Error"), message: Text("Invalid Credentials."), dismissButton: .default(Text("OK")))
}
}
}
}
}
struct LoginView_Previews: PreviewProvider {
static var previews: some View {
LoginView()
}
}
Key enhancements:
- Background Color: Added a
ZStack
to set the background color. - Color Adjustments: Applied custom colors to the logo and text elements.
- Font Styling: Updated the font for the login button.
- Keyboard Type and Autocapitalization: Set appropriate keyboard types and disabled auto-capitalization for the email field.
- Password visibility: The password can now be toggled from secure to visible for convenience and accessibility for some users.
- Forgot Password and Sign Up Links: Added optional links for improved user experience.
- Alert View: Added Alert Message to alert the users upon login failures.
Data Validation
Validating user inputs is crucial for security and user experience. Add validation to the email and password fields.
import SwiftUI
struct LoginView: View {
@State private var email = ""
@State private var password = ""
@State private var isPasswordVisible = false
@State private var showAlert = false // Alert visibility state
@State private var alertMessage = "" // Alert message
// Email validation function
func isValidEmail(_ email: String) -> Bool {
let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPredicate = NSPredicate(format:"SELF MATCHES %@", emailRegex)
return emailPredicate.evaluate(with: email)
}
var body: some View {
NavigationView {
ZStack {
Color(.systemBackground).edgesIgnoringSafeArea(.all)
VStack {
Image(systemName: "lock.shield.fill")
.resizable()
.scaledToFit()
.frame(width: 120, height: 120)
.foregroundColor(.blue)
.padding(.bottom, 30)
TextField("Email", text: $email)
.padding()
.background(Color(.secondarySystemBackground))
.cornerRadius(10)
.padding(.bottom, 20)
.keyboardType(.emailAddress)
.autocapitalization(.none)
.disableAutocorrection(true) // Disable autocorrection
HStack {
if isPasswordVisible {
TextField("Password", text: $password)
} else {
SecureField("Password", text: $password)
}
Button(action: {
isPasswordVisible.toggle()
}) {
Image(systemName: isPasswordVisible ? "eye.slash" : "eye")
.foregroundColor(.gray)
}
}
.padding()
.background(Color(.secondarySystemBackground))
.cornerRadius(10)
.padding(.bottom, 30)
Button(action: {
// Validation logic
if email.isEmpty || password.isEmpty {
alertMessage = "Please fill in all fields."
showAlert = true
} else if !isValidEmail(email) {
alertMessage = "Please enter a valid email address."
showAlert = true
} else if password.count < 6 {
alertMessage = "Password must be at least 6 characters."
showAlert = true
} else {
// Perform login action if validation passes
print("Login tapped with valid credentials.")
}
}) {
Text("Login")
.foregroundColor(.white)
.font(.headline)
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.cornerRadius(10)
}
Button(action: {
print("Forgot password tapped")
}) {
Text("Forgot Password?")
.foregroundColor(.gray)
.padding(.top, 10)
}
Spacer()
HStack {
Text("Don't have an account?")
.foregroundColor(.gray)
Button(action: {
print("Sign up tapped")
}) {
Text("Sign Up")
.foregroundColor(.blue)
}
}
.padding(.bottom, 20)
}
.padding()
.navigationTitle("Login")
.alert(isPresented: $showAlert) {
Alert(title: Text("Error"), message: Text(alertMessage), dismissButton: .default(Text("OK")))
}
}
}
}
}
struct LoginView_Previews: PreviewProvider {
static var previews: some View {
LoginView()
}
}
Additions to Data Validation Code
-
disableAutocorrection(.true)
will avoid iOS to attempt correct and/or suggest on email which might not what users expects. - Email Validation checks by
isValidEmail()
makes sure to email format validation. - Length and presence check ensures there’s no credential can proceed that’s blank, email not valid, and with less secure password, the process doesn’t continues.
Enhancements for data validation
- Show the user errors immediately using conditional view modifiers based on the length of fields.
- The user must enter credentials meeting criteria (Email in good pattern), and the password meets minimum length policies
showAlert
– is set for a variety of reasons andalertMessage
, to indicate validation failures.
State Management
For complex apps, consider using a state management solution like @ObservedObject
, @EnvironmentObject
, or Redux-like architecture.
Example using @ObservedObject
:
import SwiftUI
import Combine
class AuthenticationViewModel: ObservableObject {
@Published var email = ""
@Published var password = ""
@Published var isLoggedIn = false
func login() {
// Perform login logic
// On success, set isLoggedIn to true
isLoggedIn = true
}
}
struct ContentView: View {
@ObservedObject var authViewModel = AuthenticationViewModel()
var body: some View {
if authViewModel.isLoggedIn {
Text("Welcome!")
} else {
LoginView(authViewModel: authViewModel)
}
}
}
struct LoginView: View {
@ObservedObject var authViewModel: AuthenticationViewModel
var body: some View {
VStack {
TextField("Email", text: $authViewModel.email)
SecureField("Password", text: $authViewModel.password)
Button(action: {
authViewModel.login()
}) {
Text("Login")
}
}
}
}
Conclusion
Building a login screen with SwiftUI is efficient and allows for easy customization and styling. This guide has provided you with the foundational knowledge to create a modern and functional login interface, covering UI design, state management, data validation, and security practices. Remember to adopt these techniques to enhance user experience and security in your apps.