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

SwiftUI:结合@Published变量在数组中使用自定义函数的问题

问题描述

需求

  • 实现基于ViewModel的列表,通过自定义函数生成逐年薪资涨幅及年龄变化数据
  • 用户在ProfileFormView修改birthdate、hiredate时,列表需同步更新Age和Year group字段

遇到的问题

  1. 尝试将自定义生成数据的函数融入ViewModel的userDataList数组时出现错误
  2. 改为将userDataList设为计算属性后,遭遇初始化错误
  3. 移除计算属性上的@Published(因@Published不支持计算属性)后,WorkerView中出现只读属性赋值的错误

解决方案

核心思路

通过监听birthdate和hiredate的变化,自动触发自定义函数更新列表数据,同时保留userDataList为可观察的存储属性,既解决响应式更新问题,又避免计算属性的只读限制。

具体实现

1. 重构WorkerViewModel

import Combine
import SwiftUI

// 假设Worker结构遵循Identifiable
struct Worker: Identifiable {
    let id = UUID()
    var yearGroup: String
    var age: Int
    var salary: Double
    // 其他自定义字段
}

class WorkerViewModel: ObservableObject {
    @Published var birthdate: Date = Date()
    @Published var hiredate: Date = Date()
    @Published var userDataList: [Worker] = []
    
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        // 初始化生成初始数据
        refreshUserDataList()
        
        // 监听出生日期/入职日期变化,自动刷新列表
        $birthdate
            .combineLatest($hiredate)
            .debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
            .sink { [weak self] _, _ in
                self?.refreshUserDataList()
            }
            .store(in: &cancellables)
    }
    
    // 自定义生成逐年数据的核心函数
    private func refreshUserDataList() {
        // 示例:生成近5年的薪资/年龄数据,可根据需求调整时间范围
        userDataList = (0..<5).map { yearOffset in
            let targetYear = Calendar.current.component(.year, from: Date()) - yearOffset
            let age = calculateAge(for: birthdate, inYear: targetYear)
            let salary = calculateSalary(for: hiredate, inYear: targetYear)
            
            return Worker(
                yearGroup: "\(targetYear)年度",
                age: age,
                salary: salary
            )
        }
    }
    
    // 辅助计算年龄
    private func calculateAge(for birthdate: Date, inYear targetYear: Int) -> Int {
        let birthYear = Calendar.current.component(.year, from: birthdate)
        return targetYear - birthYear
    }
    
    // 辅助计算薪资涨幅
    private func calculateSalary(for hiredate: Date, inYear targetYear: Int) -> Double {
        let hireYear = Calendar.current.component(.year, from: hiredate)
        let workingYears = max(0, targetYear - hireYear)
        let baseSalary = 5000.0
        return baseSalary * (1.0 + Double(workingYears) * 0.12) // 示例:每年涨12%
    }
}

2. 修正WorkerView(只读展示数据)

struct WorkerView: View {
    @ObservedObject var viewModel: WorkerViewModel
    
    var body: some View {
        List(viewModel.userDataList) { worker in
            VStack(alignment: .leading, spacing: 4) {
                Text(worker.yearGroup)
                    .font(.headline)
                Text("年龄:\(worker.age)岁")
                Text("薪资:\(worker.salary, specifier: "%.2f")元")
                    .foregroundColor(.secondary)
            }
            .padding(.vertical, 8)
        }
    }
}

3. 保持ProfileFormView的绑定逻辑

struct ProfileFormView: View {
    @ObservedObject var viewModel: WorkerViewModel
    
    var body: some View {
        Form {
            DatePicker(
                "出生日期",
                selection: $viewModel.birthdate,
                displayedComponents: .date
            )
            DatePicker(
                "入职日期",
                selection: $viewModel.hiredate,
                displayedComponents: .date
            )
        }
        .navigationTitle("个人信息")
    }
}

问题根源解析

  1. 自定义函数融入数组出错:直接在数组初始化中调用函数无法响应birthdate/hiredate的变化,数据只会生成一次,不会同步更新
  2. 计算属性初始化错误:计算属性是动态推导的,不能在初始化阶段赋值,且@Published不支持修饰计算属性
  3. 只读属性赋值错误:计算属性默认只读,视图试图对其赋值必然报错,而我们的场景中视图只需要展示数据,无需修改数据

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 21:01:07