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

SwiftUI中ViewModel数据无法绑定到Table View的问题求助

问题:SwiftUI Table无法遍历ViewModel中的计算属性数组

我发现Apple官方文档及找到的Table使用教程(example 1、example 2)均基于非动态模型数组构建Table。我的ViewModel包含一个由函数和其他计算属性实现的计算属性数组userDataList,无法在View中编写代码让Table遍历该数组的每一项。

预期效果是生成一个包含5行(第1至第5年)、3列(Year、Annual Income、401k Balance)的Table,填充ViewModel中userDataList的对应数据。

原代码

struct ContentView: View {
@StateObject var vm = ViewModel()

var body: some View {
    VStack(spacing: 15){
        TabView() {
            ProfileView()
                .tabItem {
                    Image(systemName: "square.and.pencil")
                    Text("Profile")
                }
            InvestView()
                .tabItem {
                    Image(systemName: "dollarsign")
                    Text("Tips")
                }
        }
        .environmentObject(vm)
    }
}
}

struct ProfileView: View {
var body: some View {
    Text("Hello, World!")
}
}
//the ProfileView will eventually have a Form that allows the user to manipulate the values within the userData Published variable within the ViewModel

struct EmployeeInfo: Codable, Identifiable {
var id = UUID()
var monthlyHours: Double
var hourlyRateYear1: Double
var hourlyRateYear2: Double
var hourlyRateYear3: Double
var hourlyRateYear4: Double
var hourlyRateYear5: Double
var startingBalance401k: Double
var contribution: Double
var balance401k: Double
}

struct InvestInfo: Codable, Identifiable {
var id = UUID()
var year: Int
var annualIncome: Double
var balance401k: Double
}

struct Year: Codable, Identifiable {
var id = UUID()
var year: Int
}

struct InvestView: View {
@EnvironmentObject var vm: ViewModel
private let currencyStyle = Decimal.FormatStyle.Currency(code:"USD")

var body: some View {
    Table(of: vm.userDataList) {
        TableColumn("Years Until Retirement") { item in
            Text(item.year)
        }
        TableColumn("Annual Income") { item in
            Text(item.annualIncome), format: currencyStyle)
        }
        TableColumn("401k Balance") { item in
            Text(item.balance401k), format: currencyStyle)
        }
    } rows: {
        TableRow(Year(year: 1))
        TableRow(Year(year: 2))
        TableRow(Year(year: 3))
        TableRow(Year(year: 4))
        TableRow(Year(year: 5))
    }
}
}

class ViewModel: ObservableObject {
@Published var userData: EmployeeInfo = EmployeeInfo(monthlyHours: 160.0, hourlyRateYear1: 15.0, hourlyRateYear2: 15.0, hourlyRateYear3: 17.0, hourlyRateYear4: 17.0, hourlyRateYear5: 19.0, startingBalance401k: 5000.0, contribution: 5000.0, balance401k: 10000.0)

func calcAnnualEarnings(hourlyRate: Double) -> Double {
    return (userData.monthlyHours * hourlyRate) * 12
}
var annualIncomeYear1: Double {
    calcAnnualEarnings(hourlyRate: userData.hourlyRateYear1)
}
var annualIncomeYear2: Double {
    calcAnnualEarnings(hourlyRate: userData.hourlyRateYear2)
}
var annualIncomeYear3: Double {
    calcAnnualEarnings(hourlyRate: userData.hourlyRateYear3)
}
var annualIncomeYear4: Double {
    calcAnnualEarnings(hourlyRate: userData.hourlyRateYear4)
}
var annualIncomeYear5: Double {
    calcAnnualEarnings(hourlyRate: userData.hourlyRateYear5)
}

func calc401k(princ: Double, deposits: Double) -> Double {
    let newRate = 10.0
    let rn1 = 1+((newRate/100)/12)
    let comp = pow(rn1,12)
    let totPrinc = comp * princ
    return totPrinc + ((deposits/12) * (comp-1)/((newRate/100)/12))
}
var balanceLine1: Double {
    return calc401k(princ:
    userData.startingBalance401k, deposits:
    userData.contribution)
}
var balanceLine2: Double {
    return calc401k(princ:
    balanceLine1, deposits:
    userData.contribution)
}
var balanceLine3: Double {
    return calc401k(princ:
    balanceLine2, deposits:
    userData.contribution)
}
var balanceLine4: Double {
    return calc401k(princ:
    balanceLine3, deposits:
    userData.contribution)
}
var balanceLine5: Double {
    return calc401k(princ:
    balanceLine4, deposits:
    userData.contribution)
}
var userDataList: [InvestInfo] {
    return [
        InvestInfo(year: 1, annualIncome: annualIncomeYear1, balance401k: balanceLine1),
        InvestInfo(year: 2, annualIncome: annualIncomeYear2, balance401k: balanceLine2),
        InvestInfo(year: 3, annualIncome: annualIncomeYear3, balance401k: balanceLine3),
        InvestInfo(year: 4, annualIncome: annualIncomeYear4, balance401k: balanceLine4),
        InvestInfo(year: 5, annualIncome: annualIncomeYear5, balance401k: balanceLine5)
    ]
}
}

出现的错误

  • 无法将类型'[InvestInfo]'转换为预期的参数类型'TableRow.TableRowValue.Type'(即'Year.Type')
  • 调用初始化器无精确匹配
  • 尾随闭包传递给不接受闭包的参数类型'KeyPath<TableRow.TableRowValue, String>'(即'KeyPath<Year, String>')
  • 错误3重复出现

解决方案

问题根源

你混淆了SwiftUI Table的两种初始化逻辑:

  1. Table(of: 类型)是用于静态行的写法,需要配合手动声明的TableRow
  2. 直接绑定数据源数组的动态写法,不需要手动定义行,Table会自动遍历数组生成内容

你的代码既错误地把数组传入of:参数,又手动添加了无关的Year类型行,导致类型不匹配和语法错误。

修正后的InvestView代码

直接绑定vm.userDataList作为数据源,让Table自动遍历生成行,同时修正语法错误:

struct InvestView: View {
    @EnvironmentObject var vm: ViewModel
    private let currencyStyle = Decimal.FormatStyle.Currency(code: "USD")

    var body: some View {
        Table(vm.userDataList) {
            TableColumn("Years Until Retirement") { item in
                Text("\(item.year)")
            }
            TableColumn("Annual Income") { item in
                Text(item.annualIncome, format: currencyStyle)
            }
            TableColumn("401k Balance") { item in
                Text(item.balance401k, format: currencyStyle)
            }
        }
    }
}

关键修正点

  1. 数据源绑定:用Table(vm.userDataList)替代错误的Table(of: vm.userDataList),直接将计算属性数组作为数据源
  2. Int转字符串:item.year是Int类型,需要用"\(item.year)"转成字符串后传入Text
  3. 修正语法错误:原代码中Text(item.annualIncome), format: currencyStyle)多了一个右括号,修正为正确的参数格式
  4. 移除静态行:不需要手动添加TableRow,Table会自动遍历userDataList中的5个InvestInfo实例生成对应行

效果说明

修正后,Table会自动读取ViewModel中userDataList的每一项数据,生成5行3列的表格,且当userData中的参数变化时,计算属性userDataList会自动更新,表格内容也会实时刷新。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 05:45:22