Адаптируем существующее бизнес-решение под SwiftUI. Часть 1

    Доброго всем времени суток. Меня зовут Анна Жаркова, я ведущий мобильный разработчик компании «Usetech»

    О технологии SwiftUI, которую Apple анонсировали в прошлом году WWDC 2019, говорили уже много. В принципе уже довольно материала про ее особенности, что у нее под капотом и как это использовать для быстрого и красивого UI.



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

    Но начнем мы, пожалуй, с азов. В этой части будет только введение и базовая информация. Так что если вы уже знакомы с этими особенностями SwiftUI, переходите к следующим частям:
    часть 2 (как работать с готовыми библиотеками под UIKit)
    часть 3 (здесь про архитектуру)
    часть 4 (здесь про навигацию)

    Если еще не успели познакомиться, то задержитесь на пару минут)

    Основными особенностями декларативной разработки в новом фреймворке является отход от прямого использования 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х текстовых полей и одной картинки:


    Хотя 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)
    


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

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

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

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



    Основными используемыми 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:



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

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

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

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

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

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

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

    Прошу переходить по ссылке.
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 0

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое