Reputation: 15
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>
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)
}
}
}
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.
Upvotes: 0
Views: 661
Reputation: 5135
TextField
needs a binding to a string variable. Account
is and ObservableObject, like all NSManagedObjects, so if you refactor your TextField
into a separate View you could do this:
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)
}
}
Note the $account.email
— the $
turns this into a binding.
Unfortunately, this new code also fails, because email
is a String?
(i.e., it can be nil) but TextField
doesn’t allow nil. Thankfully that’s not too hard to fix, because we can define a custom Binding like so:
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 = $0 })
}
var body: some View {
TextField("Email", text: email)
}
}
Notice we don’t have a $
this time, because email
is itself a Binding.
Upvotes: 1
Reputation: 52535
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
Upvotes: 0