#ios #swift #swiftui
#iOS #swift #swiftui
Вопрос:
У меня есть форма SwiftUI в MyFormSubView.swift, где у меня есть несколько переменных @State, представляющих отдельные поля, такие как текст и т.д. Моя проблема в том, что моему родительскому представлению «contentView.swift» также необходим доступ к этой информации, а другие вложенные представления «otherView.swift» также получат доступ для отображения или редактирования. Мой текущий подход заключается в том, чтобы изменить все @State на @Binding, что создает головную боль, потому что некоторые формы могут содержать до 20 полей с некоторыми необязательными … каков наилучший способ справиться с этим? Есть ли способ просто передать объект и сделать его «редактируемым»?
Подходы:
- (Текущий, проблемный подход) Имейте несколько отдельных переменных, объявленных как @State в contentView.swift, и передавайте каждую отдельную переменную в MyFormSubView.swift с теми переменными, которые имеют @Binding перед ними, которые сопоставляются с элементами swiftui, Чтобы отображаться как «текст-заполнитель» в текстовых полях и т. Д. Это плохо, поскольку у меня потенциально может быть до 30 полей, некоторые из которых являются необязательными.
- (То, что я думаю, я желаю) Имейте идентифицируемую модель со всеми полями (и, возможно, передайте эту модель в MyFormSubView.swift, и если это возможно, привяжитесь к ней и просто сделайте так, чтобы каждое поле было $ mymodel .field1, $mymodel.field2 и т. Д. … Что устраняет необходимость передачи более 30 переменных в эту вещь.
- (Может быть, лучше?) Используйте @ObservableObject .
Возможно ли # 2? Или есть еще лучший способ? Пример кода был бы отличным!
Комментарии:
1. Роландо, ты можешь создать структуру, которая превратит тебя в объект состояния №2. Затем вы можете передать один объект в из своей формы. Сделать его наблюдаемым объектом № 3 было бы еще лучше. Я постараюсь написать это сегодня и выложу что-нибудь позже.
2. Роландо, опубликовал статью о medium.com со всеми возможными вариантами. Прочтите это здесь. medium.com/codestory /…
Ответ №1:
Существует несколько способов передачи подобных данных между представлениями. Вот краткая реализация, описывающая 4 подхода.
- Вы можете использовать an
@ObservableObject
для ссылки на класс со всеми вашими данными внутри. Переменные есть@Published
, что позволяет представлению обновляться таким же образом@State
, как и переменная. - Вы можете использовать
@StateObject
. Это то же самое@ObservableObject
, что и, за исключением того, что он будет инициализироваться только один раз, и если представление повторно отобразит переменную, она сохранится (в то время как an@ObservedObject
будет повторно инициализироваться). Подробнее о различии читайте здесь. - Вы можете использовать
@EnvironmentObject
. Это то же самое@ObservedObject
, что и, за исключением того, что оно хранится в среде, поэтому вам не нужно вручную передавать его между представлениями. Это лучше всего, когда у вас сложная иерархия представлений и не каждое представление нуждается в ссылке на данные. - Вы можете создать пользовательскую модель и использовать
@State
переменную.
Все эти методы работают, но, основываясь на вашем описании, я бы сказал, что 2-й метод, вероятно, лучше всего подходит для вашей ситуации.
class DataViewModel: ObservableObject {
@Published var text1: String = "One"
@Published var text2: String = "Two"
@Published var text3: String = "Three"
}
struct DataModel {
var text1: String = "Uno"
var text2: String = "Dos"
var text3: String = "Tres"
}
struct AppView: View {
var body: some View {
MainView()
.environmentObject(DataViewModel())
}
}
struct MainView: View {
@StateObject var dataStateViewModel = DataViewModel()
@ObservedObject var dataObservedViewModel = DataViewModel()
@EnvironmentObject var dataEnvironmentViewModel: DataViewModel
@State var dataStateModel = DataModel()
@State var showSheet: Bool = false
@State var showOtherView: Bool = false
var body: some View {
VStack(spacing: 20) {
Text(dataStateViewModel.text1)
.foregroundColor(.red)
Text(dataObservedViewModel.text2)
.foregroundColor(.blue)
Text(dataEnvironmentViewModel.text3)
.foregroundColor(.green)
Text(dataStateModel.text1)
.foregroundColor(.purple)
Button(action: {
showSheet.toggle()
}, label: {
Text("Button 1")
})
.sheet(isPresented: $showSheet, content: {
FormView(dataStateViewModel: dataStateViewModel, dataObservedViewModel: dataObservedViewModel, dataStateModel: $dataStateModel)
.environmentObject(dataEnvironmentViewModel) // Sheet is a new environment
})
Button(action: {
showOtherView.toggle()
}, label: {
Text("Button 2")
})
if showOtherView {
ThirdView(dataStateViewModel: dataStateViewModel, dataObservedViewModel: dataObservedViewModel, dataStateModel: $dataStateModel)
}
}
}
}
struct FormView: View {
@StateObject var dataStateViewModel: DataViewModel
@ObservedObject var dataObservedViewModel: DataViewModel
@EnvironmentObject var dataEnvironmentViewModel: DataViewModel
@Binding var dataStateModel: DataModel
@Environment(.presentationMode) var presentationMode
var body: some View {
Form(content: {
Button(action: {
presentationMode.wrappedValue.dismiss()
}, label: {
Text("BACK")
})
Text("EDIT TEXT FIELDS:")
TextField("Placeholder 1", text: $dataStateViewModel.text1)
.foregroundColor(.red)
TextField("Placeholder 2", text: $dataObservedViewModel.text2)
.foregroundColor(.blue)
TextField("Placeholder 3", text: $dataEnvironmentViewModel.text3)
.foregroundColor(.green)
TextField("Placeholder 4", text: $dataStateModel.text1)
.foregroundColor(.purple)
})
}
}
struct ThirdView: View {
@StateObject var dataStateViewModel: DataViewModel
@ObservedObject var dataObservedViewModel: DataViewModel
@EnvironmentObject var dataEnvironmentViewModel: DataViewModel
@Binding var dataStateModel: DataModel
var body: some View {
VStack(spacing: 20) {
Text(dataStateViewModel.text1)
.foregroundColor(.red)
Text(dataObservedViewModel.text2)
.foregroundColor(.blue)
Text(dataEnvironmentViewModel.text3)
.foregroundColor(.green)
Text(dataStateModel.text1)
.foregroundColor(.purple)
}
}
}
Комментарии:
1. Это действительно полезно, но как справиться с одним элементом вопроса OP: «… при этом некоторые из них являются необязательными «?
Ответ №2:
Используйте ObservalbeObject, вот простой пример того, как вы можете обмениваться данными: Шаг 1 — Добавьте какое-то состояние:
class AppState: ObservableObject {
@Published var value: String = ""
}
Шаг 2 — Передать состояние в contentView с помощью настройки enviromentObject
ContentView()
.environmentObject(AppState())
Шаг 3 — Теперь AppState будет доступен во всех дочерних представлениях contentView, поэтому вот код для contentView и otherView. В otherView есть текстовое поле, текст из которого будет сохранен в AppState, и вы сможете его увидеть, когда нажмете «назад» из otherView.
contentView:
import SwiftUI
struct ContentView: View {
@EnvironmentObject var appState: AppState
var body: some View {
NavigationView {
VStack {
Text("Content View")
.padding()
NavigationLink(
destination: OtherView()
) {
Text("Open other view")
}
Text("Value from other view: (appState.value)")
}
}
}
}
Другой вид:
import SwiftUI
struct OtherView: View {
@EnvironmentObject var appState: AppState
@State var value: String = ""
var body: some View {
VStack {
Text("Other View")
.padding()
TextField("Enter value", text: Binding<String>(
get: { self.value },
set: {
self.value = $0
appState.value = $0
}
))
.frame(width: 200)
.padding()
}
}
}
Это всего лишь простой пример, для более сложных случаев вы можете взглянуть на шаблоны VIPER или MVVM в Swift UI. Например, здесь:
https://www.raywenderlich.com/8440907-getting-started-with-the-viper-architecture-pattern
https://www.raywenderlich.com/4161005-mvvm-with-combine-tutorial-for-ios
Комментарии:
1. Является ли «хорошей практикой» поддерживать единый Observable EnvironmentObject «AppState», который поддерживает любые переменные для всего приложения? Я знаю, что возможно иметь несколько ObservableObjects, но лучше ли иметь только один?
2. Это зависит от того, как вы обрабатываете данные и изменения пользовательского интерфейса, какие части пользовательского интерфейса должны меняться, что не должно и т.д. Для небольших приложений с простым интерфейсом и примерами — почему бы и нет. Для сложных приложений существует множество других шаблонов. Например, каждый небольшой модуль приложения может иметь свое собственное «состояние».