Как понять эту неотъемлемую часть разработки SwiftUI

ОБНОВЛЕНИЕ: не следует вызывать сетевой запрос в методе инициализации ViewModel, как упоминал Майкл Лонг. Я записал видео, чтобы показать проблему и способы ее устранения. Посмотреть видео можно здесь: https://youtu.be/HJS_yzSihoA

SwiftUI - это новая декларативная структура Apple для создания пользовательских интерфейсов для всех устройств Apple. Каркас SwiftUI можно разделить на два основных компонента: представления и состояние.

Этот пост посвящен поддержке и пониманию состояния в SwiftUI. Управление состоянием является неотъемлемой частью разработки SwiftUI, и существует множество различных способов работы с состоянием.

Понимание состояния

Состояние представляет данные, связанные с представлением. Он может быть представлен примитивным типом, таким как boolean, string, int и т. Д .; или сложным объектом, таким как модель представления. В SwiftUI изменение значения состояния вызывает повторную визуализацию представления, позволяя ему синхронизироваться со связанными данными.

Подобные концепции доступны в React и Flutter, где изменение состояния вызывает срабатывание функций рендеринга и сборки соответственно.

Давайте посмотрим на простой пример состояния. В этом примере мы позволим пользователю переключать переключатель. В зависимости от состояния переключателя значок будет меняться с ночи на день.

Базовая иллюстрация состояния

Важной частью приведенного выше примера является использование оболочки свойства@State для логического свойства isOn. Мы также отметили его частным ключевым словом, указывающим, что это состояние является локальным / частным состоянием для ContentView компонента.

Представление Toggle принимает Binding<Bool>, что удовлетворяется передачей свойства состояния isOn. Каждый раз, когда Toggle меняет состояние между isOn или isOff, значение состояния обновляется. Каждый раз при обновлении состояния он вызывает свойство body для повторной визуализации представления.

Результат показан ниже:

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

Добавление дополнительных переменных

Ниже у нас есть реализация представления под названием Register. Он включает в себя все основные элементы регистрации нового пользователя. Чтобы упростить этот пример, мы используем базовые элементы SwiftUI, такие как TextField и т. Д. В вашем реальном приложении вы можете использовать возможности Form представлений, доступных в структуре SwiftUI.

Состояние Register представления контролируется четырьмя независимыми переменными. Каждая переменная фиксирует срез состояния. По мере того, как пользователь вводит текстовые поля, переменные состояния обновляются из-за возможности привязки.

В большинстве случаев это отлично работает! Но нам приходится иметь дело с четырьмя независимыми переменными, которые представляют единую модель. Это можно упростить, введя модель представления, которая представляет состояние представления.

SwiftUI и MVVM

Хотя вы можете использовать любой шаблон проектирования для создания приложения SwiftUI, рекомендуемым шаблоном является Модель представления, также известная как MVVM. В шаблоне проектирования MVVM вы начнете с создания модели представления, которая будет отвечать за предоставление данных в представление и управление состоянием, связанным с представлением.

Реализация RegistrationViewModel показана ниже:

Теперь мы можем обновить Register представление и использовать только что созданный RegistrationViewModel:

Как видите, наше Register представление стало намного проще, и RegistrationViewModel отвечает за поддержание состояния представления. Мы удалили отдельные фрагменты переменных состояния и заменили их на RegistrationViewModel. Теперь каждый раз, когда пользователь обновляет TextFields, модель представления обновляется автоматически. Шаблон проектирования MVVM позволяет нам четко структурировать приложения SwiftUI и в то же время упрощает модульное тестирование.

Бывают случаи, когда у вас есть вложенные / дочерние представления, и вы хотите, чтобы ваши дочерние представления обновляли состояние родительского представления. Это достигается с помощью привязки.

@ Привязка

Одно представление в SwiftUI может состоять из нескольких дочерних представлений. Иногда вы хотите разрешить дочернему элементу изменять состояние родительского представления. Привязка позволяет передавать состояние из родительского представления в дочернее представление. Как только дочернее представление изменяет состояние, родительский элемент автоматически получает обновленную копию и повторно отображает представление. Давайте реализуем тот же пример дня / ночи, что и раньше, но на этот раз мы поместим представление Toggle в дочернее представление с именем DayNightView.

Реализация DayNightView показана ниже:

Оболочка свойств @Binding указывает, что свойство isOn будет передано DayNightView. Как только DayNightView изменяет свойство isOn, исходный отправитель будет уведомлен путем повторной визуализации представления.

Назначение свойства isOn вызовет render для родительского представления.

Реализация родительского представления показана ниже:

DayNightView будет отвечать за отображение переключателя Toggle. DayNightView принимает binding в качестве аргумента. В приведенном выше коде мы передали @State свойство isOn в DayNightView. Это означает, что когда DayNightView updates связываемое свойство, свойство состояния isOn в родительском представлении также обновляется.

Основная цель @Binding - передать состояние дочернему представлению, где его можно изменить. Это дает дочерним представлениям возможность общаться со своими родителями и обновлять их.

@Observable и @Observed

Большинство приложений получают свои данные из внешнего источника, в основном с помощью веб-API JSON. После загрузки данные помещаются в объект DTO, а затем отображаются на модели представления и, наконец, отображаются на экране.

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

Прежде чем переходить к Observable и Observed, мы должны найти способ выполнить async запрос на выборку данных. Для простоты мы сделаем фальшивый запрос и получим список сообщений в async манере.

В приведенном выше коде вы можете видеть, что Webservice просто ждет две секунды, а затем отправляет жестко закодированный список сообщений обратно пользователю в обработчике завершения.

Класс Webservice используется PostListViewModel для выполнения запроса. Реализация PostListViewModel показана ниже:

PostListViewModel представляет данные, которые будут отображаться на экране списка сообщений. Самое важное, на что следует обратить внимание, - это использование протокола theObservableObject, который позволяет классу публиковать события. Свойство posts украшено оболочкой свойства @Published, что означает, что оно действует как издатель. Каждый раз, когда свойство posts присваивается значение, оно публикует событие, указывающее, что оно было изменено.

Наконец, PostListView использует PostListViewModel для извлечения и отображения сообщений в представлении, как показано ниже:

Если вы запустите приложение, PostListView будет использовать PostListViewModel и заполнит список сообщений в виде List. Шаблон проектирования MVVM вместе с возможностью публиковать и уведомлять об изменениях значительно упрощает синхронизацию представления с моделью представления.

В настоящее время мы обсудили только локальное состояние, которое представляет состояние, поддерживаемое и доступное для определенного представления. Если вам нужно изменить состояние представления из другого представления, вы можете передать его в качестве аргумента и использовать оболочку свойства @Binding. Это хорошо работает, если вы передаете состояние только между несколькими представлениями, но быстро становится проблемой, когда задействовано несколько представлений.

В следующем разделе мы рассмотрим глобальное состояние, к которому можно получить доступ из любого представления SwiftUI.

@EnvironmentObject

Концепция EnvironmentObject очень похожа на Redux. Основная цель EnvironmentObject - поддерживать глобальное состояние. Глобальное состояние - это состояние, к которому можно получить доступ из любого представления. EnvironmentObject обычно вводится в представление верхнего уровня, делая глобальное состояние доступным для всех дочерних представлений.

Чтобы упростить пример, мы создадим класс с именем UserSettings, который будет использоваться несколькими представлениями. Мы реализуем три разных представления, а именно Facebook, Twitter и TotalLikes. Просмотры Facebook и Twitter позволят пользователю увеличивать количество лайков, а TotalLikes будет отвечать за отображение общего количества лайков.

Реализация класса UserSettings показана ниже:

Класс UserSettings использует протокол ObservableObject. Единственное свойство в классе UserSettings - это likes, которое украшено оболочкой свойств @Published, указывающей, что оно будет действовать как издатель и будет уведомлять подписчиков при изменении значения.

Перед использованием UserSettings нам нужно внедрить его в родительский вид. Откройте SceneDelegate.swift и реализуйте следующий код:

Теперь объект UserSettings будет доступен для ContentView и всех видов внутри ContentView. Затем мы реализуем представление Facebook и Twitter, как показано ниже:

Интересно использование оболочки свойства the@EnvironmentObject. Экземпляр UserSettings будет автоматически заполнен из родительского представления. Когда вы увеличиваете свойство likes, оно будет увеличиваться глобально для всех представлений, заинтересованных в глобальном состоянии UserSettings.

Обновления глобального состояния также вызовут автоматический рендеринг представлений.

Представление TotalLikes отвечает за отображение значения likes.

Наконец, внутри ContentView используются представления Twitter и Facebook.

Результат показан ниже:

Потрясающие!