Как стать автором
Обновить
119.06

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

Время на прочтение6 мин
Количество просмотров674

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

Спасибо, что дочитали до конца!

Теги:
Хабы:
Всего голосов 4: ↑4 и ↓0+6
Комментарии1

Публикации

Информация

Сайт
mvideoeldorado.ru
Дата регистрации
Дата основания
Численность
свыше 10 000 человек
Местоположение
Россия