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
相关产品推荐
相关产品推荐

