foolbear
foolbear

Reputation: 964

Cannot refresh UI if update in ItemView

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

Answers (1)

Asperi
Asperi

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

Related Questions