【问题标题】:SwiftUI ObservedObject causes undesirable visible view updatesSwiftUI ObservedObject 导致不需要的可见视图更新
【发布时间】:2020-06-05 11:30:17
【问题描述】:

我正在开发一个对图像应用过滤器的应用程序。过滤器有许多用户可以修改的参数。我创建了一个包含所述参数的 ObservableObject。每当其中一个参数发生变化时,视图都会有可见的更新,即使视图显示的值与以前相同。当我将参数建模为单独的 @State 变量时,不会发生这种情况。

如果这是意料之中的(在所有观察到的对象确实发生变化,因此依赖于它的每个视图都会更新),那么 ObservedObject 是否适合这项工作?另一方面,将参数建模为单独的@State/@Binding 变量似乎非常不方便,特别是如果需要将大量参数(例如 10+)传递给多个子视图!

因此我的问题是:

我在这里正确使用 ObservedObject 吗?可见更新是无意的,但可以接受,还是在 swiftUI 中有更好的解决方案来处理这个问题?

使用@ObservedObject 的示例:

import SwiftUI

class Parameters: ObservableObject {
    @Published var pill: String = "red"
    @Published var hand: String = "left"
}

struct ContentView: View {

    @ObservedObject var parameters = Parameters()

    var body: some View {
        VStack {

            // Using the other Picker causes a visual effect here...
            Picker(selection: self.$parameters.pill, label: Text("Which pill?")) {

                Text("red").tag("red")
                Text("blue").tag("blue")

            }.pickerStyle(SegmentedPickerStyle())

            // Using the other Picker causes a visual effect here...
            Picker(selection: self.$parameters.hand, label: Text("Which hand?")) {

                Text("left").tag("left")
                Text("right").tag("right")

            }.pickerStyle(SegmentedPickerStyle())
        }
    }
}

使用@State 变量的示例:

import SwiftUI

struct ContentView: View {

    @State var pill: String = "red"
    @State var hand: String = "left"

    var body: some View {
        VStack {

            Picker(selection: self.$pill, label: Text("Which pill?")) {

                Text("red").tag("red")
                Text("blue").tag("blue")

            }.pickerStyle(SegmentedPickerStyle())

            Picker(selection: self.$hand, label: Text("Which hand?")) {

                Text("left").tag("left")
                Text("right").tag("right")

            }.pickerStyle(SegmentedPickerStyle())
        }
    }
}

【问题讨论】:

  • 根据同一个 ObservableObject 自己的属性将每个 Picker 移动到自己的子视图中,这样它们就不会相互依赖了。
  • @Asperi 你能举个代码例子吗?我尝试为具有绑定属性和观察对象属性的选择器创建子视图,并且两者都表现出相同的行为:如果选择器更改,则可见更新。

标签: swift swiftui observableobject observedobject


【解决方案1】:

警告:这个答案不太理想。如果参数的属性将在另一个视图中更新(例如额外的选择器),则选择器视图将更新。

ContentView 不应“观察”参数;参数的更改将导致它更新其内容(在 Pickers 的情况下可见)。为了避免需要观察到的属性包装器,我们可以为参数的属性提供显式绑定。 ContentView 的子视图可以在参数上使用@Observed。

import SwiftUI

class Parameters: ObservableObject {
    @Published var pill: String = "red"
    @Published var hand: String = "left"
}

struct ContentView: View {

    var parameters = Parameters()

    var handBinding: Binding<String> {
        Binding<String>(
            get: { self.parameters.hand },
            set: { self.parameters.hand = $0 }
        )
    }

    var pillBinding: Binding<String> {
        Binding<String>(
            get: { self.parameters.pill },
            set: { self.parameters.pill = $0 }
        )
    }

    var body: some View {
        VStack {

            InfoDisplay(parameters: parameters)

            Picker(selection: self.pillBinding, label: Text("Which pill?")) {
                Text("red").tag("red")
                Text("blue").tag("blue")

            }.pickerStyle(SegmentedPickerStyle())

            Picker(selection: self.handBinding, label: Text("Which hand?")) {
                Text("left" ).tag("left")
                Text("right").tag("right")

            }.pickerStyle(SegmentedPickerStyle())
        }
    }
}

struct InfoDisplay: View {
    @ObservedObject var parameters: Parameters

    var body: some View {
        Text("I took the \(parameters.pill) pill from your \(parameters.hand) hand!")
    }
}

【讨论】:

    【解决方案2】:

    第二次尝试

    ContentView 不应观察参数(这会导致不希望的可见更新)。参数的属性也应该是 ObservableObjects 以确保视图可以在特定属性更改时更新。

    由于字符串是结构,它们不能符合 ObservableObject;需要一个小的包装器“ObservableValue”。

    MyPicker 是 Picker 的一个小包装器,用于在更改时更新视图。默认的 Picker 接受绑定,因此依赖于层次结构的视图来执行更新。

    这种方法感觉可扩展:

    • 只有一个事实来源(ContentView 中的参数)
    • 视图仅在必要时更新(无不良视觉效果)

    缺点:

    • 似乎有很多样板代码用于感觉如此微不足道的东西应该由平台提供(我觉得我错过了一些东西)
    • 如果您为同一属性添加第二个 MyPicker,则更新不是即时的。
    import SwiftUI
    import Combine
    
    class ObservableValue<Value: Hashable>: ObservableObject {
        @Published var value: Value
    
        init(initialValue: Value) {
            value = initialValue
        }
    }
    
    struct MyPicker<Value: Hashable, Label: View, Content : View>: View {
    
        @ObservedObject var object: ObservableValue<Value>
        let content: () -> Content
        let label: Label
    
        init(object: ObservableValue<Value>,
             label: Label,
             @ViewBuilder _ content: @escaping () -> Content) {
            self.object  = object
            self.label   = label
            self.content = content
        }
    
        var body: some View {
            Picker(selection: $object.value, label: label, content: content)
                .pickerStyle(SegmentedPickerStyle())
        }
    }
    
    class Parameters: ObservableObject {
        var pill = ObservableValue(initialValue: "red" )
        var hand = ObservableValue(initialValue: "left")
    
        private var subscriber: Any?
    
        init() {
            subscriber = pill.$value
                .combineLatest(hand.$value)
                .sink { _ in
                self.objectWillChange.send()
            }
        }
    }
    
    struct ContentView: View {
    
        var parameters = Parameters()
    
        var body: some View {
            VStack {
                InfoDisplay(parameters: parameters)
    
                MyPicker(object: parameters.pill, label: Text("Which pill?")) {
                    Text("red").tag("red")
                    Text("blue").tag("blue")
                }
    
                MyPicker(object: parameters.hand, label: Text("Which hand?")) {
                    Text("left").tag("left")
                    Text("right").tag("right")
                }
            }
        }
    }
    
    struct InfoDisplay: View {
        @ObservedObject var parameters: Parameters
    
        var body: some View {
            Text("I took the \(parameters.pill.value) pill from your \(parameters.hand.value) hand!")
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-12-21
      • 2020-07-12
      • 2021-02-17
      • 2020-05-02
      • 1970-01-01
      • 1970-01-01
      • 2020-10-22
      相关资源
      最近更新 更多