1

我正在开发一个将过滤器应用于图像的应用程序。过滤器有许多用户可以修改的参数。我创建了一个包含所述参数的 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())
        }
    }
}
4

2 回答 2

2

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

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!")
    }
}
于 2020-06-08T10:40:12.457 回答
0

第二次尝试

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!")
    }
}
于 2020-06-09T13:17:10.713 回答