为什么使用 FetchedResults 对 Text 而不是 TextField 有效? SwiftUI、CoreData/FetchRequest

Why does using FetchedResults work for Text and not TextField? SwiftUI, CoreData/ FetchRequest

我正在从 FetchRequest 获取 CoreData 属性并希望使用它来预填充文本字段(用户的电子邮件)。

这是FetchRequest

     @FetchRequest(
       entity: Account.entity(),
       sortDescriptors:[
       NSSortDescriptor(keyPath: \Account.id, ascending: true)
       ]
     )var accounts: FetchedResults<Account>

然后在我的视图堆栈中我有:

var body: some View{
  ZStack {
    Form {
      Section(header: Text("EMAIL")) {
        ForEach(accounts, id: \.self) {account in
          TextField("Email", text:account.email)    // This is where I get an error (see error below)                         
          }
        }
    }

不过,我只是在文本字段中列出 accounts 即可。像这样:

var body: some View{
  ZStack {
    Form {
      List(accounts, id: \.self) { account in
         Text(account.email ?? "Unknown")
      }
    }
  }

为什么使用 List 的第二个代码没有给我同样的错误?

我认为它与 ?? 运算符有关,但经过研究后我意识到这样做非常好,因为我的 coredata 对象中的 emailString?

现在我的想法是我在这里收到此错误是因为 TextField 需要一个 Binding 包装器?如果那是真的,我不确定如何让它发挥作用。我想要做的就是用 FetchRequest 从我的 Account 核心数据对象中检索到的单个电子邮件记录预填充此 TextField

谢谢。

编辑:我想补充一点,我找到了这个 post https://www.hackingwithswift.com/quick-start/swiftui/how-to-fix-cannot-convert-value-of-type-string-to-expected-argument-type-binding-string

现在我想我需要做的是将这个 account.email 结果存储到 State 变量中。但是,我的问题仍然存在,我不确定如何执行此操作,因为我正在寻找一种干净的方法来在此处的视图堆栈中正确执行此操作。再次感谢。

此答案详细说明了将 CoreData 请求发送到 @State 变量中:

你最终会得到这样的东西:

@State var text = ""

在您看来(可能附加到您的 ZStack)一个 onReceive 属性 告诉您何时有新的 CoreData 要查看:

ZStack {
...
}.onReceive(accounts.publisher, perform: { _ in
                //you can reference self.accounts at this point
                //grab the index you want and set self.text equal to it
            })

但是,您必须做一些聪明的事情来确保第一次设置它,然后可能不会在用户开始输入后再次修改它。

由于您在一个列表中并且有多个帐户,这也可能变得复杂——此时,我可能会使用单独的 @State 变量将每个列表项拆分到它自己的视图中。

请记住,您也可以像这样编写自定义绑定:

Binding<String>(get: {
//return a value from your CoreData model
}, set: { newValue in })

但是 除非您对 get 部分中的返回方式更加巧妙,否则用户将无法编辑测试。您也可以在幕后用另一个 @State 变量来隐藏它。

最后,Apple Developer 论坛上有一个主题更深入地介绍了解决此问题的可能方法:https://developer.apple.com/forums/thread/128195

TextField 需要 绑定 到字符串变量。 Account 是和 ObservableObject,就像所有 NSManagedObjects 一样,所以如果您将 TextField 重构到一个单独的视图中,您可以这样做:

Form {
    Section(header: Text("EMAIL")) {
        ForEach(accounts, id: \.self) { account in
            Row(account: account)
        }
    }
}

...

struct Row: View {
    @ObservedObject var account: Account
    
    var body: some View {
        TextField("Email", text: $account.email)
    }
}

注意 $account.email$ 将其转换为绑定。

不幸的是,这个新代码也失败了,因为 email 是一个 String?(即,它可以是 nil)但 TextField 不允许 nil。值得庆幸的是,这并不难解决,因为我们可以像这样定义一个自定义绑定:

struct Row: View {
    @ObservedObject var account: Account
    
    var email: Binding<String> {
        Binding<String>(get: {
            if let email = account.email {
                return email
            } else {
                return "Unknown"
            }
        },
        set: { account.email = [=11=] })
    }
    
    var body: some View {
        TextField("Email", text: email)
    }
}

注意这次我们没有 $,因为 email 本身就是一个 Binding。