
Привет, Хабр! Сегодня мы в коротком формате сравним SwiftUI и UIKit: где SwiftUI реально выигрывает, а где старый добрый UIKit остаётся незаменимым.
Архитектурные различия
UIKit
В UIKit вы вручную создаёте экземпляры UIViewController, добавляете UIView, настраиваете AutoLayout (или пишете frame-based лэйауты, если душа требует свободы) и связываете события через делегаты или target-action.
Например, форма авторизации на UIKit может выглядеть так:
import UIKit class LoginViewController: UIViewController { private let usernameField: UITextField = { let field = UITextField() field.placeholder = "Логин" field.borderStyle = .roundedRect return field }() private let passwordField: UITextField = { let field = UITextField() field.placeholder = "Пароль" field.borderStyle = .roundedRect field.isSecureTextEntry = true return field }() private let loginButton: UIButton = { let button = UIButton(type: .system) button.setTitle("Войти", for: .normal) return button }() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white view.addSubview(usernameField) view.addSubview(passwordField) view.addSubview(loginButton) setupConstraints() } private func setupConstraints() { usernameField.translatesAutoresizingMaskIntoConstraints = false passwordField.translatesAutoresizingMaskIntoConstraints = false loginButton.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ usernameField.centerXAnchor.constraint(equalTo: view.centerXAnchor), usernameField.topAnchor.constraint(equalTo: view.topAnchor, constant: 200), usernameField.widthAnchor.constraint(equalToConstant: 250), passwordField.centerXAnchor.constraint(equalTo: view.centerXAnchor), passwordField.topAnchor.constraint(equalTo: usernameField.bottomAnchor, constant: 20), passwordField.widthAnchor.constraint(equalTo: usernameField.widthAnchor), loginButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), loginButton.topAnchor.constraint(equalTo: passwordField.bottomAnchor, constant: 20) ]) } }
Здесь не только создаётся три элемента, но ещё и вручную настраиваются констрейнты. Контроль полный, но код получается многословным и порой бессмысленно шаблонным.
SwiftUI
SwiftUI предлагает совершенно другой взгляд – вы описываете, что должно быть на экране, и система сама решает, как это отрисовать.
Пример той же формы авторизации на SwiftUI:
import SwiftUI struct LoginView: View { @State private var username = "" @State private var password = "" var body: some View { VStack(spacing: 20) { TextField("Логин", text: $username) .textFieldStyle(RoundedBorderTextFieldStyle()) .padding(.horizontal, 20) SecureField("Пароль", text: $password) .textFieldStyle(RoundedBorderTextFieldStyle()) .padding(.horizontal, 20) Button("Войти") { // Логика авторизации print("Попытка входа с логином \(username)") } .padding() .background(Color.blue) .foregroundColor(.white) .cornerRadius(8) } .padding() } } struct LoginView_Previews: PreviewProvider { static var previews: some View { LoginView() } }
Как видите, код становится компактнее, понятнее и, что самое главное, декларативнее. Вам не нужно заботиться о констрейнтах – SwiftUI сам позаботится о лэйауте. Но вот за кажущейся простотой скрываются алгоритмы diffing-а, которые при каждом изменении состояния пересчитывают всё дерево представлений.
Производительность
Как видите, код становится компактнее, понятнее и, что самое главное, декларативнее. Вам не нужно заботиться о констрейнтах – SwiftUI сам позаботится о лэйауте. Но вот за кажущейся простотой скрываются алгоритмы diffing-а, которые при каждом изменении состояния пересчитывают всё дерево представлений.
Производительность
UIKit
UIKit напрямую работает с CALayer и Core Animation. Это даёт возможность оптимизировать производительность на уровне отрисовки, кешировать содержимое, управлять GPU-ресурсами. Если есть огромный список данных, используется UITableView или UICollectionView с механизмом повторного использования ячеек. Можно вручную настроить estimatedRowHeight и rowHeight, чтобы добиться оптимального баланса между производительностью и точностью лэйаута:
tableView.estimatedRowHeight = 44 tableView.rowHeight = UITableView.automaticDimension
Кроме того, можно проследить цепочку отрисовки через инструменты типа Instruments, анализируя каждую фазу и устраняя узкие места.
SwiftUI
SwiftUI работает на основе механизма, который сравнивает старое и новое дерево представлений при изменении состояния. Если вы не оптимизируете список с помощью правильного указания id или использования модификаторов типа EquatableView, система может пересоздавать большое количество элементов, что приводит к лишним пересчётам. Вот типичная ошибка:
List(0..<1000) { index in Text("Элемент \(index)") }
Каждое изменение состояния заставляет SwiftUI пересчитывать все 1000 элементов. Правильное решение – указать идентификатор:
List(0..<1000, id: \.self) { index in Text("Элемент \(index)") }
Это позволяет системе понять, что элементы не изменились, и избежать ненужной перерисовки.
Анимации
UIKit
В UIKit анимации строятся через UIView.animate и Core Animation. Можно задавать свои кривые, управлять задержками, комбинировать анимации и контролировать продолжительность и синхронизацию. Это позволяет создавать сложные визуальные эффекты, требующие точной настройки:
UIView.animate(withDuration: 0.3, delay: 0, options: [.curveEaseInOut], animations: { self.view.alpha = 0.5 }, completion: nil)
SwiftUI
SwiftUI предлагает анимации через обёртку withAnimation – изменяете состояние, и все изменения плавно перерисовываются.
withAnimation { opacity = 0.5 }
Но если нужно что-то более сложное – например, кастомные кривые анимации, последовательные переходы или контроль над фазами анимации – может понадобиться уйти в UIKit или использовать комбинированный подход. В SwiftUI анимации зачастую ограничены декларативным описанием, и для нестандартных эффектов приходится прибегать к трюкам или даже интегрировать UIViewRepresentable для встраивания UIKit-компонентов.
Управление состоянием
Одним из главных козырей SwiftUI является его встроенная система управления состоянием. Используя свойства типа @State, @Binding, @ObservedObject и @EnvironmentObject, можно буквально забыть о том, что происходит – система сама обновляет интерфейс при изменении данных. Это выглядит очень и просто, но, как известно, за такой декларативной простотой скрывается тонкая настройка. Если не структурировать данные правильно, можно столкнуться с лишними обновлениями, или с зацикливанием обновлений, когда одно изменение запускает цепную реакцию перерасчётов.
Пример наблюдаемого объекта в SwiftUI:
import SwiftUI import Combine class UserViewModel: ObservableObject { @Published var username: String = "" @Published var isLoggedIn: Bool = false }
Здесь все обновления происходят автоматически: как только вы изменяете username или isLoggedIn, все представления, зависящие от этих данных, перерисовываются.
В UIKit ситуация совсем иная. Здесь управление состоянием зачастую реализуется через делегаты, нотификации или архитектурные паттерны вроде MVC и MVVM.
Например, рассмотрим два способа отслеживания изменений в UIKit.
Создадим модель с делегатом, чтобы уведомлять об изменениях:
protocol UserModelDelegate: AnyObject { func userModelDidUpdate(_ model: UserModel) } class UserModel { weak var delegate: UserModelDelegate? var username: String { didSet { delegate?.userModelDidUpdate(self) } } init(username: String) { self.username = username } }
А теперь – UIViewController, который будет следить за изменениями:
import UIKit class UserViewController: UIViewController, UserModelDelegate { var userModel = UserModel(username: "Начальное имя") override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white userModel.delegate = self // Симулируем обновление через 2 секунды DispatchQueue.main.asyncAfter(deadline: .now() + 2) { self.userModel.username = "Новое имя" } } func userModelDidUpdate(_ model: UserModel) { print("Делегат: имя пользователя изменилось на \(model.username)") // Здесь можно обновить UI, если требуется } }
Если хотите еще большей автоматизации без явных вызовов делегата, можно использовать KVO:
import Foundation class UserModel: NSObject { @objc dynamic var username: String init(username: String) { self.username = username super.init() } }
Настроим наблюдение в контроллере:
import UIKit class UserViewController: UIViewController { var userModel = UserModel(username: "Начальное имя") var observation: NSKeyValueObservation? override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white observation = userModel.observe(\.username, options: [.old, .new]) { model, change in if let oldValue = change.oldValue, let newValue = change.newValue { print("KVO: имя пользователя изменилось с \(oldValue) на \(newValue)") } } // Симулируем обновление через 2 секунды DispatchQueue.main.asyncAfter(deadline: .now() + 2) { self.userModel.username = "Новое имя" } } deinit { observation?.invalidate() } }
В обоих случаях получаем полный контроль над моментами обновления, хотя и жертвуете простотой декларативного подхода SwiftUI. Разница в том, что в UIKit ответственность за управление состоянием лежит на вас – и это может быть как преимуществом, так и источником ошибок, если не уделить должного внимания оптимизации.
Кстати, на основе этого опыта наши приложения М.Видео и Эльдорадо обновлены и снова доступны в AppStore – мы интегрировали современные технологии, сохранив проверенную временем стабильность.
Теперь у нас:
-
Обновлённый дизайн – используя гибридный подход UIKit и SwiftUI.
-
Быстрая работа – оптимизированная загрузка страниц, умное кеширование.
-
Новый поиск – лучше, быстрее, удобнее.
-
Поддержка бонусов – единый счёт в М.Клубе.
Так что если у вас iPhone – пора обновить приложение!
Скачать приложение М.Видео в AppStore. Скачать Эльдорадо в AppStore.
Спасибо, что дочитали до конца!
ссылка на оригинал статьи https://habr.com/ru/articles/899694/
Добавить комментарий