【问题标题】:SwiftUI + Combine, using Models and ViewModels togetherSwiftUI + Combine,一起使用 Models 和 ViewModels
【发布时间】:2021-01-16 22:24:40
【问题描述】:

我对 Swift 很陌生,我目前正在尝试通过使用 SwiftUI + Combine 构建租金拆分应用来学习。我想遵循 MVVM 模式并尝试实现这一点。目前我有以下模型、视图模型和视图文件:

型号:

import Foundation
import Combine

struct InputAmounts {
    var myMonthlyIncome : Double
    var housemateMonthlyIncome : Double
    var totalRent : Double
}

ViewModel(我尝试使用模型中的数据来符合 MVVM 模式,但我不确定我是否以最干净的方式/正确的方式完成了此操作,如果有错误请纠正我)

import Foundation
import Combine

class FairRentViewModel : ObservableObject {
    
    private var inputAmounts: InputAmounts

    init(inputAmounts: InputAmounts) {
        self.inputAmounts = inputAmounts
    }
   
    var yourShare: Double {
        inputAmounts.totalRent = Double(inputAmounts.totalRent)
        inputAmounts.myMonthlyIncome = Double(inputAmounts.myMonthlyIncome)
        inputAmounts.housemateMonthlyIncome = Double(inputAmounts.housemateMonthlyIncome)
        let totalIncome = Double(inputAmounts.myMonthlyIncome + inputAmounts.housemateMonthlyIncome)
        let percentage = Double(inputAmounts.myMonthlyIncome / totalIncome)
        let value = Double(inputAmounts.totalRent * percentage)

        return Double(round(100*value)/100)
    }
}

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

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)
    }
}

我收到了视图的构建错误: “'ObservedObject.Wrapper' 类型的值没有使用根类型 'FairRentViewModel' 的键路径的动态成员 'totalRent'”

“'ObservedObject.Wrapper' 类型的值没有动态成员 'myMonthlyIncome' 使用来自根类型 'FairRentViewModel' 的键路径”

“'ObservedObject.Wrapper' 类型的值没有使用根类型'FairRentViewModel' 的键路径的动态成员'housemateMonthlyIncome'”

我的问题是:

  • 此错误是什么意思,请指出正确的解决方向?
  • 我在尝试在这里实现 MVVM 模式时是否完全走错了路?

正如我所说,我是一个 Swift 初学者,只是想学习,所以任何建议都将不胜感激。

响应更新

  var yourShare: String {
        inputAmounts.totalRent = (inputAmounts.totalRent)
        inputAmounts.myMonthlyIncome = (inputAmounts.myMonthlyIncome)
        inputAmounts.housemateMonthlyIncome = (inputAmounts.housemateMonthlyIncome)
        var totalIncome = Double(inputAmounts.myMonthlyIncome) 0.00 + Double(inputAmounts.housemateMonthlyIncome) ?? 0.00
        var percentage = Double(myMonthlyIncome) ?? 0.0 / Double(totalIncome) ?? 0.0
        var value = (totalRent * percentage)
        
        return FairRentViewModel.formatter.string(for: value) ?? ""
    }

我在此处收到错误消息“可选类型的值 'Double?'必须解包为“双”类型的值”,我认为我正在使用 ??操作数?

【问题讨论】:

  • 似乎您正在尝试访问$viewModel.totalRent,但您的viewModel 没有任何名为totalRent 的属性。我认为您需要的可能是 $viewModel.inputAmounts.totalRent 如果这样做,您必须从 ViewModel 中的 inputAmounts 中删除 private
  • 首先,做除法时不需要将Double强制转换为Double,也不要?? 0

标签: swift mvvm swiftui combine


【解决方案1】:

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

例如

@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 方法只是一个简单的示例,说明如果您将函数绑定到按钮操作或类似操作,如何从视图更新模型。

另请注意,我用于格式化程序的数字样式只是一个猜测,您可能需要更改它。并且建议在处理金钱时使用 Decimal 而不是 Double。

【讨论】:

  • 谢谢@JoakimDanielson - 这是否意味着我不需要单独的模型文件?
  • 这完全取决于你,但我个人更喜欢为我的每个模型(以及视图模型和视图)拥有单独的文件
  • 感谢@JoakimDanielson。 “为yourShare 做类似的事情”是什么意思?
  • 把它变成一个String而不是Double
  • 对不起@JoakimDanielson,我仍然很困惑如何做到这一点-您介意更新您的答案以举例说明您的意思吗?
猜你喜欢
  • 2012-12-31
  • 1970-01-01
  • 2021-06-16
  • 1970-01-01
  • 2020-06-10
  • 2021-11-06
  • 2021-08-21
  • 1970-01-01
  • 2020-03-23
相关资源
最近更新 更多