如何在其他属性更改时添加可观察的 属性
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.isSelected
为 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()
}
}
}
有关信息,这是我正在努力实现的屏幕截图。 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) 中众所周知的模拟器错误)。
我有以下模型对象,我用它来填充一个 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.isSelected
为 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()
}
}
}
有关信息,这是我正在努力实现的屏幕截图。 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) 中众所周知的模拟器错误)。