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

SwiftUI中Picker值更新后如何触发ObservableObject刷新数据

SwiftUI 选择Picker月份自动刷新考勤数据实现方案

你的现有代码存在两个核心缺陷,导致选择月份后无法触发刷新:

  1. 网络请求逻辑写在了ObservableObject子类的构造器init中,仅会在实例初始化时执行一次,后续参数变化无法自动触发请求
  2. 视图中未定义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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 22:42:22