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

时间:2020-02-15 19:17:48

标签: swiftui combine

我有以下模型对象,可用于为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 = $0 })
        }
    }

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

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

只要任何hasSelectionmeasurement.isSelected,我都希望true属性为true。我猜测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()
        }
    }
}

有关信息,这是我要实现的屏幕截图。可选项目的列表,其中的导航链接在选择一个或多个项目时启用,在未选择任何项目时禁用。

enter image description here

2 个答案:

答案 0 :(得分:1)

此刻,您的代码存在的问题是,即使您观察到measurements的更改,由于选择将var isSelected: Binding<Bool>声明为绑定对象,因此在选择内容更新时它们也不会得到更新。这意味着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
    }
}

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

如果要支持多选,则可以改用Set of selected ids。

此外,似乎iOS模拟器在导航方面存在一些问题,但我在物理设备上进行了测试,并且可以正常工作。

答案 1 :(得分:1)

@ user3441734 hasSelection在理想情况下应为get only属性,即 如果Measurement.isSelected中的任何一项为true,则为true

struct Data {
    var bool: Bool
}
class Model: ObservableObject {
    @Published var arr: [Data] = []
    var anyTrue: Bool {
        arr.map{$0.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 {$0.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 ....

其发布者在objectWillChange上致电ObservableObject

接下来,SwiftUI认识到ObservedObject需要“更新”视图。视图将被重新创建,这将强制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)

            )
        }
    }
}

确切地说,您在更新的问题中拥有什么 enter image description here

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