Псевдокод на реальной задаче – проектирование экрана авторизации в iOS

Псевдокод — мощный инструмент проектирования, который особенно полезен при разработке пользовательских интерфейсов и логики взаимодействия. В этой статье мы разберем практический пример: как использовать псевдокод для проектирования экрана авторизации в iOS-приложении с последующей реализацией на UIKit.


Постановка задачи

Прежде чем приступить к написанию кода, важно четко определить требования. Для нашего экрана авторизации:

Требования:

  • Экран должен содержать поля для ввода логина и пароля
  • Логин должен быть валидным email-адресом
  • Пароль должен содержать минимум 8 символов, включая цифры и специальные символы
  • Кнопка входа должна активироваться только при валидных данных
  • При успешной авторизации пользователь должен быть перенаправлен на главный экран
  • При ошибке авторизации должно отображаться сообщение с ошибкой

Теперь, когда требования ясны, применим псевдокод для структурирования нашего решения.

Псевдокод для структуры экрана

Начнем с общей структуры экрана авторизации:

КЛАСС ЭкранАвторизации
    // UI компоненты
    полеВводаЛогина
    полеВводаПароля
    кнопкаВхода
    индикаторЗагрузки
    метка_ошибки

    // Жизненный цикл экрана
    ФУНКЦИЯ настройкаЭкрана()
        создатьUIКомпоненты()
        настроитьКомпоненты()
        добавитьКомпонентыНаЭкран()
        настроитьОграничения()
        настроитьОбработчикиСобытий()
    КОНЕЦ ФУНКЦИИ

    // Обработчики событий
    ФУНКЦИЯ приИзмененииПоляВвода(поле)
        валидироватьПоля()
        обновитьСостояниеКнопкиВхода()
    КОНЕЦ ФУНКЦИИ

    ФУНКЦИЯ приНажатииКнопкиВхода()
        показатьИндикаторЗагрузки()
        отправитьЗапросАвторизации(логин, пароль)
    КОНЕЦ ФУНКЦИИ

    // Вспомогательные функции
    ФУНКЦИЯ валидироватьПоля()
        валидныйЛогин ← проверитьФорматEmail(полеВводаЛогина.текст)
        валидныйПароль ← проверитьПароль(полеВводаПароля.текст)
        ВЕРНУТЬ валидныйЛогин И валидныйПароль
    КОНЕЦ ФУНКЦИИ

    ФУНКЦИЯ обновитьСостояниеКнопкиВхода()
        ЕСЛИ валидироватьПоля() ТОГДА
            кнопкаВхода.активна ← ИСТИНА
        ИНАЧЕ
            кнопкаВхода.активна ← ЛОЖЬ
        КОНЕЦ ЕСЛИ
    КОНЕЦ ФУНКЦИИ
КОНЕЦ КЛАССА

Псевдокод для бизнес-логики авторизации

Теперь опишем логику авторизации и обработки ответа от сервера:

КЛАСС МенеджерАвторизации
    ФУНКЦИЯ авторизоваться(логин, пароль, обработчикУспеха, обработчикОшибки)
        ЕСЛИ НЕ проверитьФорматEmail(логин) ИЛИ НЕ проверитьПароль(пароль) ТОГДА
            вызватьОбработчикОшибки("Неверный формат логина или пароля")
            ВЕРНУТЬ
        КОНЕЦ ЕСЛИ

        параметрыЗапроса ← {
            "email": логин,
            "password": пароль
        }

        выполнитьСетевойЗапрос("/auth/login", "POST", параметрыЗапроса, ФУНКЦИЯ(ответ, ошибка) {
            ЕСЛИ ошибка НЕ РАВНО null ТОГДА
                вызватьОбработчикОшибки(ошибка.сообщение)
            ИНАЧЕ ЕСЛИ ответ.статус = 200 ТОГДА
                сохранитьТокен(ответ.данные.токен)
                вызватьОбработчикУспеха()
            ИНАЧЕ
                вызватьОбработчикОшибки(ответ.данные.сообщение)
            КОНЕЦ ЕСЛИ
        })
    КОНЕЦ ФУНКЦИИ

    ФУНКЦИЯ проверитьФорматEmail(email)
        // Проверка формата email через регулярное выражение
        emailРегулярка ← "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
        ВЕРНУТЬ email соответствует emailРегулярка
    КОНЕЦ ФУНКЦИИ

    ФУНКЦИЯ проверитьПароль(пароль)
        ЕСЛИ длина(пароль) < 8 ТОГДА
            ВЕРНУТЬ ЛОЖЬ
        КОНЕЦ ЕСЛИ

        естьЦифра ← ЛОЖЬ
        естьСпецСимвол ← ЛОЖЬ

        ДЛЯ КАЖДОГО символ В пароль
            ЕСЛИ символ это цифра ТОГДА
                естьЦифра ← ИСТИНА
            ИНАЧЕ ЕСЛИ символ это спецСимвол ТОГДА
                естьСпецСимвол ← ИСТИНА
            КОНЕЦ ЕСЛИ
        КОНЕЦ ЦИКЛА

        ВЕРНУТЬ естьЦифра И естьСпецСимвол
    КОНЕЦ ФУНКЦИИ
КОНЕЦ КЛАССА

Псевдокод для обработки событий UI

Теперь детализируем как UI реагирует на действия пользователя:

// Внутри класса ЭкранАвторизации

ФУНКЦИЯ настроитьОбработчикиСобытий()
    полеВводаЛогина.добавитьОбработчикИзмененияТекста(приИзмененииПоляВвода)
    полеВводаПароля.добавитьОбработчикИзмененияТекста(приИзмененииПоляВвода)
    кнопкаВхода.добавитьОбработчикНажатия(приНажатииКнопкиВхода)
КОНЕЦ ФУНКЦИИ

ФУНКЦИЯ приНажатииКнопкиВхода()
    отключитьВзаимодействие()
    показатьИндикаторЗагрузки()
    скрытьСообщениеОбОшибке()

    логин ← полеВводаЛогина.текст
    пароль ← полеВводаПароля.текст

    МенеджерАвторизации.авторизоваться(логин, пароль, 
        // Обработчик успеха
        ФУНКЦИЯ() {
            скрытьИндикаторЗагрузки()
            включитьВзаимодействие()
            перейтиНаГлавныйЭкран()
        },
        // Обработчик ошибки
        ФУНКЦИЯ(сообщениеОбОшибке) {
            скрытьИндикаторЗагрузки()
            включитьВзаимодействие()
            показатьСообщениеОбОшибке(сообщениеОбОшибке)
        }
    )
КОНЕЦ ФУНКЦИИ

ФУНКЦИЯ показатьСообщениеОбОшибке(сообщение)
    метка_ошибки.текст ← сообщение
    метка_ошибки.видимость ← ИСТИНА

    // Анимация для привлечения внимания
    анимироватьПоявлениеМеткиОшибки()
КОНЕЦ ФУНКЦИИ

ФУНКЦИЯ скрытьСообщениеОбОшибке()
    метка_ошибки.видимость ← ЛОЖЬ
КОНЕЦ ФУНКЦИИ

ФУНКЦИЯ отключитьВзаимодействие()
    полеВводаЛогина.доступность ← ЛОЖЬ
    полеВводаПароля.доступность ← ЛОЖЬ
    кнопкаВхода.доступность ← ЛОЖЬ
КОНЕЦ ФУНКЦИИ

ФУНКЦИЯ включитьВзаимодействие()
    полеВводаЛогина.доступность ← ИСТИНА
    полеВводаПароля.доступность ← ИСТИНА
    кнопкаВхода.доступность ← ИСТИНА
КОНЕЦ ФУНКЦИИ

Преобразование псевдокода в код на Swift с UIKit

Теперь преобразуем наш псевдокод в реальный код Swift с использованием UIKit. Начнем с экрана авторизации:

import UIKit

class LoginViewController: UIViewController {

    // MARK: - UI Elements

    private lazy var emailTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Email"
        textField.keyboardType = .emailAddress
        textField.autocapitalizationType = .none
        textField.autocorrectionType = .no
        textField.borderStyle = .roundedRect
        textField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()

    private lazy var passwordTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Password"
        textField.isSecureTextEntry = true
        textField.borderStyle = .roundedRect
        textField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()

    private lazy var loginButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Login", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        button.isEnabled = false
        button.addTarget(self, action: #selector(loginButtonTapped), for: .touchUpInside)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    private let activityIndicator: UIActivityIndicatorView = {
        let indicator = UIActivityIndicatorView(style: .medium)
        indicator.hidesWhenStopped = true
        indicator.translatesAutoresizingMaskIntoConstraints = false
        return indicator
    }()

    private let errorLabel: UILabel = {
        let label = UILabel()
        label.textColor = .systemRed
        label.font = .systemFont(ofSize: 14)
        label.textAlignment = .center
        label.numberOfLines = 0
        label.isHidden = true
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    // MARK: - Properties

    private let authManager = AuthManager.shared

    // MARK: - Lifecycle Methods

    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }

    // MARK: - Setup UI

    private func setupUI() {
        view.backgroundColor = .white

        view.addSubview(emailTextField)
        view.addSubview(passwordTextField)
        view.addSubview(loginButton)
        view.addSubview(activityIndicator)
        view.addSubview(errorLabel)

        NSLayoutConstraint.activate([
            emailTextField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 100),
            emailTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            emailTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            emailTextField.heightAnchor.constraint(equalToConstant: 50),

            passwordTextField.topAnchor.constraint(equalTo: emailTextField.bottomAnchor, constant: 20),
            passwordTextField.leadingAnchor.constraint(equalTo: emailTextField.leadingAnchor),
            passwordTextField.trailingAnchor.constraint(equalTo: emailTextField.trailingAnchor),
            passwordTextField.heightAnchor.constraint(equalTo: emailTextField.heightAnchor),

            loginButton.topAnchor.constraint(equalTo: passwordTextField.bottomAnchor, constant: 40),
            loginButton.leadingAnchor.constraint(equalTo: emailTextField.leadingAnchor),
            loginButton.trailingAnchor.constraint(equalTo: emailTextField.trailingAnchor),
            loginButton.heightAnchor.constraint(equalToConstant: 50),

            activityIndicator.centerXAnchor.constraint(equalTo: loginButton.centerXAnchor),
            activityIndicator.centerYAnchor.constraint(equalTo: loginButton.centerYAnchor),

            errorLabel.topAnchor.constraint(equalTo: loginButton.bottomAnchor, constant: 20),
            errorLabel.leadingAnchor.constraint(equalTo: emailTextField.leadingAnchor),
            errorLabel.trailingAnchor.constraint(equalTo: emailTextField.trailingAnchor)
        ])
    }

    // MARK: - Actions

    @objc private func textFieldDidChange(_ textField: UITextField) {
        validateFields()
    }

    @objc private func loginButtonTapped() {
        disableInteraction()
        activityIndicator.startAnimating()
        hideErrorMessage()

        guard let email = emailTextField.text, let password = passwordTextField.text else {
            return
        }

        authManager.login(email: email, password: password) { [weak self] result in
            guard let self = self else { return }

            DispatchQueue.main.async {
                self.activityIndicator.stopAnimating()
                self.enableInteraction()

                switch result {
                case .success:
                    self.navigateToMainScreen()
                case .failure(let error):
                    self.showErrorMessage(error.localizedDescription)
                }
            }
        }
    }

    // MARK: - Helper Methods

    private func validateFields() {
        guard let email = emailTextField.text, let password = passwordTextField.text else {
            loginButton.isEnabled = false
            return
        }

        let isEmailValid = authManager.isValidEmail(email)
        let isPasswordValid = authManager.isValidPassword(password)

        loginButton.isEnabled = isEmailValid && isPasswordValid

        // Визуальный индикатор валидности
        emailTextField.layer.borderColor = isEmailValid ? UIColor.systemGreen.cgColor : nil
        passwordTextField.layer.borderColor = isPasswordValid ? UIColor.systemGreen.cgColor : nil
    }

    private func showErrorMessage(_ message: String) {
        errorLabel.text = message
        errorLabel.isHidden = false

        // Простая анимация для привлечения внимания
        UIView.animate(withDuration: 0.3, animations: {
            self.errorLabel.alpha = 0
        }) { _ in
            UIView.animate(withDuration: 0.3) {
                self.errorLabel.alpha = 1
            }
        }
    }

    private func hideErrorMessage() {
        errorLabel.isHidden = true
    }

    private func disableInteraction() {
        emailTextField.isEnabled = false
        passwordTextField.isEnabled = false
        loginButton.isEnabled = false
    }

    private func enableInteraction() {
        emailTextField.isEnabled = true
        passwordTextField.isEnabled = true
        validateFields() // Восстанавливаем правильное состояние кнопки
    }

    private func navigateToMainScreen() {
        // Переход на главный экран
        let mainVC = MainViewController()
        let navController = UINavigationController(rootViewController: mainVC)
        navController.modalPresentationStyle = .fullScreen
        present(navController, animated: true)
    }
}

Теперь реализуем класс AuthManager, который будет отвечать за логику авторизации:

import Foundation

class AuthManager {

    static let shared = AuthManager()

    private init() {}

    enum AuthError: Error {
        case invalidCredentials
        case networkError
        case serverError(String)

        var localizedDescription: String {
            switch self {
            case .invalidCredentials:
                return "Invalid email or password format"
            case .networkError:
                return "Network connection error. Please try again."
            case .serverError(let message):
                return message
            }
        }
    }

    func login(email: String, password: String, completion: @escaping (Result<Void, AuthError>) -> Void) {
        // Перед отправкой на сервер проверяем формат
        guard isValidEmail(email), isValidPassword(password) else {
            completion(.failure(.invalidCredentials))
            return
        }

        // Создаем параметры запроса
        let parameters: [String: Any] = [
            "email": email,
            "password": password
        ]

        // Симуляция сетевого запроса
        DispatchQueue.global().asyncAfter(deadline: .now() + 1.5) {
            // Для демонстрации, проверяем специальные учетные данные
            if email == "test@example.com" && password == "Password123!" {
                // Успешная авторизация
                UserDefaults.standard.set("demo_token_123", forKey: "authToken")
                completion(.success(()))
            } else {
                // Ошибка авторизации
                completion(.failure(.serverError("Invalid email or password. Please try again.")))
            }
        }
    }

    func isValidEmail(_ email: String) -> Bool {
        let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}"
        let emailPredicate = NSPredicate(format: "SELF MATCHES %@", emailRegex)
        return emailPredicate.evaluate(with: email)
    }

    func isValidPassword(_ password: String) -> Bool {
        guard password.count >= 8 else { return false }

        let hasDigit = password.contains { $0.isNumber }
        let hasSpecialChar = password.contains { !$0.isLetter && !$0.isNumber }

        return hasDigit && hasSpecialChar
    }
}

И наконец, заглушка для главного экрана:

import UIKit

class MainViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        title = "Main Screen"

        // Добавляем кнопку выхода
        navigationItem.rightBarButtonItem = UIBarButtonItem(
            title: "Logout",
            style: .plain,
            target: self,
            action: #selector(logoutTapped)
        )

        setupWelcomeLabel()
    }

    private func setupWelcomeLabel() {
        let welcomeLabel = UILabel()
        welcomeLabel.text = "Welcome! You are successfully logged in."
        welcomeLabel.textAlignment = .center
        welcomeLabel.numberOfLines = 0
        welcomeLabel.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(welcomeLabel)

        NSLayoutConstraint.activate([
            welcomeLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            welcomeLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            welcomeLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            welcomeLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
        ])
    }

    @objc private func logoutTapped() {
        // Удаляем токен и возвращаемся на экран входа
        UserDefaults.standard.removeObject(forKey: "authToken")
        dismiss(animated: true)
    }
}

Преимущества использования псевдокода в этом примере

Использование псевдокода при разработке экрана авторизации дало нам несколько важных преимуществ:

  1. Структурированное мышление — мы разделили задачу на логические блоки еще до написания кода
  2. Фокус на логике — мы сосредоточились на поведении и взаимодействии, а не на деталях реализации
  3. Упрощение коммуникации — такой псевдокод понятен не только iOS-разработчикам, но и другим участникам команды
  4. Переиспользование паттернов — описанный алгоритм можно адаптировать для других платформ (Android, Web)
  5. Предотвращение ошибок — мы продумали обработку различных сценариев до написания кода
Также читайте:  Числа и системы счисления

Практические рекомендации по использованию псевдокода в UI-разработке

Основываясь на нашем примере, можно выделить несколько рекомендаций по эффективному использованию псевдокода при разработке пользовательских интерфейсов:

  1. Начинайте с пользовательских историй — описывайте, как пользователь будет взаимодействовать с интерфейсом
  2. Используйте иерархический подход — разделяйте интерфейс на логические компоненты
  3. Детализируйте состояния UI — описывайте, как меняется интерфейс в зависимости от действий и событий
  4. Документируйте валидации — четко определяйте правила проверки пользовательского ввода
  5. Отделяйте бизнес-логику от UI — это позволит легко адаптировать интерфейс при изменениях

Заключение

Применение псевдокода при разработке функциональности авторизации позволяет эффективно спроектировать и реализовать не только UI, но и логику его работы. Этот подход особенно полезен при работе над сложными экранами с множеством взаимодействий и состояний.

При разработке собственных экранов авторизации, регистрации или других подобных компонентов, рекомендуется сначала описать их в виде псевдокода — это значительно упростит процесс реализации и поможет избежать многих ошибок.


Не забудьте загрузить приложение iJun в AppStore и подписаться на мой YouTube канал для получения дополнительных материалов по iOS-разработке.

Список литературы и дополнительных материалов

  1. Human Interface Guidelines - Apple Developer
  2. UIKit Documentation - Apple Developer
  3. Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin