0
0
iOS Swiftmobile~20 mins

Mock objects and protocols in iOS Swift - Mini App: Build & Ship

Choose your learning style9 modes available
Build: Login Screen
A simple login screen that uses a protocol for authentication. We will create a mock object to simulate the login process for testing.
Target UI
---------------------
|     Login Screen    |
|-------------------|
| Username: ________ |
| Password: ________ |
|                   |
|   [Login Button]   |
|                   |
| Status: __________ |
---------------------
Create a protocol named AuthService with a login function.
Implement a MockAuthService that conforms to AuthService and simulates login success or failure.
Build a LoginViewController with username and password text fields and a login button.
When login button is tapped, call the login function from AuthService.
Display login success or failure message in a status label.
Starter Code
iOS Swift
import UIKit

protocol AuthService {
    func login(username: String, password: String, completion: @escaping (Bool) -> Void)
}

class LoginViewController: UIViewController {
    // TODO: Add UI elements and AuthService property
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        // TODO: Setup UI
    }
    
    @objc func loginButtonTapped() {
        // TODO: Call login on AuthService
    }
}
Task 1
Task 2
Task 3
Task 4
Task 5
Task 6
Solution
iOS Swift
import UIKit

protocol AuthService {
    func login(username: String, password: String, completion: @escaping (Bool) -> Void)
}

class MockAuthService: AuthService {
    func login(username: String, password: String, completion: @escaping (Bool) -> Void) {
        // Simulate network delay
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            if username == "user" && password == "pass" {
                completion(true)
            } else {
                completion(false)
            }
        }
    }
}

class LoginViewController: UIViewController {
    private let usernameField = UITextField()
    private let passwordField = UITextField()
    private let loginButton = UIButton(type: .system)
    private let statusLabel = UILabel()
    
    var authService: AuthService = MockAuthService()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        setupUI()
    }
    
    private func setupUI() {
        usernameField.placeholder = "Username"
        usernameField.borderStyle = .roundedRect
        usernameField.autocapitalizationType = .none
        usernameField.translatesAutoresizingMaskIntoConstraints = false
        
        passwordField.placeholder = "Password"
        passwordField.borderStyle = .roundedRect
        passwordField.isSecureTextEntry = true
        passwordField.translatesAutoresizingMaskIntoConstraints = false
        
        loginButton.setTitle("Login", for: .normal)
        loginButton.addTarget(self, action: #selector(loginButtonTapped), for: .touchUpInside)
        loginButton.translatesAutoresizingMaskIntoConstraints = false
        
        statusLabel.text = ""
        statusLabel.textAlignment = .center
        statusLabel.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(usernameField)
        view.addSubview(passwordField)
        view.addSubview(loginButton)
        view.addSubview(statusLabel)
        
        NSLayoutConstraint.activate([
            usernameField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40),
            usernameField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            usernameField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            passwordField.topAnchor.constraint(equalTo: usernameField.bottomAnchor, constant: 20),
            passwordField.leadingAnchor.constraint(equalTo: usernameField.leadingAnchor),
            passwordField.trailingAnchor.constraint(equalTo: usernameField.trailingAnchor),
            
            loginButton.topAnchor.constraint(equalTo: passwordField.bottomAnchor, constant: 30),
            loginButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            
            statusLabel.topAnchor.constraint(equalTo: loginButton.bottomAnchor, constant: 30),
            statusLabel.leadingAnchor.constraint(equalTo: usernameField.leadingAnchor),
            statusLabel.trailingAnchor.constraint(equalTo: usernameField.trailingAnchor)
        ])
    }
    
    @objc func loginButtonTapped() {
        let username = usernameField.text ?? ""
        let password = passwordField.text ?? ""
        statusLabel.text = "Logging in..."
        authService.login(username: username, password: password) { [weak self] success in
            DispatchQueue.main.async {
                self?.statusLabel.text = success ? "Login Successful!" : "Login Failed. Try again."
            }
        }
    }
}

We defined a protocol AuthService with a login method that takes username, password, and a completion handler.

The MockAuthService class simulates a login by checking if username is "user" and password is "pass" after a short delay.

The LoginViewController sets up UI elements: two text fields, a button, and a label.

When the login button is tapped, it calls authService.login and updates the status label based on success or failure.

This setup allows easy swapping of AuthService implementations for real or mock authentication, which is great for testing.

Final Result
Completed Screen
---------------------
|     Login Screen    |
|-------------------|
| Username: user     |
| Password: *****    |
|                   |
|   [Login Button]   |
|                   |
| Status: Login Successful! |
---------------------
User types username and password.
User taps Login button.
Status label shows 'Logging in...' briefly.
After delay, status label updates to 'Login Successful!' if credentials are correct, else 'Login Failed. Try again.'
Stretch Goal
Add a loading spinner that appears while login is in progress and disappears after completion.
💡 Hint
Use UIActivityIndicatorView and show it when login starts, hide it in the completion handler.