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

SwiftUI报错:ContentView类型无法使用实例成员enableFilter、filterDate

报错原因

这个错误的本质是:@State属性的默认值初始化闭包,是在ContentView实例完成创建之前运行的。此时enableFilter、filterDate这类实例属性还没完成初始化,你在给people赋默认值的闭包里直接访问这两个属性,Swift就会提示无法在类型上访问实例成员。
之前改成@State static var的方案不可行,是因为静态属性归类型所有,不归视图实例所有,@State无法跟踪静态属性的变化,修改静态值不会触发视图刷新,自然没法通过筛选控件更新状态。

修复方案
  • 移除people初始化闭包里的筛选逻辑,初始化阶段只负责从UserDefaults读取全量原始生日数据,不做筛选操作。
  • 新增专门的计算属性返回筛选后的列表,这个属性会自动跟踪enableFilter、filterDate、people三个状态的变化,任意值改动时会自动重新计算结果,同步触发列表刷新。
  • 把列表遍历的数据源替换为筛选后的计算属性,不需要直接绑定原数组。
  • 补全原代码遗漏的筛选面板弹出逻辑,原代码中定义了showFilter状态和filterView但没有挂载到视图层级,用户无法操作筛选控件。
修复后可直接运行的完整代码
import SwiftUI

// Swift类型名统一首字母大写,符合编码规范
struct Person: Codable, Hashable, Identifiable {
    var id: Int
    var Birthday: Date
    var BirthdayString: String
}

func filter(date: Date) -> String {
    let dateComponents = Calendar.current.dateComponents([.year, .month, .day], from: date)
    let dateComponentsString: String = "\(dateComponents.day!)/\(dateComponents.month!)/\(dateComponents.year!)"
    return dateComponentsString
}

struct ContentView: View {
    // 初始化时仅加载全量原始数据
    @State var people: [Person] = {
        guard let data = UserDefaults.standard.data(forKey: "people") else { return [] }
        if let json = try? JSONDecoder().decode([Person].self, from: data) {
            return json
        }
        return []
    }()

    @State var filterDate: Date = Date()
    @State var enableFilter: Bool = false
    @State var showFilter: Bool = false
    @State var newPersonDate: Date = Date()
    
    // 自动跟踪状态变化返回筛选结果
    var filteredPeople: [Person] {
        if enableFilter {
            let targetDateStr = filter(date: filterDate)
            return people.filter { $0.BirthdayString.contains(targetDateStr) }
        } else {
            return people
        }
    }

    var body: some View {
        NavigationView {
            VStack {
                HStack {
                    DatePicker(selection: $newPersonDate, displayedComponents: .date, label: {Text("生日")}).padding()
                    Button(action: {didTapAddTask()}, label: {Text("添加")}).padding()
                }
                
                List {
                    ForEach(filteredPeople) { person in
                        Text(person.Birthday.formatted(date: .long, time: .omitted))
                    }
                }
            }
            .navigationTitle("人员生日列表")
            // 补全筛选按钮和面板弹出逻辑
            .toolbar {
                Button("筛选") {
                    showFilter.toggle()
                }
            }
            .sheet(isPresented: $showFilter) {
                filterView
                    .presentationDetents([.height(250)])
            }
        }
    }
    
    var filterView: some View {
        VStack {
            DatePicker(selection: $filterDate, displayedComponents: .date, label: {Text("筛选日期")}).padding()
            Toggle(isOn: $enableFilter, label: {Text("启用筛选")}).padding()
            Spacer()
        }
    }
    
    func didTapAddTask() {
        let id = people.reduce(0) { max($0, $1.id) } + 1
        people.insert(Person(id: id, Birthday: newPersonDate, BirthdayString: filter(date: newPersonDate)), at: 0)
        newPersonDate = Date()
        save()
    }
                                                 
    func save() {
        guard let data = try? JSONEncoder().encode(people) else { return }
        UserDefaults.standard.set(data, forKey: "people")
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
额外优化点
  • 修正了原代码中类型名person小写的问题,避免和实例变量命名混淆。
  • 日期选择器限定只选日期,屏蔽时间选择组件,更符合生日记录的使用场景。
  • 日期显示用系统格式化方法输出可读性更高的格式,替代直接打印Date对象的默认输出。
  • 筛选面板设置了固定高度,弹出时不会占用全屏,操作体验更好。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 04:18:15