Как понять эту неотъемлемую часть разработки 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
.
Результат показан ниже:
Потрясающие!