SwiftUI中Picker值更新后如何触发ObservableObject刷新数据
SwiftUI 选择Picker月份自动刷新考勤数据实现方案
你的现有代码存在两个核心缺陷,导致选择月份后无法触发刷新:
- 网络请求逻辑写在了
ObservableObject子类的构造器init中,仅会在实例初始化时执行一次,后续参数变化无法自动触发请求 - 视图中未定义
selectedMonth状态变量,Picker选择的值没有绑定到可触发视图更新/逻辑执行的状态上
第一步:重构ObservableObject类
把网络请求从构造器中抽离为独立可调用的方法,同时保存请求所需的公共参数,方便后续参数变化时重复调用请求:
// 建议遵循Swift命名规范,类名使用大驼峰,这里保留原类名做演示 class getAttendanceRecord2: ObservableObject { @Published var attlist = [EAttendance]() // 保存请求固定参数 private let id: String // 保存当前选中的请求参数 private var currentMonth: String private var currentYear: String // 可选:用于保存请求任务,切换月份时取消上一个未完成请求,避免数据错乱 private var dataTask: URLSessionDataTask? init(id: String, month: String, year: String) { self.id = id self.currentMonth = month self.currentYear = year // 初始化时第一次拉取数据 fetchAttendanceData() } // 抽离独立的网络请求方法,外部可随时调用刷新 func fetchAttendanceData(month: String? = nil, year: String? = nil) { // 如果传入了新的月份/年份,更新当前存储的参数 if let newMonth = month { currentMonth = newMonth } if let newYear = year { currentYear = newYear } // 取消上一个未完成的请求 dataTask?.cancel() guard let url = URL(string: "http://superteclabs.com/apis2/AttendanceRecord2.php") else { return } var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Accept") let formdata = "id=\(id)&month=\(currentMonth)&year=\(currentYear)".data(using: .utf8)! request.httpBody = formdata dataTask = URLSession.shared.dataTask(with: request) { [weak self] response_data, response, error in guard let self = self else { return } // 忽略取消请求的报错 if let error = error, (error as NSError).code != NSURLErrorCancelled { print("请求错误: \(error)") return } guard let data = response_data, let httpResponse = response as? HTTPURLResponse, 200..<299 ~= httpResponse.statusCode else { print("响应错误") return } do { let decodedData = try JSONDecoder().decode([EAttendance].self, from:data) DispatchQueue.main.async { self.attlist = decodedData } } catch { print("数据解析错误: \(error)") } } dataTask?.resume() } }
第二步:修改视图层逻辑
补全Picker绑定的状态变量,通过onChange修饰符监听月份选择的变化,触发ViewModel的刷新方法:
struct AttendanceRecord: View { @ObservedObject var item_data: getAttendanceRecord2 // 补全Picker绑定的选中月份状态,初始值设为当前月份 @State private var selectedMonth: String let months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November","December"] // 保存用户id,方便后续传参 private let userId: String init(id: String) { self.userId = id let currentMonth = getMonth() // 初始化状态变量 self._selectedMonth = State(initialValue: currentMonth) self.item_data = getAttendanceRecord2(id: id, month: currentMonth, year: getYear()) } var body: some View { VStack { Picker(selection: $selectedMonth, label: Text("Months")) { ForEach(months, id: \.self) { month in Text(month) } } .pickerStyle(MenuPickerStyle()) // 监听选中月份的变化,触发数据刷新 .onChange(of: selectedMonth) { newMonth in // 如果你的接口需要数字格式的月份,在这里做月份字符串到数字的映射即可 item_data.fetchAttendanceData(month: newMonth) } Text("You selected: \(selectedMonth)") // 原有考勤列表渲染逻辑写在这里,直接用item_data.attlist即可 } } }
可选优化点
- 类名、方法名建议遵循Swift官方命名规范,使用大驼峰命名类(例如将
getAttendanceRecord2改为AttendanceRecordViewModel),小驼峰命名方法和变量 - 可在ViewModel中新增
@Published var isLoading: Bool属性,请求开始时置为true、结束后置为false,视图层可根据该状态展示加载指示器,优化交互 - 如果年份也需要选择,只需要仿照月份的逻辑加一个Picker和对应的状态监听,调用刷新方法时传入新年份即可
- 生产环境建议将接口地址、请求逻辑抽离到独立的网络层,不要直接写在ViewModel中
内容的提问来源于stack exchange,提问作者Samin Jilani
相关产品推荐
相关产品推荐

