Псевдокод на реальной задаче – проектирование экрана авторизации в iOS
Псевдокод — мощный инструмент проектирования, который особенно полезен при разработке пользовательских интерфейсов и логики взаимодействия. В этой статье мы разберем практический пример: как использовать псевдокод для проектирования экрана авторизации в iOS-приложении с последующей реализацией на UIKit.
Содержание:
- Постановка задачи
- Псевдокод для структуры экрана
- Псевдокод для бизнес-логики авторизации
- Псевдокод для обработки событий UI
- Преобразование псевдокода в код на Swift с UIKit
- Преимущества использования псевдокода в этом примере
- Практические рекомендации по использованию псевдокода в UI-разработке
- Заключение
- Список литературы и дополнительных материалов
Постановка задачи
Прежде чем приступить к написанию кода, важно четко определить требования. Для нашего экрана авторизации:
Требования:
- Экран должен содержать поля для ввода логина и пароля
- Логин должен быть валидным 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)
}
}
Преимущества использования псевдокода в этом примере
Использование псевдокода при разработке экрана авторизации дало нам несколько важных преимуществ:
- Структурированное мышление — мы разделили задачу на логические блоки еще до написания кода
- Фокус на логике — мы сосредоточились на поведении и взаимодействии, а не на деталях реализации
- Упрощение коммуникации — такой псевдокод понятен не только iOS-разработчикам, но и другим участникам команды
- Переиспользование паттернов — описанный алгоритм можно адаптировать для других платформ (Android, Web)
- Предотвращение ошибок — мы продумали обработку различных сценариев до написания кода
Практические рекомендации по использованию псевдокода в UI-разработке
Основываясь на нашем примере, можно выделить несколько рекомендаций по эффективному использованию псевдокода при разработке пользовательских интерфейсов:
- Начинайте с пользовательских историй — описывайте, как пользователь будет взаимодействовать с интерфейсом
- Используйте иерархический подход — разделяйте интерфейс на логические компоненты
- Детализируйте состояния UI — описывайте, как меняется интерфейс в зависимости от действий и событий
- Документируйте валидации — четко определяйте правила проверки пользовательского ввода
- Отделяйте бизнес-логику от UI — это позволит легко адаптировать интерфейс при изменениях
Заключение
Применение псевдокода при разработке функциональности авторизации позволяет эффективно спроектировать и реализовать не только UI, но и логику его работы. Этот подход особенно полезен при работе над сложными экранами с множеством взаимодействий и состояний.
При разработке собственных экранов авторизации, регистрации или других подобных компонентов, рекомендуется сначала описать их в виде псевдокода — это значительно упростит процесс реализации и поможет избежать многих ошибок.
Не забудьте загрузить приложение iJun в AppStore и подписаться на мой YouTube канал для получения дополнительных материалов по iOS-разработке.