我如何使用SwiftUI和Combine来使最高视图的状态取决于其包含的SubView的状态,而该状态取决于其他条件,而该标准取决于其所包含的SubSubView?
我具有以下View层次结构:V1包含V2,V2包含V3。
V1是特定设置视图V2的常规“包装”,通常是带有装饰性的“包装”,并带有“保存”按钮。类型为Bool
的按钮的禁用状态应取决于V2的可保存性状态。
V2是特定的设置视图。 V2的哪种类型(显示的具体设置)可能会有所不同,具体取决于程序的其余部分。保证能够确定其保存能力。它包含一个Toggle和一个V3,一个MusicPicker。 V2的可保存性取决于处理V3的选择状态及其切换状态的条件。
V3是一般的“ MusicPicker”视图,其选择状态为Int?
。它可以与任何父级一起使用,可以双向传递其选择状态。
通常应使用Binding
在2个视图之间来回通信。这样,V1和V2以及V2和V3之间可能存在绑定。但是,据我所知/理解,V2无法/不应对V3的绑定的值更改作出反应,并将其(连同其他条件)传达回V1。我可以使用ObservableObject
与V1和V2共享保存能力,并与V2和V3共享选择状态,但是我不清楚如何将V3的ObservableObject更改与其他条件集成以设置V1的ObservableObject
@State
和@Binding
/* V1 */
struct SettingsView: View {
@State var saveable = false
var body: some View {
VStack {
Button(action: saveAction){
Text("Save")
}.disabled(!saveable)
getSpecificV2(saveable: $saveable)
}
}
func getSpecificV2(saveable: Binding<Bool>) -> AnyView {
// [Determining logic...]
return AnyView(SpecificSettingsView(saveable: saveable))
}
func saveAction(){
// More code...
}
}
/* V2 */
struct SpecificSettingsView: View {
@Binding var saveable: Bool
@State var toggled = false
@State var selectedValue: Int?
var body: some View {
Form {
Toggle("Toggle me", isOn: $toggled)
CustomPicker(selected: $selectedValue)
}
}
func someCriteriaProcess() -> Bool {
if let selected = selectedValue {
return (selected == 5)
} else {
return toggled
}
}
}
/* V3 */
struct CustomPicker: View {
@Binding var selected: Int?
var body: some View {
List {
Text("None")
.onTapGesture {
self.selected = nil
}.foregroundColor(selected == nil ? .blue : .primary)
Text("One")
.onTapGesture {
self.selected = 1
}.foregroundColor(selected == 1 ? .blue : .primary)
Text("Two")
.onTapGesture {
self.selected = 2
}.foregroundColor(selected == 2 ? .blue : .primary)
}
}
}
在此示例代码中,我实际上需要使saveable
依赖于someCriteriaProcess()
。
ObservableObject
为响应Tobias的回答,可能的替代方法是使用ObservableObject
s。
/* V1 */
class SettingsStore: ObservableObject {
@Published var saveable = false
}
struct SettingsView: View {
@ObservedObject var store = SettingsStore()
var body: some View {
VStack {
Button(action: saveAction){
Text("Save")
}.disabled(!store.saveable)
getSpecificV2()
}.environmentObject(store)
}
func getSpecificV2() -> AnyView {
// [Determining logic...]
return AnyView(SpecificSettingsView())
}
func saveAction(){
// More code...
}
}
/* V2 */
struct SpecificSettingsView: View {
@EnvironmentObject var settingsStore: SettingsStore
@ObservedObject var pickerStore = PickerStore()
@State var toggled = false
@State var selectedValue: Int?
var body: some View {
Form {
Toggle("Toggle me", isOn: $toggled)
CustomPicker(store: pickerStore)
}.onReceive(pickerStore.objectWillChange){ selected in
print("Called for selected: \(selected ?? -1)")
self.settingsStore.saveable = self.someCriteriaProcess()
}
}
func someCriteriaProcess() -> Bool {
if let selected = selectedValue {
return (selected == 5)
} else {
return toggled
}
}
}
/* V3 */
class PickerStore: ObservableObject {
public let objectWillChange = PassthroughSubject<Int?, Never>()
var selected: Int? {
willSet {
objectWillChange.send(newValue)
}
}
}
struct CustomPicker: View {
@ObservedObject var store: PickerStore
var body: some View {
List {
Text("None")
.onTapGesture {
self.store.selected = nil
}.foregroundColor(store.selected == nil ? .blue : .primary)
Text("One")
.onTapGesture {
self.store.selected = 1
}.foregroundColor(store.selected == 1 ? .blue : .primary)
Text("Two")
.onTapGesture {
self.store.selected = 2
}.foregroundColor(store.selected == 2 ? .blue : .primary)
}
}
}
使用onReceive()
附件,我尝试对PickerStore
的任何更改做出反应。尽管该操作会触发并且调试可以正确打印,但不会显示UI更改。
在这种情况下,最合适的方法是使用SwiftUI和Combine对V3的变化做出反应,用V2中的其他状态进行处理以及相应地更改V1的状态?
答案 0 :(得分:0)
由于SwiftUI不支持刷新嵌套的ObservableObject内的更改视图,因此您需要手动执行此操作。我在此处发布了有关如何执行此操作的解决方案:
https://stackoverflow.com/a/58996712/12378791(例如,使用ObservedObject)
https://stackoverflow.com/a/58878219/12378791(例如,使用EnvironmentObject)
答案 1 :(得分:0)
我想出了一种具有相同最终结果的可行方法,这可能对其他人有用。但是,它不能按照我在问题中所要求的方式传递数据,但是SwiftUI在任何情况下似乎都不适合这样做。
由于V2是“中间”视图,可以正确访问选择和保存能力这两个重要状态,所以我意识到我可以使V2成为父视图,并让V1最初是“父”视图成为子视图改为接受@ViewBuilder
内容。此示例不适用于所有情况,但适用于我的情况。
一个有效的示例如下。
/* V2 */
struct SpecificSettingsView: View {
@State var toggled = false
@State var selected: Int?
var saveable: Bool {
return someCriteriaProcess()
}
var body: some View {
SettingsView(isSaveable: self.saveable, onSave: saveAction){
Form {
Toggle("Toggle me", isOn: self.$toggled)
CustomPicker(selected: self.$selected)
}
}
}
func someCriteriaProcess() -> Bool {
if let selected = selected {
return (selected == 2)
} else {
return toggled
}
}
func saveAction(){
guard saveable else { return }
// More code...
}
}
/* V1 */
struct SettingsView<Content>: View where Content: View {
var content: () -> Content
var saveAction: () -> Void
var saveable: Bool
init(isSaveable saveable: Bool, onSave saveAction: @escaping () -> Void, @ViewBuilder content: @escaping () -> Content){
self.saveable = saveable
self.saveAction = saveAction
self.content = content
}
var body: some View {
VStack {
// More decoration
Button(action: saveAction){
Text("Save")
}.disabled(!saveable)
content()
}
}
}
/* V3 */
struct CustomPicker: View {
@Binding var selected: Int?
var body: some View {
List {
Text("None")
.onTapGesture {
self.selected = nil
}.foregroundColor(selected == nil ? .blue : .primary)
Text("One")
.onTapGesture {
self.selected = 1
}.foregroundColor(selected == 1 ? .blue : .primary)
Text("Two")
.onTapGesture {
self.selected = 2
}.foregroundColor(selected == 2 ? .blue : .primary)
}
}
}
我希望这对其他人有帮助。
答案 2 :(得分:0)
在问题本身带有方法ObservableObject
的前提下发布此答案。
仔细看。尽快的代码:
.onReceive(pickerStore.objectWillChange){ selected in
print("Called for selected: \(selected ?? -1)")
self.settingsStore.saveable = self.someCriteriaProcess()
}
在SpecificSettingsView
中运行,settingsStore
将要更改,从而触发父SettingsView
刷新其关联的视图组件。这意味着func getSpecificV2() -> AnyView
将返回SpecificSettingsView
对象,该对象又将再次实例化PickerStore
。因为,
作为值类型的SwiftUI视图(因为它们是结构),例如,如果视图是由父视图重新创建的,则不会将对象保留在其视图范围内。因此,最好通过引用传递这些可观察对象,并具有某种容器视图或Holder类,该容器视图或容器类将实例化并引用这些对象。如果该视图是该对象的唯一所有者,并且由于其父视图由SwiftUI更新而重新创建了该视图,则您将丢失
ObservedObject
的当前状态。
(上面的Read More)
如果仅在视图层次结构(可能是最终父级)中将PickerStore
的实例化推到更高位置,您将获得预期的行为。
struct SettingsView: View {
@ObservedObject var store = SettingsStore()
@ObservedObject var pickerStore = PickerStore()
. . .
func getSpecificV2() -> AnyView {
// [Determining logic...]
return AnyView(SpecificSettingsView(pickerStore: pickerStore))
}
. . .
}
struct SpecificSettingsView: View {
@EnvironmentObject var settingsStore: SettingsStore
@ObservedObject var pickerStore: PickerStore
. . .
}
注意:我将项目上传到了远程存储库here