Search code examples
core-dataswiftuinsfetchrequest

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


I am getting CoreData properties from a FetchRequest and want to use it to pre-populate a text field (a user's Email).

Here is the FetchRequest

     @FetchRequest(
       entity: Account.entity(),
       sortDescriptors:[
       NSSortDescriptor(keyPath: \Account.id, ascending: true)
       ]
     )var accounts: FetchedResults<Account>
  • Note I have all of the proper persistant container and @environment stuff set up to use @FetchRequest like this.

Then in my View stack I have:

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)                         
          }
        }
    }
  • Error is : Cannot convert value of type 'String' to expected argument type 'Binding<String>'

However is I simply list accounts in a textfield it works. Like so:

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

Why does the second code that uses List not give me the same error?

I thought it had something to do with the ?? operator but after research I realized that it perfectly fine to do given that email in my coredata object is String?.

Now my thought is that I am getting this error here because TextField needs a Binding wrapper? If that is true I'm not sure how to get this to work. All I want to do is have this TextField pre-populated with the single email record the FetchRequest retrieves from my Account Core Data object.

Thanks.

Edit: I want to add that I have found this post https://www.hackingwithswift.com/quick-start/swiftui/how-to-fix-cannot-convert-value-of-type-string-to-expected-argument-type-binding-string

and now I think what I need to do is store this account.email result into a State variable. My question still remains however, I'm not sure how to do this as I am looking for clean way to do it right in the view stack here. Thanks again.


Solution

  • This answer details getting a CoreData request into a @State variable: Update State-variable Whenever CoreData is Updated in SwiftUI

    What you'll end up with is something like this:

    @State var text = ""
    

    In your view (maybe attached to your ZStack) an onReceive property that tells you when you have new CoreData to look at:

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

    However, you'll have to do some clever stuff to make sure setting it the first time and then probably not modifying it again once the user starts typing.

    This could also get complicated by the fact that you're in a list and have multiple accounts -- at this point, I may split every list item out into its own view with a separate @State variable.

    Keep in mind, you can also write custom bindings like this:

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

    But unless you get more clever with how you're returning in the get section, the user won't be able to edit the test. You could shadow it with another @State variable behind the scenes, too.

    Finally, here's a thread on the Apple Developer forums that gets even more in-depth about possible ways to address this: https://developer.apple.com/forums/thread/128195