Программирование
Поток данных SwiftUI с примерами
SwiftUI предоставляет несколько оберток свойств, которые помогут вам управлять потоком данных в вашем приложении.
SwiftUI предлагает декларативный подход к проектированию пользовательского интерфейса и автоматически обновляет затронутые части интерфейса при изменении данных. Такой подход часто называют однонаправленным потоком данных, и он коренным образом изменил способ разработки приложений в экосистеме Apple.
Чтобы обновлять представления при изменении данных, вы делаете классы модели данных наблюдаемыми объектами (observable), публикуете их свойства и объявляете их экземпляры с помощью специальных атрибутов. Чтобы изменения в данных, получаемые от пользователя, возвращались в модель, вы привязываете элементы управления пользовательского интерфейса к свойствам модели. Управление состоянием является основой для создания эффективных приложений, которые поддерживают актуальность информации для пользователя.
SwiftUI предоставляет несколько оберток свойств, которые помогут вам управлять потоком данных в вашем приложении. В этой статье мы расскажем о различиях между ними и о том, как правильно их использовать.
@Binding
Обертка свойства @Binding позволяет создать двустороннюю связь между свойством, хранящим данные, и представлением, которое отображает и изменяет эти данные. Привязка (Binding) соединяет свойство с источником истины, хранящимся в другом месте, вместо того, чтобы хранить данные напрямую. Например, вы можете использовать привязку для создания двусторонней связи между Toggle и свойством Boolean объекта State.
struct ToggleView: View { @Binding var isOn: Bool var body: some View { Toggle("Switch", isOn: $isOn) } } struct ContentView: View { @State private var isOn = false var body: some View { ToggleView(isOn: $isOn) } }
В этом примере ContentView имеет свойство @State, которое хранит булево значение для переключателя. Он передает привязку этого свойства в ToggleView, который использует его для отображения и обновления переключения. Префикс $ перед именем свойства дает доступ к его прогнозируемому значению, которое для свойства @State является привязкой к значению.
Вы можете использовать @Binding для создания дочерних представлений, зависящих от внешних источников данных, без необходимости передавать весь объект или хранить избыточные копии данных. Вы также можете использовать его для создания многократно используемых представлений, которые могут работать с различными типами источников данных.
@StateObject
Обертка свойства @StateObject позволяет вам создавать и сохранять наблюдаемый объект, принадлежащий представлению. Вы используете эту обертку свойства, когда хотите, чтобы ваше представление было владельцем объекта и отвечало за его создание и уничтожение.
Наблюдаемый объект — это пользовательский класс, который соответствует протоколу ObservableObject и может использоваться для хранения данных и логики приложения. Он также может использовать обертку свойства @Published, чтобы пометить некоторые из своих свойств как опубликованные, что означает, что они будут вызывать обновления представления при каждом изменении.
class Counter: ObservableObject { @Published var value = 0 func increment() { value += 1 } } struct CounterView: View { @StateObject var counter = Counter() var body: some View { VStack { Text("Count: \(counter.value)") Button("Increment") { counter.increment() } } } }
В этом примере CounterView создает и владеет экземпляром Counter, который является наблюдаемым объектом, хранящим и обновляющим значение. Представление использует @StateObject для сохранения объекта во время обновления представления и предотвращения многократной инициализации. Представление также наблюдает за изменениями опубликованного значения и отображает его в текстовом представлении.
Вы должны использовать @StateObject только в представлениях, которые отвечают за создание наблюдаемого объекта. Если вам нужно передать существующий наблюдаемый объект в другое представление, вместо него следует использовать @ObservedObject или @EnvironmentObject.
@Environment
Обертка свойства @Environment позволяет прочитать значение, хранящееся в окружении представления. Окружение — это набор пар ключ-значение, которые SwiftUI использует для передачи информации вниз по иерархии представления. С помощью этой обертки свойств можно получить доступ к различным системным настройкам и предпочтениям.
Например, вы можете использовать @Environment для чтения текущей цветовой схемы вашего приложения:
struct ContentView: View { @Environment(\\.colorScheme) var colorScheme var body: some View { Text("Color scheme: \(colorScheme == .dark ? "Dark" : "Light")") } }
В этом примере ContentView использует @Environment для чтения значения colorScheme из среды и отображает его в текстовом представлении. Если пользователь изменит настройки внешнего вида системы, представление автоматически обновится, чтобы отразить новое значение.
Вы можете использовать @Environment для чтения любого из предопределенных ключей в структуре EnvironmentValues, таких как horizontalSizeClass, managedObjectContext, locale и других. Вы также можете определить собственные пользовательские значения среды, создав пользовательский ключ, соответствующий протоколу EnvironmentKey, и расширив структуру EnvironmentValues, чтобы добавить вычисляемое свойство для вашего ключа.
Вы не можете использовать @Environment для записи или изменения значения среды. Для этого нужно использовать модификатор environment(_ :) в представлении и передать ключ и новое значение. Это установит или переопределит значение окружения для текущего представления и всех его вложенных представлений.
@Published
Обертка свойства @Published позволяет пометить свойство наблюдаемого объекта как опубликованное, что означает, что он будет уведомлять своих наблюдателей при каждом изменении значения свойства. Вы используете эту обертку свойства внутри пользовательского класса, который соответствует протоколу ObservableObject.
class User: ObservableObject { @Published var name: String @Published var age: Int init(name: String, age: Int) { self.name = name self.age = age } }
В этом примере User — это наблюдаемый объект, который имеет два опубликованных свойства: имя и возраст. При изменении одного из этих свойств SwiftUI автоматически обновляет все представления, которые зависят от них.
Вы можете использовать @Published для создания простых моделей данных, которые могут быть совместно использованы в вашем приложении с помощью оберток свойств, таких как @StateObject, @ObservedObject или @EnvironmentObject. Вы также можете использовать его для создания кастомных паблишеров, которые можно использовать с фреймворком Combine.
@State
Обертка свойства @State позволяет вам создавать и сохранять локальное состояние, принадлежащее представлению. Вы используете эту обертку свойства, когда хотите, чтобы ваше представление хранило некоторые временные данные, которые относятся только к этому представлению и не поступают из внешнего источника.
Свойство State — это одно значение или коллекция значений, которые соответствуют протоколу Value, что означает, что они являются структурами, имеющими семантику Value. Например, вы можете использовать свойство state для хранения булевского флага, целочисленного счетчика или массива строк.
struct CounterView: View { @State private var count = 0 var body: some View { VStack { Text("Count: \(count)") Button("Increment") { count += 1 } } } }
В этом примере у CounterView есть свойство state, которое хранит целочисленное значение для счета. Представление отображает счетчик в текстовом представлении и обновляет его, когда пользователь нажимает кнопку. Свойство state помечено как private, потому что оно используется только этим представлением и не должно быть доступно другим представлениям.
Вы должны использовать @State только для простых данных, локальных для одного представления. Если вам необходимо обмениваться данными между несколькими представлениями или хранить сложные данные с семантикой ссылок, вместо этого следует использовать другие обертки свойств, например @StateObject, @ObservedObject или @EnvironmentObject.
@EnvironmentObject
Обертка @EnvironmentObject позволяет вам создать и наблюдать observable объект, который является общим для всей иерархии представлений. Вы используете эту обертку свойства, когда хотите, чтобы ваши представления имели доступ к общему источнику данных без необходимости передавать его в явном виде через инициализаторы или биндинги.
Объект среды — это экземпляр пользовательского класса, который соответствует протоколу ObservableObject и может использоваться для хранения данных и логики приложения. Он также может использовать обертку свойств @Published, чтобы пометить некоторые из своих свойств как опубликованные, что означает, что они будут вызывать обновления представления при каждом изменении.
class UserSettings: ObservableObject { @Published var username = "Guest" } struct SettingsView: View { @EnvironmentObject var settings: UserSettings var body: some View { TextField("Username", text: $settings.username) } } struct ProfileView: View { @EnvironmentObject var settings: UserSettings var body: some View { Text("Hello, \(settings.username)!") } } struct ContentView: View { @StateObject var settings = UserSettings() var body: some View { TabView { SettingsView() .tabItem { Image(systemName: "gear") Text("Settings") } ProfileView() .tabItem { Image(systemName: "person") Text("Profile") } } .environmentObject(settings) } }
В этом примере ContentView создает и владеет экземпляром UserSettings, который является наблюдаемым объектом, хранящим имя пользователя. Представление использует @StateObject для сохранения объекта во время обновления представления и предотвращения многократной инициализации. Представление также использует модификатор environmentObject(_:), чтобы внедрить объект в окружение своих вложенных представлений.
SettingsView и ProfileView используют @EnvironmentObject для доступа к одному и тому же экземпляру UserSettings из среды и отображения и обновления имени пользователя. Представлениям не нужно знать, откуда берется объект или как он создается, им просто нужно указать тип объекта, который они ожидают.
Вы должны использовать @EnvironmentObject, когда у вас есть модель данных, которая используется многими представлениями в вашем приложении, и вы хотите избежать ее явной передачи. Вы также можете использовать его для создания представлений, которые могут многократно использоваться и работать с различными источниками данных.
@ObservedObject
Обертка свойства @ObservedObject позволяет вам создать и наблюдать наблюдаемый объект, который передается представлению в качестве параметра. Вы используете эту обертку свойства, когда хотите, чтобы ваше представление получило доступ к внешнему источнику данных, который принадлежит другому представлению или делегату приложения.
Наблюдаемый объект — это экземпляр пользовательского класса, который соответствует протоколу ObservableObject и может использоваться для хранения данных и логики приложения. Он также может использовать обертку свойств @Published, чтобы пометить некоторые из своих свойств как опубликованные, что означает, что они будут вызывать обновления представления при каждом изменении.
class TimerModel: ObservableObject { @Published var secondsElapsed = 0 var timer = Timer() init() { timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in self.secondsElapsed += 1 } } } struct TimerView: View { @ObservedObject var timer: TimerModel var body: some View { Text("Seconds elapsed: \(timer.secondsElapsed)") } } struct ContentView: View { @StateObject var timer = TimerModel() var body: some View { TimerView(timer: timer) } }
В этом примере ContentView создает и владеет экземпляром TimerModel, который является наблюдаемым объектом, хранящим и обновляющим значение таймера. Представление использует @StateObject для сохранения объекта во время обновления представления и предотвращения многократной инициализации. Представление также передает объект в качестве параметра в TimerView, который использует его для отображения значения таймера. Представление использует @ObservedObject для наблюдения за изменениями объекта и соответствующего обновления.
Вы должны использовать @ObservedObject, когда у вас есть модель данных, которая принадлежит другому представлению или делегату приложения, и вы хотите передать ее дочернему представлению. Вы также можете использовать его для создания представлений, которые являются настраиваемыми и могут работать с различными источниками данных.
Заключение
В этой статье мы объяснили различия между различными обертками свойств, которые SwiftUI предоставляет для потока данных. Мы также показали, как правильно использовать их в различных сценариях.