SwiftUI или UIKit: что выбрать для iOS-приложений?

от автора

Привет, Хабр! Сегодня мы в коротком формате сравним 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *