Reputation: 413
Goal: To have unlimited TextFields in list with CRUD functionality using coredata.
Example of goal but w/o Coredata:
struct Item: Identifiable {
let id = UUID()
var title: String
}
class TestItems: ObservableObject {
@Published var items = [Item]()
}
struct ContentView: View {
@ObservedObject var itemGroup = TestItems()
var body: some View {
NavigationView{
List{
ForEach(itemGroup.items.indices, id:\.self) { index in
TextField("Type Stuff Here", text: $itemGroup.items[index].title)
}
.onDelete(perform: removeRows)
}
.navigationBarTitle("Working Example")
.navigationBarItems(trailing: Button(action: {
let stuff = Item(title: "")
itemGroup.items.append(stuff)
}, label: {
Text("Add")
}))
}
}
func removeRows(at offsets: IndexSet) {
itemGroup.items.remove(atOffsets: offsets)
}
}
My Attempt with Coredata:
import SwiftUI
import CoreData
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [
NSSortDescriptor(keyPath: \Item.timestamp, ascending: true),
NSSortDescriptor(keyPath: \Item.title, ascending: true)
],
animation: .default)
private var items: FetchedResults<Item>
var body: some View {
NavigationView{
List {
ForEach(items) { item in
TextField("Type Response Here", text: $item.title) //<-- This returns an error "cannot find item in scope"
}
.onDelete(perform: deleteItems)
}
.navigationBarTitle("CoreData")
.navigationBarItems(trailing: Button(action: {
addItem()
}, label: {
Text("Add Item")
}))
}
}
private func addItem() {
withAnimation {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
newItem.title = "Hello"
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
offsets.map { items[$0] }.forEach(viewContext.delete)
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}
I have also attempted to use structs & classes with Coredata (like my example) but am unable to find a way to save a class into Coredata if that is even possible.
Upvotes: 2
Views: 300
Reputation: 258117
You need to use ObservedObject
over CoreData object and for this it is better to created separated sub-view for row, like
ForEach(items) { item in
ItemView(item: item)
}
and ItemView
struct ItemView: View {
@ObservedObject var item: Item
var body: some View {
// now binding over item title is provided by ObservedObject wrapper
TextField("Type Response Here", text: $item.title)
}
}
Update: handling of optional properties might differ depending on which behavior is expected. Here is possible variant:
var body: some View {
let text = Binding(
get: { item.title ?? "" },
set: { item.title = $0 }
)
TextField("Type Response Here", text: text)
}
Note: entering text into field does not save CoreData object, so you need to think where to save it, possible variant is in .onCommit
for TextField
.
Upvotes: 2