You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI List视图无法根据状态变量动态更新显示/隐藏元素求助

问题:SwiftUI列表从配置页返回后未根据ObservableObject属性更新渲染

我在Watch App的SwiftUI中实现了一个包含NavigationLink的列表,每个列表项通过isVisible布尔值控制是否显示。另有一个配置页,通过Toggle组件设置各元素的显示/隐藏状态。

问题是:从配置页返回主视图后,主视图并未根据更新后的isVisible值重新渲染,if (hv.isVisible)的判断没有重新执行。我已确认布尔值已被修改,重启应用后列表能正确显示对应元素。

可复现代码

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)
                }
            }
        }
    }
}
}

原因分析

问题出在视图的更新触发逻辑上:

  • 主视图的List依赖DummyViewModel的@Published var values数组作为数据源,只有当数组本身发生变化(比如添加、删除元素)时,才会触发ViewModel的更新通知,进而刷新主视图。
  • 而每个DummyValueObject的isVisible是独立的@Published属性,修改它只会触发自身的ObservableObject更新,但主视图并没有监听单个DummyValueObject的变化,因此主视图不会重新执行if (v.isVisible)的判断。

解决方案

方案1:在列表中监听单个元素的变化(推荐,iOS13+)

在主视图的List闭包中,将元素声明为@ObservedObject,让视图监听每个DummyValueObject的属性变化:

// 修改DummyView中的List部分
List(model.values, id:\.type.rawValue) { @ObservedObject v in
    if v.isVisible {
        HStack {
            Image(systemName: v.symbolName)
            Text(v.type.rawValue)
        }
    }
}

如果你的项目基于iOS17+/watchOS10+,可以使用更轻量的@Bindable替代@ObservedObject:

List(model.values, id:\.type.rawValue) { @Bindable v in
    if v.isVisible {
        HStack {
            Image(systemName: v.symbolName)
            Text(v.type.rawValue)
        }
    }
}

方案2:让ViewModel监听元素属性变化

在DummyViewModel中订阅每个元素的isVisible变化,一旦变化就手动发送ViewModel的更新通知:

import Combine // 需要导入Combine框架

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")
]

private var cancellables = Set<AnyCancellable>()

init() {
    // 订阅每个元素的isVisible变化
    for value in values {
        value.$isVisible
            .sink { [weak self] _ in
                self?.objectWillChange.send() // 手动触发ViewModel更新
            }
            .store(in: &cancellables)
    }
}
}

这种方式不需要修改主视图的代码,ViewModel会统一管理所有元素的变化通知。

方案3:将元素改为值类型(Struct)

如果业务允许,把DummyValueObject从class改为struct,因为值类型的属性修改会触发数组的变化,进而触发ViewModel的@Published更新:

struct DummyValueObject : Identifiable {
let id = UUID() // 需要添加Identifiable的id属性
let type: ValueType
let symbolName: String
var isVisible: Bool = true {
    didSet {
        print("ValueObject type=\(type.rawValue), isVisible = \(isVisible)")
    }
}

init(type: ValueType, symbolName: String) {
    self.type = type
    self.symbolName = symbolName
}
}

同时需要修改配置页的Toggle绑定,因为struct是值类型,需要通过ViewModel的数组索引来修改:

// 修改DummySettingCellView
struct DummySettingCellView: View {
let index: Int
@ObservedObject var model: DummyViewModel

var body: some View {
    let value = model.values[index]
    HStack {
        Image(systemName: value.symbolName)
        Text(value.type.rawValue)
        Toggle("", isOn: $model.values[index].isVisible)
    }
}
}

// 修改DummySettingsView中的List
List(model.values.indices, id: \.self) { index in
    DummySettingCellView(index: index, model: model)
}

这种方式适合不需要将元素作为独立ObservableObject传递的场景。

内容的提问来源于stack exchange,提问作者imodude

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 13:32:01