【问题标题】:How to add an observable property when other properties change当其他属性发生变化时如何添加一个可观察的属性
【发布时间】:2020-02-15 19:17:48
【问题描述】:

我有以下模型对象,我用它来填充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
    }
}

只要任何measurement.isSelectedtrue,我希望hasSelection 属性为真。我猜想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()
        }
    }
}

有关信息,这是我正在尝试实现的屏幕截图。可选择项目的列表,带有一个导航链接,当一个或多个被选中时启用,当没有项目被选中时禁用。

【问题讨论】:

  • 您能否举例说明您是如何使用您的模型的?
  • @AshleyMills 让我们说按钮将 hasSelection 设置为 true。必须更改哪个 isSelected 值?这是 hasSelection 和 isSelected 之间的 1:N 链接。反之很简单,hasSelection 可以按需计算
  • @user3441734 hasSelection 理想情况下应该是只获取属性,如果measurement.isSelectedany 为真,则为真

标签: swiftui combine


【解决方案1】:

@user3441734 hasSelection 理想情况下应该是一个只能获取的属性,即 如果有任何measurement.isSelected 为真,则为真

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 ....

它的发布者在ObservableObject 上调用objectWillChange

Next 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)

            )
        }
    }
}

这正是您更新后的问题中的内容

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

【讨论】:

  • 在您的回答中,arr 的成员设置为 true 并发布其状态时,anyTrue 是否会更新?
  • @AshleyMills 已更新,包含您之前问题中的示例
  • @AshleyMills 为什么它的工作原理也在我更新的答案中进行了解释
  • 在我的示例中,我想使用hasSelection 属性来启用/禁用NavigationLink,所以它必须是Binding&lt;Bool&gt;,而不仅仅是简单的Bool
  • @AshleyMills 看到示例中的 Toggle,它使用 Binding,这正是您想要的,不是吗?
【解决方案2】:

目前您的代码的问题是即使您观察到measurements的更改,它们也不会在选择更新时更新,因为您将var isSelected: Binding&lt;Bool&gt;作为绑定。这意味着 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 。您可以根据自己的需要进行修改。

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

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

【讨论】:

  • 非常感谢您的回答 - 我已在问题中添加了屏幕截图和更多信息,以便您了解我想要实现的目标。
猜你喜欢
  • 2017-10-15
  • 1970-01-01
  • 1970-01-01
  • 2011-05-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多