如果在 ItemView 中更新,则无法刷新 UI

Posted

技术标签:

【中文标题】如果在 ItemView 中更新,则无法刷新 UI【英文标题】:Cannot refresh UI if update in ItemView 【发布时间】:2020-02-27 08:28:38 【问题描述】:

如果我只是将 ItemView 的代码放在 ForEach 中,触摸列表项的名称,切换属性检查,更改名称颜色,它就可以正常工作。当我将代码提取到ItemView中时,数据发生了变化,但UI没有刷新,为什么?

请注意,Item 是类,而不是结构。也许这很重要。

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()
        
    

【问题讨论】:

【参考方案1】:

这是因为ItemView默认检测为相等,因为item属性是指针。

这是可以做的(快照不可测试),所以只是粗糙(但它应该根据经验工作):

1) 如下更改ForEach

ForEach(items)  item in
    ItemView(item: item).equatable()

2) [!!!] 确保您的 Item 也是 Equatable 考虑到更改 check 属性

3) 使ItemView 相等

struct ItemView: View, Equatable 
    static func == (lhs: ItemView, rhs: ItemView) -> Bool 
        return lhs.item == rhs.item // preferable, but can be other appropriate
    
    ...

更新:替代方式

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
        
    

【讨论】:

lhs 和 rhs 总是指向同一个地址,所以总是相等的。 @foolbear,这就是为什么项目应该明确符合 Equatable,覆盖 == 并比较所有属性,因为默认情况下只比较指针。 两个项目指向同一个地址,这两个项目的所有属性也相同,所以总是相等的。毫无意义。 @foolbear,这真的很奇怪......无论如何,总是有替代方式,请参阅更新部分。 是的,它会强制 ItemView 刷新。但这不是正常的方式。我发现 ItemView(item: item).id(item.check) 或 ItemView(item: item).id(UUID()) 工作正常,但我认为它们也不正常。

以上是关于如果在 ItemView 中更新,则无法刷新 UI的主要内容,如果未能解决你的问题,请参考以下文章

使用更新的参数集无法从 VB 关闭或刷新访问报表

SwiftUI中状态改变未导致视图正确刷新的原因及解决

SwiftUI中状态改变未导致视图正确刷新的原因及解决

如何在父组件中强制刷新子组件

如果文件可用于 JavaScript,则启用下载按钮

在 3ds Max 中更新进度条后,环境和效果 UI 不刷新