Адаптация существующего бизнес-решения под SwiftUI | OTUS
Запланируйте обучение с выгодой в Otus!
-15% на все курсы до 30.11 Забрать скидку! →
Выбрать курс

Адаптация существующего бизнес-решения под SwiftUI

О технологии SwiftUI говорили уже много. Предлагаю вам небольшой цикл статей про то, как использовать этот фреймворк в реальной жизни и реальных приложениях, а не в конструкторах сэндвичей для авокадо. А чтобы все было серьезно и по-взрослому, мы рассмотрим, как сделать так, чтобы наше приложение на SwiftUI соответствовало принципам чистой архитектуры и чистого кода.

kv96it7_od_mj4xk783lgxeeh3o_1-1801-4fc882.png

Основными особенностями декларативной разработки в новом фреймворке является отход от прямого использования UIViewController, UIView и заменой на структуры, реализующие протокол View. Все компоненты визуальной части описываются также с помощью декларативного синтаксиса и располагаются внутри основного свойства body каждого View. Настройки, стилизации и кастомизации компонентов, навигация между экранными View тоже задается с помощью декларативного синтаксиса.

Например, этот код описывает вью для списка новостей, по клику на который открывается экран с вью отдельной новости:

struct NewsListView: View{
    @State var data: [NewsItemMock]

    var body: some View {
        NavigationView{
        List(data) { item in
            NavigationLink(destination:NewsItemView(item:item)) {
            NewsItemRow(data: item)
            }
        }
    }
}

SwiftUI использует ViewBuilder – декларативный конструктор интерфейса на основе Functional Builder. Этот механизм появился в Swift 5.1 и позволяет группировать элементы в некий массив внутри closure-блока, например, родительского объекта. Пример использования ViewBuilder представлен на слайде. Мы просто располагаем View-контролы в нужном нам порядке, например, внутри вертикального или горизонтального Stack без использования addSubview, а при компиляции SwiftUI сам добавляет и группирует элементы в более сложный родительский контейнер.

И вот такой код:

          VStack {
            HStack {
                VStack(alignment: .leading,spacing: 10) {
                    HeaderText(text: data.title ?? "")
                    SubheaderText(text: data.description ?? "")
                    SmallText(text: data.publishedAt?
                                   .formatToString("dd.MM.yyyy") ?? "")
                }
               ThumbImage(withURL: data.urlToImage ?? "")
             }

преобразуется в элемент списка из 3-х текстовых полей и одной картинки:

jvpde33lozawsyeodecs0plwxj0_1-1801-104e0d.png

Хотя SwiftUI и отрицает концепцию UIViewController, точкой входа в приложение является UIHostingController, внутрь которого передается и встраивается отображаемый View. Т. е. по сути новая технология является надстройкой над UIKit:

@available(iOS 13.0, tvOS 13.0, *)
open class UIHostingController<Content> : UIViewController where Content : View {

    public var rootView: Content

    public init(rootView: Content)

1wpkubsfiibrjhtrf5ec_3bfasm_1-1801-254b9c.png

Кстати, все контролы SwiftUI являются декларативными аналогами UIKit-контролов.

Например, VStack, HStack — аналоги привычных нам вертикальных и горизонтальных UIStackView соответственно. List — это UITableView, Text — UILabel, Button — UIButton, Image — UIImage и т. д.

Подключение и настройка контролов производится декларативно с помощью доступных модификаторов. Группируются элементы внутри аналогов UIStackView с некоторыми предопределенными свойствами.

Помимо изменения способа описания визуальной части, меняется управление потоком данным и механизм реакции UI на него. Swift UI является не событийно зависимым фреймворком. Т.е. View в нем — это результат функции неких состояний, а не последовательности событий. Действие, производимое пользователем, не меняет UI напрямую, нельзя напрямую изменить тот или иной View, добавить или удалить контрол. Сначала меняются свойства или переменные состояния, которые подключены к View через те или иные Property wrappers (обертки свойств).

pbqp1cc7aqbiqsohof2k0et9zku_1-1801-5080f9.png

Основными используемыми Property Wrappers являются:

1.@State — используется для локальных переменных.

struct NewsItemRow: View {
    @State var title: String
    @State var  description: String
    @State var dateFormatted: String 
    @State var imageUrl: String 

    var body: some View {
        VStack {
            HStack {
                VStack(alignment: .leading,spacing: 10) {
                    HeaderText(text: title)
                    SubheaderText(text: description)
                    SmallText(text: dateFormatted)
                }
              ThumbImage(withURL: imageUrl)
            }
         }
     }

2.@Binding – аналог weak, используется при передаче ссылки на значение.

Используем, когда от какого-то свойства у нас зависит более одного View. Например, если мы хотим передавать значение исходному View от View второго уровня.

struct FirstView: View {
    @State var isPresented: Bool = true

    var body: some View {
        NavigationView {
            NavigationLink(destination:
            SecondView(isPresented: self.$isPresented)) {
                Text("Some")
            }
        }
    }
}

struct SecondView: View {
    @Binding var isPresented: Bool

    var body: some View {
        Button("Dismiss") {
            self.$isPresented = false
        }
    }
}

3.@EnvironmentObject – передача объектов между View

4.@ObjectBinding, @ObservableObject – используется для отслеживания изменения свойств модели с помощью средств фреймворка Combine.

class NewsItemModel: ObservableObject,IModel {
   @Published var title: String
   @Published var  description: String
   @Published var dateFormatted: String 
   @Published var imageUrl: String 
}

О нем мы поговорим позже.

Итак. Если мы хотим изменить наш View, мы меняем свойство, объявление с одним из Property Wrappers. Затем уже декларативный View перестраивается со всеми внутренними контролами.

При изменении любой из переменных состояния View перестроится весь целиком.

Рассмотрим небольшой пример. У нас есть какой-то экран, в навигационном баре которого расположена кнопка добавления контента в избранное. Чтобы у нас поменялось изображение-индикатор на этой кнопке, мы воспользуемся PropertyWrappers. Например, в данном случае создадим локальную переменную и объявим ее как State:

struct NewsItemView: View {
@State var isFavorite: Bool 
....

Изменение значения свойства привяжем к триггерному-событию, возникающему при нажатии на кнопку:

struct NewsItemView: View{
    @State var isFavorite: Bool 

    var body: some View {
    NavigationView {
    VStack {
        Text("Some content")
       }
    }
     .navigationBarItems(trailing: Button(action: {
            self.isFavorite = !self.isFavorite
        }){
            Image(self.isFavorite ? "favorite" : "unfavorite")
           .frame(width: 20, height: 20, alignment: .topTrailing)
        })
   }

Таким образом будет меняться наш View:

35dqtkrxdpgfubq8qvvvy4zs6fk_1-1801-1f268a.png

И в принципе это все основное, что вам нужно знать для начала про SwiftUI.

Но достаточно ли это для работы?

Чтобы создавать несложные UI из простых контролов без привязки к Xib и сторибордам, вполне.

А для чего-то большего -- нет.

Во-первых, не для всех контролов есть аналоги в SwiftUI. Это относится как к стандартным для UIKit UISearchView, UICollectionView, так и для каких элементов из third-part-библиотек.

Во-вторых, нет (или почти нет, может, кто-то это делает прямо сейчас) сторонних решений для работы с Data Flow SwiftUI.

Значит, придется, адаптировать уже существующие решения под стандартные приложения iOS.

Не пропустите новые полезные статьи!

Спасибо за подписку!

Мы отправили вам письмо для подтверждения вашего email.
С уважением, OTUS!

Автор
0 комментариев
Для комментирования необходимо авторизоваться
Популярное
Сегодня тут пусто
Черная пятница в Otus! ⚡️
Скидка 15% на все курсы до 27.11 →