如何在其他属性更改时添加可观察的 属性

How to add an observable property when other properties change

我有以下模型对象,我用它来填充一个 List,每一行都有一个 Toggle,它绑定到 measurement.isSelected

final class Model: ObservableObject {

    struct Measurement: Identifiable {
        var id = UUID()
        let name: String
        var isSelected: Binding<Bool>

        var selected: Bool = false

        init(name: String) {
            self.name = name

            let selected = CurrentValueSubject<Bool, Never>(false)
            self.isSelected = Binding<Bool>(get: { selected.value }, set: { selected.value = [=11=] })
        }
    }

    @Published var measurements: [Measurement]
    @Published var hasSelection: Bool = false  // How to set this?

    init(measurements: [Measurement]) {
        self.measurements = measurements
    }
}

我希望 hasSelection 属性 在 measurement.isSelectedtrue 时为真。我猜想 Model 需要观察 measurements 的变化,然后更新它的 hasSelection 属性…但我不知道从哪里开始!

想法是 hasSelection 将绑定到 Button 以启用或禁用它。


Model用法如下…

struct MeasurementsView: View {

    @ObservedObject var model: Model

    var body: some View {
        NavigationView {
            List(model.measurements) { measurement in
                MeasurementView(measurement: measurement)
            }
            .navigationBarTitle("Select Measurements")
            .navigationBarItems(trailing: NavigationLink(destination: NextView(), isActive: $model.hasSelection, label: {
                Text("Next")
            }))
        }
    }
}

struct MeasurementView: View {
    let measurement: Model.Measurement
    var body: some View {
        HStack {
            Text(measurement.name)
                .font(.subheadline)
            Spacer()
            Toggle(measurement.name, isOn: measurement.isSelected)
                .labelsHidden()
        }
    }
}

有关信息,这是我正在努力实现的屏幕截图。 A list of selectable items, with a navigation link that is enabled when one or more is selected, and disabled when no items are selected.

目前您的代码存在的问题是,即使您观察到 measurements 的更改,它们也不会在选择更新时更新,因为您将 var isSelected: Binding<Bool> 声明为 Binding .这意味着 SwiftUI 将它存储在您的结构之外,并且结构本身不会更新(保持不变)。

你可以尝试在你的模型上声明 @Published var selectedMeasurementId: UUID? = nil 所以你的代码应该是这样的:


import SwiftUI
import Combine

struct NextView: View {
    var body: some View {
        Text("Next View")

    }
}

struct MeasurementsView: View {

    @ObservedObject var model: Model

    var body: some View {

        let hasSelection = Binding<Bool> (
            get: {
                self.model.selectedMeasurementId != nil
            },
            set: { value in
                self.model.selectedMeasurementId = nil
            }
        )

        return NavigationView {
            List(model.measurements) { measurement in
                MeasurementView(measurement: measurement, selectedMeasurementId: self.$model.selectedMeasurementId)
            }
            .navigationBarTitle("Select Measurements")
            .navigationBarItems(trailing: NavigationLink(destination: NextView(), isActive: hasSelection, label: {
                Text("Next")
            }))
        }
    }
}

struct MeasurementView: View {


    let measurement: Model.Measurement
    @Binding var selectedMeasurementId: UUID?

    var body: some View {

        let isSelected = Binding<Bool>(
            get: {
                self.selectedMeasurementId == self.measurement.id
            },
            set: { value in
                if value {
                    self.selectedMeasurementId = self.measurement.id
                } else {
                    self.selectedMeasurementId = nil
                }
            }
        )

        return HStack {
            Text(measurement.name)
                .font(.subheadline)
            Spacer()
            Toggle(measurement.name, isOn: isSelected)
                .labelsHidden()
        }
    }
}

final class Model: ObservableObject {

    @Published var selectedMeasurementId: UUID? = nil

    struct Measurement: Identifiable {
        var id = UUID()
        let name: String

        init(name: String) {
            self.name = name
        }
    }

    @Published var measurements: [Measurement]


    init(measurements: [Measurement]) {
        self.measurements = measurements
    }
}

我不确定您希望导航栏中的导航按钮如何运作。现在我只是在点击时将选择设置为零。您可以根据自己的需要进行修改。

如果你想支持多选,你可以使用一组选择的ids来代替。

此外,似乎 iOS 模拟器在导航方面存在一些问题,但我在物理设备上进行了测试并且它有效。

@user3441734 hasSelection should ideally be a get only property, that is true if any of measurement.isSelected is true

struct Data {
    var bool: Bool
}
class Model: ObservableObject {
    @Published var arr: [Data] = []
    var anyTrue: Bool {
        arr.map{[=10=].bool}.contains(true)
    }
}

示例(和以前一样)复制 - 粘贴 - 运行

import SwiftUI

struct Data: Identifiable {
    let id = UUID()
    var name: String
    var on_off: Bool
}

class Model: ObservableObject {
    @Published var data = [Data(name: "alfa", on_off: false), Data(name: "beta", on_off: false), Data(name: "gama", on_off: false)]
    var bool: Bool {
        data.map {[=11=].on_off} .contains(true)
    }
}



struct ContentView: View {
    @ObservedObject var model = Model()
    var body: some View {
        VStack {
        List(0 ..< model.data.count) { idx in
            HStack {
                Text(verbatim: self.model.data[idx].name)
                Toggle(isOn: self.$model.data[idx].on_off) {
                    EmptyView()
                }
            }
        }
            Text("\(model.bool.description)").font(.largeTitle).padding()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

更新model.data时

@Published var data ....

其发布者在 ObservableObject 上调用 objectWillChange

接下来 SwiftUI 识别出 ObservedObject 需要 View 为 "updated"。重新创建视图,这将强制 model.bool.description 具有新值。

最后更新

更改这部分代码

struct ContentView: View {
    @ObservedObject var model = Model()
    var body: some View {
        NavigationView {
        List(0 ..< model.data.count) { idx in
            HStack {
                Text(verbatim: self.model.data[idx].name)
                Toggle(isOn: self.$model.data[idx].on_off) {
                    EmptyView()
                }
            }
            }.navigationBarTitle("List")
            .navigationBarItems(trailing:

                NavigationLink(destination: Text("next"), label: {
                    Text("Next")
                }).disabled(!model.bool)

            )
        }
    }
}

这正是你在更新的问题中所拥有的

在真实设备上尝试,否则 NavigationLink 只能使用一次(这是当前 Xcode 11.3.1 (11C504) 中众所周知的模拟器错误)。