Swift 迅捷+;结合使用模型和ViewModels

Swift 迅捷+;结合使用模型和ViewModels,swift,mvvm,swiftui,combine,Swift,Mvvm,Swiftui,Combine,我是Swift的新手,目前正在尝试使用SwiftUI+Combine构建一个租金分摊应用程序。我希望遵循MVVM模式,并尝试实现这一点。目前,我有以下模型、视图模型和视图文件: 型号: import Foundation import Combine struct InputAmounts { var myMonthlyIncome : Double var housemateMonthlyIncome : Double var totalRent : Double }

我是Swift的新手,目前正在尝试使用SwiftUI+Combine构建一个租金分摊应用程序。我希望遵循MVVM模式,并尝试实现这一点。目前,我有以下模型、视图模型和视图文件:

型号:

import Foundation
import Combine

struct InputAmounts {
    var myMonthlyIncome : Double
    var housemateMonthlyIncome : Double
    var totalRent : Double
}
ViewModel(我试图使用模型中的数据来符合MVVM模式,但我不确定我是否以最干净的方式/正确的方式进行了此操作,因此如果错误,请纠正我)

然后我试着将这一切传递给视图:

import SwiftUI
import Combine

struct FairRentView: View {
    
    @ObservedObject private var viewModel: FairRentViewModel
    
    init(viewModel: FairRentViewModel){
        self.viewModel = viewModel
    }
    
    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("Enter the total monthly rent:")) {
                    TextField("Total rent", text: $viewModel.totalRent)
                        .keyboardType(.decimalPad)
                }
                
                Section(header: Text("Enter your monthly income:")) {
                    TextField("Your monthly wage", text: $viewModel.myMonthlyIncome)
                        .keyboardType(.decimalPad)
                }
                
                Section(header: Text("Enter your housemate's monthly income:")) {
                    TextField("Housemate's monthly income", text: $viewModel.housemateMonthlyIncome)
                        .keyboardType(.decimalPad)
                }
                Section {
                    Text("Your share: £\(viewModel.yourShare, specifier: "%.2f")")
                }
            }
            .navigationBarTitle("FairRent")
        }
    }
}

struct FairRentView_Previews: PreviewProvider {
    static var previews: some View {
        let viewModel = FairRentViewModel(inputAmounts: <#InputAmounts#>)
        FairRentView(viewModel: viewModel)
    }
}


我在这里得到了一个错误,“可选类型'Double'的值必须被解包为'Double'类型的值”,我以为我是用??操作数?

视图模型应具有要在视图中使用的每个模型属性的属性,并且视图模型还应负责将它们转换为适合视图的格式。属性应标记为@Published to,以便在更改时更新视图

比如说

@Published var myMonthlyIncome: String
这是一个字符串,我们可以在
init

myMonthlyIncome = FairRentViewModel.formatter.string(for: inputAmounts.myMonthlyIncome) ?? ""
这是我的视图模型的完整版本

final class FairRentViewModel : ObservableObject {
    private static let formatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        return formatter
    }()

    private var inputAmounts: InputAmounts

    @Published var myMonthlyIncome: String
    @Published var housemateMonthlyIncome: String
    @Published var totalRent: String

    init(inputAmounts: InputAmounts) {
        self.inputAmounts = inputAmounts
        myMonthlyIncome = FairRentViewModel.formatter.string(for: inputAmounts.myMonthlyIncome) ?? ""
        housemateMonthlyIncome = FairRentViewModel.formatter.string(for: inputAmounts.housemateMonthlyIncome) ?? ""
        totalRent = FairRentViewModel.formatter.string(for: inputAmounts.totalRent) ?? ""
    }

    var yourShare: String {
        let value = inputAmounts.totalRent * inputAmounts.myMonthlyIncome / (inputAmounts.myMonthlyIncome + inputAmounts.housemateMonthlyIncome)

        return FairRentViewModel.formatter.string(for: Double(round(100*value)/100)) ?? ""
    }

    func save() {
        inputAmounts.myMonthlyIncome = FairRentViewModel.formatter.number(from: myMonthlyIncome)?.doubleValue ?? 0
        //...
    }
}
您应该为
yourShare
执行类似的操作,
save
方法只是一个简单的示例,说明了如果将函数绑定到按钮操作或类似操作,如何从视图更新模型


还要注意,我用于格式化程序的数字样式只是一个猜测,您可能需要更改它。在处理金钱时,建议使用十进制而不是双精度。

似乎您正试图访问
$viewModel.totalRent
,但您的
viewModel
没有任何名为
totalRent
的属性。我认为您需要的可能是
$viewModel.inputAmounts.totalRent
,如果您这样做,您必须从viewModel中的inputAmounts中删除
private
,首先,没有必要将Double转换为Double,也永远不要执行
??0
在进行分割时。谢谢@JoakimDanielson-这是否意味着我不需要仅为模型创建单独的文件?这真的取决于您,但就个人而言,我更喜欢为我的每个模型(以及视图模型和视图)创建单独的文件谢谢@JoakimDanielson。“为
yourShare
做点类似的事情”是什么意思?请将其转换为字符串而不是double对不起@JoakimDanielson,我仍然不知道如何做-请您更新您的答案,以举例说明您的意思好吗?
myMonthlyIncome = FairRentViewModel.formatter.string(for: inputAmounts.myMonthlyIncome) ?? ""
final class FairRentViewModel : ObservableObject {
    private static let formatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        return formatter
    }()

    private var inputAmounts: InputAmounts

    @Published var myMonthlyIncome: String
    @Published var housemateMonthlyIncome: String
    @Published var totalRent: String

    init(inputAmounts: InputAmounts) {
        self.inputAmounts = inputAmounts
        myMonthlyIncome = FairRentViewModel.formatter.string(for: inputAmounts.myMonthlyIncome) ?? ""
        housemateMonthlyIncome = FairRentViewModel.formatter.string(for: inputAmounts.housemateMonthlyIncome) ?? ""
        totalRent = FairRentViewModel.formatter.string(for: inputAmounts.totalRent) ?? ""
    }

    var yourShare: String {
        let value = inputAmounts.totalRent * inputAmounts.myMonthlyIncome / (inputAmounts.myMonthlyIncome + inputAmounts.housemateMonthlyIncome)

        return FairRentViewModel.formatter.string(for: Double(round(100*value)/100)) ?? ""
    }

    func save() {
        inputAmounts.myMonthlyIncome = FairRentViewModel.formatter.number(from: myMonthlyIncome)?.doubleValue ?? 0
        //...
    }
}