英文:
List view not hiding/unhiding elements based on a state variable
问题
我明白你只需要代码部分的中文翻译。以下是你提供的代码的翻译:
// 我有一个 NavigationLinks 的列表。每个 NavigationLink 都被一个 "isVisible" 布尔值检查包装。
// 分开,一个配置页面有 Toggle,用于设置项目的可见性。
// 问题是,当我从配置页面返回时,主视图不会根据所有项目上的新可见性布尔值更新自身。即 "if (hv.isVisible)" 不会被重新评估。
// 我知道这些布尔值被持久化了。当我重新启动应用程序时,列表中显示了正确的项目
@ObservedObject fileprivate var viewModel = HealthValuesViewModel.instance
// 在这里,values 被定义为
// @Published var values: [HealthValue] = []
// 然后在 init() 中填充
// HealthValue 是一个 ObservableObject 类,拥有属性
// @Published var isVisible: Bool
List(viewModel.values, id:\.type.rawValue) { hv in
    if (hv.isVisible) {
        NavigationLink(destination: hv.view) {
            HealthValueCellView(healthValue: hv )
        }
    }
}
// 我已经尝试了各种 @ObervedObject、@Published 等的组合。
// 也尝试确保列表中有一个 id。
// 非常感谢任何帮助或建议。
// 更新:
// 正如建议的,我创建了一个可重现的示例。希望这样更清晰
// DummyView 是主视图 - 具有设置按钮和 3 个对象的列表。
// DummySettingsView 是子视图,用于在列表中切换项目的显示/隐藏状态。
// DummySettingsCellView 是设置视图列表中的元素,具有切换按钮。
import SwiftUI
enum ValueType: String {
    case type1 = "T1"
    case type2 = "T2"
    case type3 = "T3"
}
class DummyValueObject : Identifiable, ObservableObject {
    let type: ValueType
    let symbolName: String
    @Published var isVisible: Bool = true {
        didSet {
            print("ValueObject type=\(type.rawValue), isVisible = \(isVisible)")
        }
    }
    init(type: ValueType, symbolName: String) {
        self.type = type
        self.symbolName = symbolName
    }
}
class DummyViewModel : ObservableObject {
    static let instance = DummyViewModel()
    @Published var values: [DummyValueObject] = [
        DummyValueObject(type: .type1, symbolName: "heart.fill"),
        DummyValueObject(type: .type2, symbolName: "wind"),
        DummyValueObject(type: .type3, symbolName: "scalemass.fill")
    ]
}
struct DummySettingCellView: View {
    @ObservedObject var value: DummyValueObject
    var body: some View {
        HStack {
            Image(systemName: value.symbolName)
            Text(value.type.rawValue)
            Toggle("", isOn: $value.isVisible)
        }
    }
}
struct DummySettingsView: View {
    @ObservedObject var model = DummyViewModel.instance
    var body: some View {
        VStack {
            List(model.values) { v in
                DummySettingCellView(value: v)
            }
        }
    }
}
struct DummyView: View {
    @ObservedObject var model = DummyViewModel.instance
    var body: some View {
        VStack {
            NavigationLink(destination: DummySettingsView()) {
                Image(systemName: "wrench")
            }
            Spacer()
            List(model.values, id:\.type.rawValue) { v in
                if (v.isVisible) {
                    HStack {
                        Image(systemName: v.symbolName)
                        Text(v.type.rawValue)
                    }
                }
            }
        }
    }
}
希望这对你有所帮助。
英文:
I've got a List of NavigationLinks. Each NavigationLink is wrapped by a "isVisible" boolean check.
Separately, a config page has Toggle's that set the visibility on or off.
The problem is when I come back from the config page, the main view doesnt update itself based on the new visibility boolean on all the items. i.e. the "if (hv.isVisible)" isn't reevaluated.
I know the booleans are being persisted. When I restart the app, the correct items in the list are shown
@ObservedObject fileprivate var viewModel = HealthValuesViewModel.instance
// in here values is defined as
// @Published var values: [HealthValue] = []
// which is then populated in init()
// The HealthValue is an ObservableObject class and has property
// @Published var isVisible: Bool
List(viewModel.values, id:\.type.rawValue) { hv in
if (hv.isVisible) {
NavigationLink(destination: hv.view) {
HealthValueCellView(healthValue: hv )
}
}
}
I've been playing with all sorts of combinations of @ObervedObject, @Published etc.
Also tried making sure I have an id in the List.
Any help or suggestions greatly appreciated.
UPDATE:
As suggested, I've created a reproducible example. Hope this makes things clearer
// DummyView is the main view - has a settings button and a list of 3 objects.
// DummySettingsView is the sub-view where the items in the list are toggled on/off
// DummySettingsCellView are the elements in the settings view list that have the toggle
import SwiftUI
enum ValueType: String {
case type1 = "T1"
case type2 = "T2"
case type3 = "T3"
}
class DummyValueObject : Identifiable, ObservableObject {
let type: ValueType
let symbolName: String
@Published var isVisible: Bool = true {
didSet {
print("ValueObject type=\(type.rawValue), isVisible = \(isVisible)")
}
}
init(type: ValueType, symbolName: String) {
self.type = type
self.symbolName = symbolName
}
}
class DummyViewModel : ObservableObject {
static let instance = DummyViewModel()
@Published var values: [DummyValueObject] = [
DummyValueObject(type: .type1, symbolName: "heart.fill"),
DummyValueObject(type: .type2, symbolName: "wind"),
DummyValueObject(type: .type3, symbolName: "scalemass.fill")
]
}
struct DummySettingCellView: View {
@ObservedObject var value: DummyValueObject
var body: some View {
HStack {
Image(systemName: value.symbolName)
Text(value.type.rawValue)
Toggle("", isOn: $value.isVisible)
}
}
}
struct DummySettingsView: View {
@ObservedObject var model = DummyViewModel.instance
var body: some View {
VStack {
List(model.values) { v in
DummySettingCellView(value: v)
}
}
}
}
struct DummyView: View {
@ObservedObject var model = DummyViewModel.instance
var body: some View {
VStack {
NavigationLink(destination: DummySettingsView()) {
Image(systemName: "wrench")
}
Spacer()
List(model.values, id:\.type.rawValue) { v in
if (v.isVisible) {
HStack {
Image(systemName: v.symbolName)
Text(v.type.rawValue)
}
}
}
}
}
}
I should probably have mentioned that this is a watch app - but I guess it shouldnt make a difference.
答案1
得分: 1
尝试这种方法,使用struct替代嵌套的ObservableObject来定义DummyValueObject,并使用@Binding var value: DummyValueObject将其传递给DummySettingCellView。
此外,请注意使用List(model.values.filter{$0.isVisible})而不是使用if (v.isVisible) {...},以确保视图能够正确刷新。
struct ContentView: View {
    var body: some View {
        NavigationStack {
            DummyView()
        }
    }
}
struct DummyView: View {
    @StateObject var model = DummyViewModel()
    
    var body: some View {
        VStack {
            NavigationLink(destination: DummySettingsView(model: model)) {
                Image(systemName: "wrench")
            }
            Spacer()
            List(model.values.filter{$0.isVisible}) { v in
                HStack {
                    Image(systemName: v.symbolName)
                    Text(v.type.rawValue)
                }
            }
        }
    }
}
struct DummySettingsView: View {
    @ObservedObject var model: DummyViewModel
    
    var body: some View {
        VStack {
            List($model.values) { $v in
                DummySettingCellView(value: $v)
            }
        }
    }
}
class DummyViewModel: ObservableObject {
    @Published var values: [DummyValueObject] = [
        DummyValueObject(type: .type1, symbolName: "heart.fill"),
        DummyValueObject(type: .type2, symbolName: "wind"),
        DummyValueObject(type: .type3, symbolName: "scalemass.fill")
    ]
}
struct DummyValueObject: Identifiable {
    let id = UUID()
    let type: ValueType
    let symbolName: String
    var isVisible: Bool = true {
        didSet {
            print("--> DummyValueObject type=\(type.rawValue), isVisible = \(isVisible)")
        }
    }
    init(type: ValueType, symbolName: String) {
        self.type = type
        self.symbolName = symbolName
    }
}
struct DummySettingCellView: View {
    @Binding var value: DummyValueObject
    var body: some View {
        HStack {
            Image(systemName: value.symbolName)
            Text(value.type.rawValue)
            Toggle("", isOn: $value.isVisible)
            Spacer()
        }
    }
}
enum ValueType: String {
    case type1 = "T1"
    case type2 = "T2"
    case type3 = "T3"
}
此外,您可以使用嵌套的ObservableObjects,但这可能会引入其他问题。在这种情况下,仍然应在DummySettingCellView中使用@Binding var value: DummyValueObject来解决当前问题。不过,请注意,不要使用@ObservedObject var model = DummyViewModel.instance,这是不正确的。@StateObject var model = DummyViewModel() 在DummyView 中应该是您应用程序的唯一数据源,将此模型传递给需要它的视图。请查看Managing model data in your app链接,以获取有关如何管理应用程序数据的官方示例。
英文:
Try this approach, using a struct for the DummyValueObject instead of a nested ObservableObject, and a @Binding var value: DummyValueObject to pass it into DummySettingCellView
Also note, the important use of List(model.values.filter{$0.isVisible}) instead of using
if (v.isVisible) {...}, to allow the view to refresh properly.
struct ContentView: View {
var body: some View {
NavigationStack {  // <-- here
DummyView()
}
}
}
struct DummyView: View {
@StateObject var model = DummyViewModel()  // <-- here
var body: some View {
VStack {
NavigationLink(destination: DummySettingsView(model: model)) { // <-- here
Image(systemName: "wrench")
}
Spacer()
List(model.values.filter{$0.isVisible}) { v in // <-- here important
HStack {
Image(systemName: v.symbolName)
Text(v.type.rawValue)
}
}
}
}
}
struct DummySettingsView: View {
@ObservedObject var model: DummyViewModel  // <-- here
var body: some View {
VStack {
List($model.values) { $v in  // <-- here
DummySettingCellView(value: $v)  // <-- here
}
}
}
}
class DummyViewModel : ObservableObject {
@Published var values: [DummyValueObject] = [
DummyValueObject(type: .type1, symbolName: "heart.fill"),
DummyValueObject(type: .type2, symbolName: "wind"),
DummyValueObject(type: .type3, symbolName: "scalemass.fill")
]
}
struct DummyValueObject : Identifiable {  // <-- here
let id = UUID()  // <-- here
let type: ValueType
let symbolName: String
var isVisible: Bool = true {
didSet {
print("---> DummyValueObject type=\(type.rawValue), isVisible = \(isVisible)")
}
}
init(type: ValueType, symbolName: String) {
self.type = type
self.symbolName = symbolName
}
}
struct DummySettingCellView: View {
@Binding var value: DummyValueObject   // <-- here
var body: some View {
HStack {
Image(systemName: value.symbolName)
Text(value.type.rawValue)
Toggle("", isOn: $value.isVisible)
Spacer()
}
}
}
enum ValueType: String {
case type1 = "T1"
case type2 = "T2"
case type3 = "T3"
}
EDIT-1
You can of course use (not recommended), your nested ObservableObjects.
In that case, follow this approach, still using @Binding var value: DummyValueObject in DummySettingCellView, it will fix your current problem, but may create others
in the other parts of your code, if not now, then later on.
Note, you should not use @ObservedObject var model = DummyViewModel.instance thinking
you can use it as a singleton in different views, this is not correct.
The @StateObject var model = DummyViewModel() in DummyView should be the
only source of truth for your app. Pass this model around to the views that need it.
Have a look at this link, it gives you some good official examples of how to manage data in your app
Managing model data in your app
class DummyViewModel : ObservableObject {
//static let instance = DummyViewModel()  <--- NOT THIS
@Published var values: [DummyValueObject] = [
DummyValueObject(type: .type1, symbolName: "heart.fill"),
DummyValueObject(type: .type2, symbolName: "wind"),
DummyValueObject(type: .type3, symbolName: "scalemass.fill")
]
}
struct DummySettingCellView: View {
@Binding var value: DummyValueObject  // <-- here
var body: some View {
HStack {
Image(systemName: value.symbolName)
Text(value.type.rawValue)
Toggle("", isOn: $value.isVisible)
}
}
}
struct DummySettingsView: View {
@ObservedObject var model: DummyViewModel // <-- here
var body: some View {
VStack {
List($model.values) { $v in         // <-- here $
DummySettingCellView(value: $v) // <-- here
}
}
}
}
struct DummyView: View {
@StateObject var model = DummyViewModel()  // <-- here
var body: some View {
VStack {
NavigationLink(destination: DummySettingsView(model: model)) {  // <-- here
Image(systemName: "wrench")
}
Spacer()
// -- here --
List(model.values.filter{$0.isVisible}, id:\.type.rawValue) { v in
HStack {
Image(systemName: v.symbolName)
Text(v.type.rawValue)
}
}
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论