Reputation: 964
It works fine if I just put the code of ItemView in ForEach, touch the name of list item, attribute check toggled, name color changed. When I extract the code into ItemView, data changed but UI not refreshed, why?
notice that Item is class, not a struct. Maybe it's important.
import CoreData
class Item: NSManagedObject, Identifiable {
@NSManaged public var createAt: Date?
@NSManaged public var id: UUID?
@NSManaged public var name: String?
@NSManaged public var check: Bool
...
}
struct ContentView: View {
@Environment(\.managedObjectContext) var context
@FetchRequest(entity: Item.entity(), sortDescriptors: [NSSortDescriptor(key: "createAt", ascending: false)]) var items: FetchedResults<Item>
var body: some View {
NavigationView {
VStack {
List {
ForEach(items) { item in
// HStack {
// Text("\(item.name!)").foregroundColor(item.check ? .red : .gray)
// Spacer()
// }.onTapGesture {
// item.check.toggle()
// try? self.context.save()
// }
ItemView(item: item)
}.onDelete(perform: { indexSet in
let index = indexSet.first!
let item = self.items[index]
self.context.delete(item)
try? self.context.save()
})
}
Button(action: {
let item = Item(context: self.context)
item.id = UUID()
item.createAt = Date()
item.name = "New Item"
try? self.context.save()
}) {
Text("New")
}
}
}
}
}
struct ItemView: View {
@Environment(\.managedObjectContext) var context
var item: Item
var body: some View {
HStack {
Text("\(item.name!)").foregroundColor(item.check ? .red : .gray)
Spacer()
}.onTapGesture {
self.item.check.toggle()
try? self.context.save()
}
}
}
Upvotes: 1
Views: 60
Reputation: 258443
It is because ItemView
is by default detected as equal, because item
property is pointer.
Here is what could be done (snapshot is not testable), so just scratchy (but it should work by experience):
1) change ForEach
as follows
ForEach(items) { item in
ItemView(item: item).equatable()
2) [!!!] make sure your Item
is also Equatable
taking into account changing check
property
3) make ItemView
equatable
struct ItemView: View, Equatable {
static func == (lhs: ItemView, rhs: ItemView) -> Bool {
return lhs.item == rhs.item // preferable, but can be other appropriate
}
...
Update: alternate way
struct ItemView: View {
@Environment(\.managedObjectContext) var context
var item: Item
@State private var refresh: Bool = true
var body: some View {
HStack {
Text("\(item.name!)").foregroundColor(item.check ? .red : .gray)
.background(Color.clear.opacity(self.refresh ? 1.0 : 0.0)) // just to be in view body
Spacer()
}.onTapGesture {
self.item.check.toggle()
try? self.context.save()
self.refresh.toggle() // force refresh self
}
}
}
Upvotes: 2