如何最好地传递数据以在 swiftui 中进行表单编辑(同时让该数据可用于父视图和其他子视图)?

Posted

技术标签:

【中文标题】如何最好地传递数据以在 swiftui 中进行表单编辑(同时让该数据可用于父视图和其他子视图)?【英文标题】:How to best pass data for form editing in swuiftui (while having that data available to parent view and other subviews)? 【发布时间】:2021-01-17 05:59:14 【问题描述】:

我在 MyFormSubView.swift 中有一个 SwiftUI 表单,其中我有多个 @State 变量代表文本等各个字段。我的问题是我的父视图“ContentView.swift”也需要访问此信息,以及其他子视图“ OtherView.swift" 也将受益于访问以进行显示或编辑。我目前的方法是将所有@State 更改为@Binding,这会让人头疼,因为某些表单可能有多达 20 个字段以及一些可选的……处理这个问题的最佳方法是什么?有没有办法简单地传递一个对象并让它“可编辑”?

方法:

    (当前,问题方法)将多个单独的变量声明为 @State 在 ContentView.swift 中,并将每个单独的变量传递给 MyFormSubView.swift 与前面有 @Binding 的那些变量 它们映射到 swiftui 元素以显示为“占位符” text' in textboxes 等。这很糟糕,因为我可能有多达 30 个字段,其中一些是可选的。

    (What I Think I Desire) 拥有可识别的模型 字段(并且可能将此模型传递给 MyFormSubView.swift,并且 如果可能的话,绑定到它并让每个字段 是 $mymodel.field1、$mymodel.field2 等... 需要将 30 多个变量传递给这个东西。

    (也许更好?)使用@ObservableObject。

#2 可能吗?还是有更好的方法?示例代码会很棒!

【问题讨论】:

Rolando,你可以创建一个结构体,你可以创建一个 State 对象 #2。然后,您可以将单个对象传递给您的表单。让它成为一个可观察的对象#3 会更好。我今天会尝试写它,稍后再发布一些东西。 Rolando,在 medium.com 上发布了一篇文章,其中包含所有选项。在这里阅读。 medium.com/codestory/… 【参考方案1】:

有几种方法可以跨视图传递这样的数据。以下是概述 4 种方法的快速实现。

    您可以使用@ObservableObject 来引用包含所有数据的类。变量是@Published,它允许视图以与@State 变量相同的方式更新。 您可以使用@StateObject。这与@ObservableObject 相同,只是它只会初始化一次,并且如果视图重新呈现该变量将保持不变(而@ObservedObject 将重新初始化)。 Read more about the difference here. 您可以使用@EnvironmentObject。这与@ObservedObject 相同,只是它存储在Environment 中,因此您不必在视图之间手动传递它。当您拥有复杂的视图层次结构并且并非每个视图都需要对数据的引用时,这是最好的选择。 您可以创建自定义模型并使用@State 变量。

所有这些方法都有效,但根据您的描述,我认为第二种方法可能最适合您的情况。

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)
        
    

【讨论】:

这真的很有用,但是如何处理 OP 问题的一个元素:“......有些是可选的”?【参考方案2】:

使用 ObservalbeObject,这里是如何共享数据的简单示例: 第 1 步 - 添加某种状态:

class AppState: ObservableObject 
    @Published var value: String = ""

第 2 步 - 通过设置 enviromentObject 将状态传递给 ContentView

    ContentView()
            .environmentObject(AppState())

第 3 步 - 现在 AppState 将在 ContentView 的所有子视图中可用,因此这里是 ContentView 和 OtherView 的代码。 OtherView 有 TextField,其中的文本将保存到 AppState 中,当您在 OtherView 中按“返回”时,您可以看到它。

内容视图:

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()
        
    

这只是一个简单的示例,对于更复杂的情况,您可以查看 Swift UI 中的 VIPER 或 MVVM 模式。例如,这里: https://www.raywenderlich.com/8440907-getting-started-with-the-viper-architecture-pattern https://www.raywenderlich.com/4161005-mvvm-with-combine-tutorial-for-ios

【讨论】:

维护单个 Observable+EnvironmentObject 'appState' 来维护整个应用程序的任何变量是否是“好习惯”?我知道可以有多个 ObservableObject,但最好只有一个吗? 这取决于您如何处理数据和 UI 更改,UI 的哪些部分应该更改,哪些不应该更改等。对于具有简单 UI 和示例的小型应用程序 - 为什么不呢。对于复杂的应用程序,还有许多其他模式。例如,应用程序的每个小模块都可以有自己的“状态”。

以上是关于如何最好地传递数据以在 swiftui 中进行表单编辑(同时让该数据可用于父视图和其他子视图)?的主要内容,如果未能解决你的问题,请参考以下文章

我如何传递从另一个模型获取的角色数组以在 laravel 中注册表单

将变量从 UIHostingController 传递到 SWIFTUI 视图

Cloudkit 与组合

如何在 SwiftUI 中创建共享导航栏以在多个视图之间进行交互? [关闭]

如何在 Body 中传递查询数据以在 graphql 中进行 REST API 调用

如何使用 MapKit (SwiftUI) 将工具提示添加到地图注释以在地图上显示位置名称