问题
I have a SwiftUI app with SwiftUI App lifecycle that includes a master-detail type list driven from CoreData. I have the standard list in ContentView and NavigationLinks to the DetailView. I pass a Core Data entity object to the Detailview.
My struggle is setting-up bindings to TextFields in the DetailView for data entry and for editing. I tried to create an initializer which I could not make work. I have only been able to make it work with the following. Assigning the initial values inside the body does not seem like the best way to do this, though it does work.
Since the Core Data entities are ObservableObjects I thought I should be able to directly access and update bound variables, but I could not find any way to reference a binding to Core Data in a ForEach loop.
Is there a way to do this that is more appropriate than my code below?
Simplified Example:
struct DetailView: View {
var thing: Thing
var count: Int
@State var localName: String = ""
@State private var localComment: String = ""
@State private var localDate: Date = Date()
//this does not work - cannot assign String? to State<String>
// init(t: Thing) {
// self._localName = t.name
// self._localComment = t.comment
// self._localDate = Date()
// }
var body: some View {
//this is the question - is this safe?
DispatchQueue.main.async {
self.localName = self.thing.name ?? "no name"
self.localComment = self.thing.comment ?? "No Comment"
self.localDate = self.thing.date ?? Date()
}
return VStack {
Text("\(thing.count)")
.font(.title)
Text(thing.name ?? "no what?")
TextField("name", text: $localName)
Text(thing.comment ?? "no comment?")
TextField("comment", text: $localComment)
Text("\(thing.date ?? Date())")
//TextField("date", text: $localDate)
}.padding()
}
}
And for completeness, the ContentView:
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Thing.date, ascending: false)])
private var things : FetchedResults<Thing>
@State private var count: Int = 0
@State private var coverDeletedDetail = false
var body: some View {
NavigationView {
List {
ForEach(things) { thing in
NavigationLink(destination: DetailView(thing: thing, count: self.count + 1)) {
HStack {
Image(systemName: "gear")
.resizable()
.frame(width: 40, height: 40)
.onTapGesture(count: 1, perform: {
updateThing(thing)
})
Text(thing.name ?? "untitled")
Text("\(thing.count)")
}
}
}
.onDelete(perform: deleteThings)
if UIDevice.current.userInterfaceIdiom == .pad {
NavigationLink(destination: WelcomeView(), isActive: self.$coverDeletedDetail) {
Text("")
}
}
}
.navigationTitle("Thing List")
.navigationBarItems(trailing: Button("Add Task") {
addThing()
})
}
}
private func updateThing(_ thing: FetchedResults<Thing>.Element) {
withAnimation {
thing.name = "Updated Name"
thing.comment = "Updated Comment"
saveContext()
}
}
private func deleteThings(offsets: IndexSet) {
withAnimation {
offsets.map { things[$0] }.forEach(viewContext.delete)
saveContext()
self.coverDeletedDetail = true
}
}
private func addThing() {
withAnimation {
let newThing = Thing(context: viewContext)
newThing.name = "New Thing"
newThing.comment = "New Comment"
newThing.date = Date()
newThing.count = Int64(self.count + 1)
self.count = self.count + 1
saveContext()
}
}
func saveContext() {
do {
try viewContext.save()
} catch {
print(error)
}
}
}
And Core Data:
extension Thing {
@nonobjc public class func fetchRequest() -> NSFetchRequest<Thing> {
return NSFetchRequest<Thing>(entityName: "Thing")
}
@NSManaged public var comment: String?
@NSManaged public var count: Int64
@NSManaged public var date: Date?
@NSManaged public var name: String?
}
extension Thing : Identifiable {
}
Any guidance would be appreciated. Xcode 12.2 iOS 14.2
回答1:
You already mentioned it. CoreData works great with SwiftUI.
Just make your Thing as ObservableObject
@ObservedObject var thing: Thing
and then you can pass values from thing as Binding. This works in ForEach aswell
TextField("name", text: $thing.localName)
回答2:
For others - note that I had to use the Binding extension above since NSManagedObjects are optionals. Thus as davidev stated:
TextField("name", text: Binding($thing.name, "no name"))
And ObservedObject, not Observable
来源:https://stackoverflow.com/questions/65225849/swiftui-core-data-binding-textfields-in-detailview