首页 > 解决方案 > SwiftUI 视图会更新,即使它不依赖于动态属性

问题描述

我创建了一个ObservableObject收集数据的视图和一个依赖于该对象的视图。更具体地说,UI 的某些部分依赖于它的一个属性,而其他部分依赖于其他属性。

工作方式ObservableObject是,如果它的任何Published属性被更新(如果它没有改变,则事件)它会发送一个objectWillChange通知,触发订阅视图的更新。

但是,我确实希望只有依赖于ObservableObject实际更改的属性的视图部分会被更新,而不是整个视图,因为主体计算很昂贵。

不幸的是,这不是我在实验中观察到的行为。例如,在下面的代码中,List视图仅取决于“首选项”的color动态属性,并且仅更新观察对象的属性。我观察到,当我键入文本时,列表及其行每次都会更新,即使它不依赖于.ObservedObjectTextFieldtextdebugPrinttext

struct ContentView: View {
    @State private var list = ["foo", "bar", "baz"]
    @StateObject private var preferences = Preferences()
    
    var body: some View {
        VStack {
            List(list, id: \.self) { element in
                Text(element)
                    .foregroundColor(preferences.color)
                    .debugPrint("Row view updated")
            }
            
            Spacer()
            
            HStack {
                TextField("Text", text: $preferences.text)
                    .debugPrint("Text field view updated")
                
                Button("Toogle Color", action: toggleColor)
                    .debugPrint("Button view updated")
            }
        }
        .padding()
        .debugPrint("Content view updated")
    }
    
    func toggleColor() {
        preferences.color = [.blue, .green, .orange, .red].randomElement()!
    }
}

final class Preferences: ObservableObject {
    @Published var color: Color = .blue
    @Published var text: String = ""
}

extension View {
    func debugPrint(_ elements: Any...) -> Self {
        #if DEBUG
        print(elements)
        return self
        #else
        return self
        #endif
    }
}

这是正确和预期的行为吗?如何获得我上面描述的最佳行为,即bodyObservedObject更改时不调用,而只调用依赖于特定属性的组件?

我在较大的 SwiftUI 项目中观察到,这种行为会大大降低应用程序的速度并且不容易调试,因为唯一状态通常通过在根视图中注入的重ObservedObjects (或StateObject)来强制执行,并在许多地方使用。我观察到了这种行为,即使是带有大量 UI 布局的小组件列表。

注意 1:我被定义所困扰,该StateObject定义表明只有依赖于这些属性的视图才会更新(而不是在StateObject更改时)。

SwiftUI 只为声明对象的结构的每个实例创建一次对象的新实例。当可观察对象的已发布属性发生更改时,SwiftUI 会更新依赖于这些属性的任何视图的部分 [...]

注2:如果我List在自己的组件中定义,问题完全消失,渲染速度很快:

struct ListView: View {
    let list: [String]  // Also works with a @Binding
    
    let color: Color  // Also works if it would be `preferences`
    
    var body: some View {
        List {
            ForEach(list, id: \.self) { element in
                Row(text: element, color: color)
            }
        }
    }
}

为什么在这种情况下视图更新很快?

标签: swiftui

解决方案


推荐阅读