簡體   English   中英

SwiftUI ObservedObject 導致不需要的可見視圖更新

[英]SwiftUI ObservedObject causes undesirable visible view updates

我正在開發一個將過濾器應用於圖像的應用程序。 過濾器有許多用戶可以修改的參數。 我創建了一個包含所述參數的 ObservableObject。 每當其中一個參數發生變化時,視圖都會有可見的更新,即使視圖顯示的值與以前相同。 當我將參數作為單獨的@State 變量時,這不會發生。

如果這是意料之中的(畢竟觀察到的 object確實發生了變化,所以依賴於它的每個視圖都會更新),ObservedObject 是適合這項工作的工具嗎? 另一方面,model 將參數作為單獨的@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())
        }
    }
}

警告:這個答案不太理想。 如果參數的屬性將在另一個視圖中更新(例如,額外的選擇器),則不會更新選擇器視圖。

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!")
    }
}

第二次嘗試

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!")
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM