SwiftUI Core Data Binding TextFields in DetailView

家住魔仙堡 提交于 2021-02-08 05:55:51

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!