![](https://habrastorage.org/getpro/habr/upload_files/5aa/96d/27b/5aa96d27b1c006211a11eea06906192c.png)
Для разработки iOS-приложений можно использовать два основных фреймворка: UIKit и SwiftUI. Однако при переходе со старого инструмента на новый, многие разработчики сталкиваются с трудностями, ведь парадигмы программирования у них сильно отличаются.
Цель статьи
Помочь разработчикам приложений для iOS понять различия между императивным и декларативным подходами к программированию, а также рассмотреть плюсы и минусы фреймворков UIKit и SwiftUI. Знакомство с ними необходимо для оптимизации процесса разработки и создания продукта высокого качества.
В первой части статьи рассмотрю императивный и декларативный подходы, и это станет основой для всего последующего материала.
Во второй части расскажу о героях этой статьи — фреймворках UIKit и SwiftUI. Я покажу, какие подходы используются при разработке на каждом из них и в чём их отличия.
В третьей части разберу плюсы и минусы рассмотренных подходов, чтобы сравнить два инструмента и помочь читателю выбрать то, что лучше подходит для решения его задач.
Почему я над этим задумался?
Наша команда iOS-разработки приложения для продавцов столкнулась с проблемой отсутствия коммерческого опыта в разработке на SwiftUI и понимания двух подходов программирования: императивного и декларативного.
Темпы роста команды были очень высокими. Мы нанимали крутых инженеров, но большинство из них не сталкивались в своей работе со SwiftUI. Поэтому им приходилось переучиваться в процессе адаптации, набивать шишки на код-ревью и изучать новую технологию на ходу. База знаний о SwiftUI для погружения новых сотрудников у нас отсутствует. Эта ситуация дала понять, что большинство разработчиков сталкиваются с одной и той же проблемой: сложностью перехода с императивного на декларативный подход. Это подтолкнуло меня сначала подготовить доклад для внутреннего митапа Ozon, а позже — написать эту статью.
Содержание
Императивный и декларативный подходы
Рассмотрим два подхода к программированию: императивный и декларативный.
Императивный подход основывается на определении последовательности команд и операций, которые выполняются шаг за шагом в определённом порядке. Разработчику необходимо подробно описать, как программа должна работать и какие операции должны быть выполнены, чтобы добиться нужных результатов.
UIKit — это пример императивного подхода. Фреймворк подразумевает полный контроль над состоянием интерфейса и необходимость вручную управлять каждым его элементом с помощью кода. Этот подход имеет свои преимущества в схемах, где полный контроль текущего состояния является критически важным.
Декларативный подход, в свою очередь, сфокусирован на том, чтобы описывать, что программа должна делать, а не как это должно быть выполнено. Разработчик описывает желаемый результат, а система сама определяет, как его получить.
SwiftUI - это пример декларативного подхода. Он позволяет разработчикам указывать, что они хотят отображать на экране, и система на основании этого описания сама определяет, как будет выглядеть интерфейс.
Благодаря такому подходу, разработчики могут писать менее сложный код. Система берет на себя множество задач, связанных с управлением состоянием. Ранее приходилось выполнять эти задачи вручную при императивном подходе.
Для того, чтобы более четко понимать, о каком состоянии идет речь, уточню- это состояние пользовательского интерфейса. Разработчик может описать, как должен выглядеть интерфейс при различных сценариях, например, когда пользователь что-то вводит или прокручивает контент.
Таким образом, декларативный подход в SwiftUI позволяет разработчикам сосредоточиться на описании того, что нужно получить в итоге, вместо того, чтобы писать множество манипуляций для управления состоянием пользовательского интерфейса.
![](https://habrastorage.org/getpro/habr/upload_files/b69/c39/cdc/b69c39cdce396b752cb176d8dd3b991c.jpeg)
Давайте представим, что у нас есть компания. У неё есть руководитель, пожилой человек, который символизирует императивную часть компании, аналогичную UIKit. У этого руководителя методы управления довольно архаичные. Он настаивает на том, чтобы все его инструкции выполнялись строго в том порядке, который он установил, и в точности так, как он их сформулировал. В противном случае, по его мнению, компания не сможет добиться нужного результата. На схеме выше видно, что от руководителя спускается ряд команд, а для достижения конечного результата нужно совершить несколько действий.
С другой стороны, у нас есть декларативный стартап, которым руководит парнишка, он же Mr SwiftUI. У него не очень много опыта, но большие амбиции и стремления, он полностью доверяет своим подчинённым и при распределении задач полагается на их опыт и экспертизу, поэтому просто описывает видение конечной реализации и делегирует решение коллегам.
Примеры кода
// Императивный подход
func sumOfSquaresI(array: [Int]) -> Int {
var sum = 0
for number in array {
let square = number * number
sum += square
}
return sum
}
// Декларативный подход
func sumOfSquaresD(array: [Int]) -> Int {
array.map { $0 * $0 }.reduce(0, +)
}
В этих примерах мы решаем одну и ту же задачу — вычисление суммы квадратов чисел в массиве — но делаем это по-разному: с помощью императивного и декларативного подходов.
В первом примере мы используем цикл for, чтобы последовательно перебрать элементы массива, и переменную sum, чтобы хранить сумму. Таким образом, мы описываем последовательность шагов, необходимых для решения задачи.
Во втором примере мы также определяем функцию для нахождения суммы квадратов чисел в массиве. Однако вместо последовательного описания шагов мы используем функциональные методы map и reduce. Метод map преобразует каждый элемент массива в его квадрат, а метод reduce находит сумму преобразованных элементов. То есть мы описываем, что нужно сделать, а не как.
Важно знать об особенностях каждого подхода, чтобы выбирать оптимальные инструменты в зависимости от поставленных задачи и требований проекта.
Важно понимать, что выбор подхода зависит от поставленных задач и требований проекта. Например, для написания алгоритмов с последовательным выполнением операций предпочтительным может быть императивный подход, а для описания пользовательского интерфейса декларативный подход может оказаться более удобным.
Устаревающий UIKit ?? vs Новый SwiftUI ?
В этом разделе мы рассмотрим два фреймворка для разработки пользовательских интерфейсов для iOS: устаревающий UIKit и новый SwiftUI. Мы разберём их особенности и отличия, а также рассмотрим примеры кода на UIKit и SwiftUI, и какой код писать точно не нужно.
UIKit — это фреймворк, который применяется для создания пользовательского интерфейса в iOS-приложениях с 2007 года. Он использует императивный подход к разработке, на который опираются программисты, описывая на Swift и Objective-C, как создавать и настраивать элементы интерфейса. Вы определяете все свойства каждого элемента: положение на экране, размеры, цвета, шрифты и т. д. UIKit предоставляет великолепный инструментарий, однако требует высокого уровня квалификации для работы с ним.
SwiftUI Apple представила в 2019 году. Это фреймворк, который реализует декларативный подход к разработке пользовательского интерфейса. Он даёт возможность описывать элементы интерфейса, используя концепцию зависимостей данных между ними, а не процесс их создания и настройку. Это означает, что каждый элемент интерфейса зависит от данных, которые ему передаются. Если эти данные изменяются, то соответствующие элементы интерфейса автоматически обновляются, что является однонаправленным подходом в программировании. Это означает, что изменения в модели данных автоматически обновляют представление, но обратного не происходит.
Чтобы лучше понимать особенности SwiftUI, полезно знать о связке с реактивным программированием и библиотекой Combine.
Реактивное программирование — это концепция, в которой данные представляются в виде потоков (streams), автоматически обновляющихся при изменении состояния представления. А библиотека Combine позволяет использовать реактивный подход в SwiftUI, упрощая работу с потоками данных.
Действие -> Состояние -> Отображение
![](https://habrastorage.org/getpro/habr/upload_files/b6b/7d6/13c/b6b7d613cf7906698d3b8c2f3362236c.png)
SwiftUI — это новый инструмент для разработки пользовательского интерфейса iOS-приложений, который быстро набирает популярность у разработчиков. Он прост в использовании и гибок, что позволяет писать код значительно быстрее, чем со старым UIKit.
Однако многие разработчики всё ещё не уверены в своих силах и предпочитают использовать привычные методы. Но SwiftUI даёт возможность значительно упростить процесс разработки и уменьшить количество ошибок, а это важные факторы для любого проекта и весомые аргументы в его пользу.
SwiftUI не является зависимым звеном в цепочке и не заменяет UIKit. Это новый инструмент, который помогает разработчикам создавать пользовательские интерфейсы быстро и легко.
Давайте рассмотрим процесс создания кнопки в обоих фреймворках.
Императивный подход на UIKit
final class MyViewController: UIViewController {
let myButton = UIButton(type: .system)
override func viewDidLoad() {
super.viewDidLoad()
myButton.frame = CGRect(x: 100, y: 100, width: 100, height: 50)
myButton.setTitle("Press me", for: .normal)
myButton.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
view.addSubview(myButton)
}
@objc func buttonTapped() {
print("Button tapped!")
}
}
Декларативный подход на SwiftUI
struct MyView: View {
var body: some View {
Button("Press me") {
print("Button tapped!")
}
.frame(width: 100, height: 50)
.position(x: 100, y: 100)
}
}
Оба этих примера демонстрируют создание кнопки, нажатие на которую выводит сообщение “Button tapped” на консоль. Однако декларативный подход на SwiftUI гораздо более лаконичный и понятный. Вместо того чтобы настраивать каждый аспект кнопки отдельно, мы описываем кнопку в терминах её желаемых свойств — и SwiftUI самостоятельно обеспечивает реализацию. Это может существенно ускорить процесс разработки и уменьшить количество ошибок.
Кроме того, SwiftUI в связке с Combine позволяет нам использовать многие удобные функции, такие как, например, автоматические обновления интерфейса при изменении состояния, что делает создание пользовательских интерфейсов ещё более простым и эффективным.
Теперь рассмотрим процесс создания таблиц в обоих фреймворках.
TableView на UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let data: [String] = ["Первый элемент", "Второй элемент", "Третий элемент"]
var tableView: UITableView = UITableView()
override func viewDidLoad( ) {
super .viewDidLoad()
tableView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height)
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.delegate = self
tableView.dataSource = self
self.view.addSubview(tableView)
}
func numberOfSections(in tableView: UITableView) -> Int { return 1 }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return data.count }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = data[ indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Выбран элемент \(data[ indexPath.row])" )
}
}
По коду понятно, что реализовать таблицу на UIKit довольно сложно.
Таблица на SwiftUI
struct ContentView: View {
let data: Set<String> = ["Первый элемент", "Второй элемент", "Третий элемент"]
var body: some View {
List(data, id: \.self) { item in
Text(item)
}
}
}
А вот реализация таблицы на SwiftUI не требует стольких усилий — код выглядит куда проще.
Хороший пример с плохим кодом
import SwiftUI
struct ContentView: View {
var label = Text("Hello, World!")
label = label.font(.system(size: 16))
label = label.background(.red)
var button = Button {
print("Button tapped")
} {
Text("Tap Me")
.font(.headline)
.foregroundColor(.white)
.padding()
.background(Color.blue)
.cornerRadius(5)
}
button.frame(width: 120, height: 44)
}
Данный код на SwiftUI нарушает структуру View, поскольку в нём нет её объявления и функции body, которая должна содержать описание интерфейса. Переменные label и button объявлены вне функции body, что может вызвать ошибки при компиляции. Кроме того, использование модификаторов неоптимально: переменная label переопределяется три раза, что затрудняет чтение кода и может привести к утере значений его свойств. Помимо этого, кнопка не связана с функцией body и не обрабатывает события. Подобный код будет сложно поддерживать, он требует много времени на написание и ревью. Он не будет соответствовать стандартам качества кода и не будет принят инженерами.
Если помните, в начале я писал, что мы нанимаем крутых разработчиков, но они приходят без опыта работы со SwiftUI. Это не является блокером, и мы знаем, что через какое-то время они вольются в процесс и будут писать код по стайлгайдам. Но на первых порах мы можем наблюдать попытки сваять похожие конструкции.
Итак, SwiftUI — это новый инструмент для разработки пользовательского интерфейса с низким порогом входа и декларативным подходом к программированию, который упрощает создание пользовательского интерфейса. Однако при наличии многолетнего опыта работы с UIKit многие разработчики предпочитают продолжать работать с ним. Но ведь мы должны быть готовы меняться и адаптироваться к новым технологиям, чтобы оставаться на плаву в быстро меняющемся мире iOS-разработки.
Меняйся или сдохни
Мы уже познакомились с обоими фреймворками, рассмотрели примеры кода, выяснили, как писали код раньше, во времена динозавров, а как это делают с приходом новых технологий, и, конечно же, увидели, как писать код на SwiftUI не стоит, на приближенном к реальности примерe.
Кажется, мы уже немного гуру и правильно понимаем различия между подходами и фреймворками, понимаем, как нам следует писать трушный декларативный пользовательский интерфейс, а как этого лучше не делать. Ну а чтобы завершить эту тему и получить ещё больше экспертизы, дальше мы рассмотрим различные характеристики каждого фреймворка.
Преимущества и недостатки фреймворков
Некоторые инженеры, которые приходят к нам в Ozon Tech, считают, что SwiftUI — полная фигня и им не стоит пользоваться, тем более в коммерческой разработке. Другие же уверены, что UIKit — это безнадёжно устаревшая технология динозавров. И все они правы! Но не совсем.
Мы переходим к основной части моей работы. Я поговорил с большим количеством экспертов, провёл масштабное исследование и выписал неопровержимые плюсы и минусы SwiftUI и UIKit. Давайте на них посмотрим:
![](https://habrastorage.org/getpro/habr/upload_files/057/650/c1b/057650c1badafde74b540eff33a2556d.png)
Посмеялись — и хватит ?
Как вы могли догадаться, табличка шуточная. А теперь предлагаю взглянуть на реальную:
| SwiftUI ? | UIKit ?? |
Скорость вёрстки | + | - |
Совместимость с легаси-кодом | - | + |
Дебаггинг | - | + |
Мультиплатформенность | + | - |
Поддержка сообщества | - | + |
Количество кода | + | - |
Совместимость с Modern Concurrency | + | - |
Поддерживаемость | - | + |
Быстродействие | + | - |
Скорость вёрстки: SwiftUI +, UIKit -
При разработке пользовательского интерфейса с использованием SwiftUI, процесс верстки значительно упрощается. Широкий набор встроенных компонентов позволяет создавать интерфейс, используя декларативный подход, что значительно сокращает объем кода и уменьшает вероятность ошибок.
В SwiftUI не нужно задавать точные координаты каждому элементу интерфейса, описывая их в коде. Вместо этого, разработчик может использовать макеты и стеки для автоматического позиционирования элементов. Это дает возможность быстро создавать пользовательский интерфейс без необходимости тратить время на выравнивание каждого элемента вручную.
Также, SwiftUI предлагает удобный подход к созданию списков и таблиц, используя компоненты List и Form. Они автоматически настраиваются в зависимости от данных, что значительно упрощает процесс создания списков и таблиц.
В отличие от SwiftUI, UIKit использует императивный подход, где разработчику необходимо явно определять каждый элемент пользовательского интерфейса. Это может привести к дополнительным затратам времени, так как разработчику приходится задавать точные координаты каждого элемента вручную.
Кроме того, в UIKit требуется больше кода, чтобы создавать интерфейс, что может замедлить процесс разработки и усложнить поддержку кода в будущем.
Таким образом, использование SwiftUI может значительно повысить скорость верстки пользовательского интерфейса, благодаря использованию декларативного подхода и упрощенной структуре кода. Однако, следует учитывать, что для создания сложных пользовательских интерфейсов могут потребоваться дополнительные инструменты и подходы.
Совместимость с легаси-кодом: SwiftUI -, UIKit +
UIKit появился раньше, чем SwiftUI, он работает с Objective-C и Swift, что обеспечивает совместимость с готовым к использованию легаси-кодом.
Особенностью SwiftUI являются жирные структуры. Это структуры, которые содержат много переменных и методов. Каждый элемент пользовательского интерфейса — кнопка, текстовое поле или изображение — в SwiftUI представлен в виде отдельной структуры.
Однако, в отличие от Swift, Objective-C не поддерживает жирные структуры. Следовательно, нет способа написать пользовательский интерфейс с помощью SwiftUI и импортировать его в приложение, написанное на Objective-C. Это означает, что разработчики, которые хотят использовать SwiftUI, должны перейти на Swift и создать новое приложение с нуля, если хотят использовать весь потенциал фреймворка.
Дебаггинг: SwiftUI -, UIKit +
При разработке мобильных приложений, одной из важных задач является дебаггинг. Дебаггинг - это процесс поиска и устранения ошибок в исходном коде приложения.
Одним из выборов разработчиков является выбор между двумя фреймворками: SwiftUI и UIKit. Каждый из них имеет свои преимущества и недостатки при дебаггинге.
UIKit имеет долгую историю и широкое сообщество разработчиков, что делает дебаггинг на этой платформе более простым. UIKit имеет хорошо разработанные инструменты дебаггинга, такие как инструменты Xcode, которые позволяют быстро находить и исправлять ошибки.
С другой стороны, SwiftUI является новым фреймворком.. У него есть свои преимущества, такие как более простой синтаксис и быстрота верстки. Однако, это также делает дебаггинг на SwiftUI менее эффективным, так как инструменты для дебаггинга еще не так хорошо развиты как для UIKit.
SwiftUI имеет свой уникальный подход к жизненному циклу представлений, что может вызывать проблемы в процессе дебаггинга. Например, в SwiftUI может возникнуть проблема с обновлением вида, когда изменение в одном представлении может повлиять на другие представления и вызвать ошибки. Кроме того, SwiftUI имеет ограничения в работе с различными типами данных, которые могут приводить к ошибкам в процессе дебаггинга.
Мультиплатформенность: SwiftUI +, UIKit -
SwiftUI был создан как фреймворк для мультиплатформенной разработки приложений, что позволяет использовать единый код для создания приложений для разных платформ: iOS, macOS, watchOS и tvOS. UIKit ориентирован на создание приложений только для iOS.
Поддержка сообщества: SwiftUI -, UIKit +
UIKit является «взрослым» общепризнанным фреймворком, который объединяет большое сообщество разработчиков и множество инструментов и библиотек для его использования. SwiftUI только появился и его сообщество пока ещё не такое развитое и большое, как у UIKit.
Количество кода: SwiftUI +, UIKit -
SwiftUI благодаря своему декларативному подходу позволяет уменьшить количество кода, необходимого для создания пользовательского интерфейса, и сделать его более читаемым и понятным. UIKit работает в императивном стиле и требует больше кода для достижения тех же результатов.
Совместимость с Modern Concurrency: SwiftUI +, UIKit -
SwiftUI представляет собой фреймворк, который полностью поддерживает Modern Concurrency с помощью новых Swift API, таких как async/await и Combine. UIKit же как старший фреймворк может не всегда соответствовать современным требованиям к асинхронному программированию и требовать дополнительных усилий для написания асинхронного кода.
Поддержка версий: SwiftUI -, UIKit +
SwiftUI был представлен в 2019 году вместе с iOS 13, поэтому поддерживает только устройства на ней и более новых версиях. Это означает, что разработка приложения с использованием SwiftUI может стать проблематичной, если требуется поддержка более старых версий iOS.
UIKit в дополнение к поддержке iOS 13 и более новых версий поддерживает и более ранние версии iOS. Это означает, что с ним разработчики могут создавать приложения, которые будут работать на более старых устройствах и версиях платформы iOS.
Быстродействие: SwiftUI +, UIKit -
SwiftUI предоставляет более быстрое и эффективное развитие своего инструментария по сравнению с UIKit. Он ориентирован на использование структур, тогда как UIKit использует классы для создания пользовательского интерфейса. Использование структур делает код на SwiftUI более лёгким, читабельным и удобным в работе. Структуры в Swift хранятся в стеке, а не в куче, как классы, — это обеспечивает более эффективное использование памяти (более быстрое выделение и освобождение).
Также структуры в Swift не требуют сборки мусора, в отличие от классов, что способствует более быстрой работе кода в структурах. Использование классов в UIKit может утяжелить код, сделать его менее понятным и более сложным в обслуживании. Однако использование классов может быть предпочтительным в случаях, когда необходимо использовать наследование и другие концепции, которые не поддерживаются структурами.
Использование структур в SwiftUI может значительно ускорить разработку и обеспечить более эффективное использование памяти, а это может повысить быстродействие приложений.
Одной из ключевых особенностей, способствующей быстродействию SwiftUI, является его декларативный подход к созданию пользовательского интерфейса. Вместо того чтобы явно обновлять каждый элемент интерфейса вручную, SwiftUI использует концепцию "объявления состояния" (state declaration), где вы определяете желаемое состояние интерфейса, а фреймворк автоматически обрабатывает обновления и перерисовку только тех частей, которые изменились.
Такой подход позволяет SwiftUI выполнять оптимизацию и сокращать ненужные операции перерисовки, что в результате повышает быстродействие. Фреймворк также использует различные стратегии кэширования и инкрементальной перерисовки, чтобы минимизировать количество работы, которое необходимо выполнить при изменении интерфейса.
Благодаря использованию новых технологий и оптимизаций, SwiftUI также может использовать преимущества аппаратного ускорения, такого как Metal и Core Animation, для повышения производительности графики и анимаций.
В целом, SwiftUI стремится обеспечить более высокую производительность и быстродействие по сравнению с UIKit, упрощая разработку, оптимизируя перерисовку и используя современные технологии для ускорения работы с графикой и анимациями.
Все эти оптимизации и подходы к разработке в SwiftUI в целом способствуют более быстрому развитию проектов, более эффективному использованию ресурсов и повышению производительности пользовательского интерфейса.
Однако следует отметить, что факторы, такие как сложность проекта и качество кода, также могут влиять на общую производительность приложения.
Кто же победил?
![](https://habrastorage.org/getpro/habr/upload_files/8af/6a7/610/8af6a7610f12a195ec16b311920d99a7.png)
Оба рассмотренных фреймворка имеют свои преимущества и недостатки, и разработчики должны выбирать инструмент в соответствии с конкретным приложением и его требованиями.
В заключительном разделе мы рассмотрели плюсы и минусы фреймворков, и это даёт возможность провести параллели между совершенно разными инструментами и углубить свою экспертизу.
При работе с версткой пользовательских интерфейсов, мы можем использовать фреймворки UIKit или SwiftUI. Однако, важно понимать, что каждый из них имеет свои особенности и специфику работы.
Получилось осветить все аспекты, которые помогают нам в решении первоначальной проблемы, с отсутствием экспертизы в разработке на новом фреймворке и в понимании парадигм, по которым работают основные инструменты для работы с пользовательским интерфейсом..
Сейчас мы имеем представление о ключевых парадигмах в iOS-разработке, так как погрузились в академические определения и рассмотрели примеры кода, разобрались в особенностях SwiftUI и UIKit, увидели отличия в количестве кода и код, который лучше не писать, и, конечно же, узнали о плюсах и минусах двух фреймворков, что внесло ясность в общую картину и понимание положения этих инструментов в мире iOS-инженера.
В этой статье мы рассмотрели:
особенности императивного и декларативного подходов программирования;
особенности фреймворков UIKit ?? и SwiftUI ?;
плюсы и минусы фреймворков UIKit ?? и SwiftUI ?.
Несмотря на то, что дедуля UIKit не использует декларативный подход, он является очень мощным и гибким инструментом для создания пользовательского интерфейса iOS-приложений, и не стоит списывать его со счетов. Но сейчас у него есть серьёзный конкурент в виде относительно нового фреймворка, который впечатляет своими фичами, взять хотя бы сниженный порог входа в разработку. Может повториться история Objective-С и Swift, ведь реальность такова, что Apple движется в сторону технологических улучшений своих инструментов для разработки, и увеличение популярности SwiftUI и переход на его сторону многочисленного комьюнити — вопрос времени.
Полезные материалы
1. Фреймворк Carbon, который даёт возможность потрогать декларативный подход в UIKit. Это библиотека для создания пользовательских интерфейсов на основе компонентов UITableView и UICollectionView, вдохновлённая SwiftUI и React.
2. Реактивное программирование с Combine: тут и тут
3. Доклад Руслана Кавецкого из Agora «Избавляемся от старья и переходим на SwiftUI».
4. Статья «SwiftUI по полочкам».
*Хотел бы поблагодарить за помощь в написании статьи моих коллег и друзей: