当其他属性发生变化时如何添加一个可观察的属性
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.isSelected
是true
,我希望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 可以按需计算 @user3441734hasSelection
理想情况下应该是只获取属性,如果measurement.isSelected
的any 为真,则为真
【参考方案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<Bool>
,而不仅仅是简单的Bool
@AshleyMills 看到示例中的 Toggle,它使用 Binding目前您的代码的问题是即使您观察到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 。您可以根据自己的需要进行修改。
如果你想支持多选,你可以使用一组选定的id来代替。
另外,ios 模拟器似乎在导航方面存在一些问题,但我在物理设备上进行了测试,并且可以正常工作。
【讨论】:
非常感谢您的回答 - 我已在问题中添加了屏幕截图和更多信息,以便您了解我想要实现的目标。以上是关于当其他属性发生变化时如何添加一个可观察的属性的主要内容,如果未能解决你的问题,请参考以下文章
如何观察 firebase.auth().currentUser.displayName 和其他属性的变化?
观察 NSManagedObject 属性的变化:如何避免循环?