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

jq6vz3qz  于 12个月前  发布在  Swift
关注(0)|答案(3)|浏览(131)

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

djmepvbi

djmepvbi1#

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

ContentView不应该“observe”参数;参数的更改将导致它更新其内容(在Pickers的情况下是可见的)。为了防止需要observed属性 Package 器,我们可以为参数的属性提供显式绑定。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!")
    }
}

字符串

zz2j4svz

zz2j4svz2#

第二次尝试

ContentView不应该观察参数(这会导致不希望的可见更新)。参数的属性也应该是ObservableObjects,以确保视图可以在特定属性更改时更新。
由于String是结构体,它们不能符合ObservableObject;一个小的 Package 器'ObservableValue'是必要的。
MyPicker是Picker的一个小 Package 器,用于使视图在更改时更新。默认的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!")
    }
}

字符串

ycggw6v2

ycggw6v23#

快进到2023年。ObservedObject的原始示例不再导致不需要的可见更新,这似乎表明这确实是SwiftUI中的一个小“bug”。请注意,如果ContentView示例化参数,则需要使用@StateObject而不是@ObservedObject。否则,您应该将参数传递给ContentView。

相关问题