【问题标题】:Bidirectional binding with SwiftUI and Combine与 SwiftUI 和 Combine 的双向绑定
【发布时间】:2020-03-23 07:01:22
【问题描述】:

我正在尝试弄清楚如何在父子关系中的两个 ViewModel 之间正确传递一个对象或一组值,以便在更新子 ViewModel 时,更改气泡会回到父级。

这在仅使用 SwiftUI 视图并直接绑定到商店时非常简单,但我希望将字段验证等业务逻辑与 SwiftUI 视图分开。

下面的代码显示了当父级更新时子级更新(如预期),但我需要以某种方式将子级中更改的值传递回父级。我对移动应用程序开发非常陌生,并且仍在学习,所以我确定我错过了一些非常简单的东西。

import SwiftUI
import Combine

struct Person: Hashable {
  var givenName: String
  var familyName: String
}

// my person store - in the real app it's backed by coredata
class PersonStore: ObservableObject {
  @Published var people: [Person] = [
    Person(
      givenName: "Test",
      familyName: "Person"
    )
  ]
  static let shared = PersonStore()
}

// app entrypoint
struct PersonView: View {
  @ObservedObject var viewModel: PersonView_ViewModel = PersonView_ViewModel()

  var body: some View {
    NavigationView {
      VStack {
        List(viewModel.people.indices, id: \.self) { idx in
          NavigationLink(destination: PersonDetailView(viewModel: PersonDetailView_ViewModel(personIndex: idx))) {
            Text(self.viewModel.people[idx].givenName)
          }
        }
      }
    }
  }
}

class PersonView_ViewModel: ObservableObject {
  @Published var people: [Person] = PersonStore.shared.people
}

// this is the detail view
struct PersonDetailView: View {
  @ObservedObject var viewModel: PersonDetailView_ViewModel

  var body: some View {
    Form {
      Section(header: Text("Parent View")) {
        VStack {
          TextField("Given Name", text: self.$viewModel.person.givenName)
          Divider()
          TextField("Family Name", text: self.$viewModel.person.familyName)
        }
      }
      PersonBasicDetails(viewModel: PersonBasicDetails_ViewModel(person: viewModel.person))
    }
  }
}

// viewmodel associated with detail view
class PersonDetailView_ViewModel: ObservableObject {
  @Published var person: Person

  init(personIndex: Int) {
    self.person = PersonStore.shared.people[personIndex]
  }
}

// this is the child view - in the real app there are multiple sections which are conditionally rendered
struct PersonBasicDetails: View {
  @ObservedObject var viewModel: PersonBasicDetails_ViewModel

  var body: some View {
    Section(header: Text("Child View")) {
      VStack {
        TextField("Given Name", text: self.$viewModel.person.givenName)
        Divider()
        TextField("Family Name", text: self.$viewModel.person.familyName)
      }
    }
  }
}

class PersonBasicDetails_ViewModel: ObservableObject {
  @Published var person: Person

  init(person: Person) {
    self.person = person
  }
}

struct PersonView_Previews: PreviewProvider {
  static var previews: some View {
    PersonView()
  }
}

【问题讨论】:

    标签: swiftui combine


    【解决方案1】:

    在网络上的大多数 SwiftUI TextField 示例中,绑定是通过使用 @State 变量来提供的,该变量会为您创建 Binding 的实例。

    不过,您也可以使用Binding 构造函数创建自定义绑定。下面是一个例子:

    TextField(
      "Given Name",
      text: Binding(
        get: { self.$viewModel.person.givenName },
        set: { self.$viewModel.person.givenName = $0 }))
    

    【讨论】:

    • 非常感谢您发布此信息。这是我一直在寻找的确切解决方案!
    • 那么当观察值在视图模型中时我如何使用@state?而不是在 SwiftUI 导入视图类中?
    【解决方案2】:

    如果你想要两种方式的作品,不仅你需要发布,你还必须使用绑定向上。

    struct Person: Hashable {
        var givenName: String
        var familyName: String
    }
    
    // my person store - in the real app it's backed by coredata
    class PersonStore: ObservableObject {
        @Published var people: [Person] = [
            Person(givenName: "Test",familyName: "Person")
        ]
    
        static let shared = PersonStore()
    }
    
    // app entrypoint
    struct PersonView: View {
        @ObservedObject var viewModel: PersonView_ViewModel = PersonView_ViewModel()
    
        var body: some View {
            NavigationView {
                VStack {
                    List(viewModel.people.indices, id: \.self) { idx in
                        NavigationLink(destination: PersonDetailView(viewModel: PersonDetailView_ViewModel(person: self.$viewModel.people , index: idx ))) {
                            Text(self.viewModel.people[idx].givenName)
                        }
                    }
                }
            }
        }
    }
    
    class PersonView_ViewModel: ObservableObject {
        @Published var people: [Person] = PersonStore.shared.people
    }
    
    // this is the detail view
    struct PersonDetailView: View {
        @ObservedObject var viewModel: PersonDetailView_ViewModel
    
        var body: some View {
            Form {
                Section(header: Text("Parent View")) {
                    VStack {
                        TextField("Given Name", text: self.viewModel.person.givenName)
                        Divider()
                        TextField("Family Name", text: self.viewModel.person.familyName)
                    }
                }
                 PersonBasicDetails(viewModel: PersonBasicDetails_ViewModel(person: viewModel.person))
            }
        }
    }
    
    // viewmodel associated with detail view
    class PersonDetailView_ViewModel: ObservableObject {
        @Published var person: Binding<Person>
    
        init(person: Binding<[Person]> ,index: Int) {
            self.person = person[index]
        }
    }
    
    // this is the child view - in the real app there are multiple sections which are conditionally rendered
    struct PersonBasicDetails: View {
        @ObservedObject var viewModel: PersonBasicDetails_ViewModel
    
        var body: some View {
            Section(header: Text("Child View")) {
                VStack {
                    TextField("Given Name", text: self.viewModel.person.givenName)
                    Divider()
                    TextField("Family Name", text: self.viewModel.person.familyName)
                }
            }
        }
    }
    
    class PersonBasicDetails_ViewModel: ObservableObject {
        @Published var person: Binding<Person>
    
        init(person: Binding<Person>) {
            self.person = person //person
        }
    }
    
    struct PersonView_Previews: PreviewProvider {
        static var previews: some View {
            PersonView()
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-03-31
      • 1970-01-01
      • 2011-02-16
      • 2020-01-10
      • 2016-02-10
      • 2012-07-10
      • 1970-01-01
      相关资源
      最近更新 更多