当其他属性发生变化时如何添加一个可观察的属性

Posted

技术标签:

【中文标题】当其他属性发生变化时如何添加一个可观察的属性【英文标题】: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 为真,则为真 【参考方案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 模拟器似乎在导航方面存在一些问题,但我在物理设备上进行了测试,并且可以正常工作。

【讨论】:

非常感谢您的回答 - 我已在问题中添加了屏幕截图和更多信息,以便您了解我想要实现的目标。

以上是关于当其他属性发生变化时如何添加一个可观察的属性的主要内容,如果未能解决你的问题,请参考以下文章

如何观察 firebase.auth().currentUser.displayName 和其他属性的变化?

观察 NSManagedObject 属性的变化:如何避免循环?

KVO的使用

Knockout.js:当可观察数组中的值发生变化时触发计算的最佳方法是啥

Angular2 中组件属性变化的可观察性

VueJS 观察属性和改变数据