SwiftUI ObservedObject导致不良的可见视图更新

时间:2020-06-05 11:30:17

标签: swift swiftui observableobject observedobject

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

2 个答案:

答案 0 :(得分: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!")
    }
}

答案 1 :(得分: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!")
    }
}