SwiftUI + Combine,一起使用Models和ViewModels [英] SwiftUI + Combine, using Models and ViewModels together

查看:78
本文介绍了SwiftUI + Combine,一起使用Models和ViewModels的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm very new to Swift and I am currently trying to learn by building a rent splitting app with SwiftUI + Combine. I want to follow the MVVM pattern and am trying to implement this. At the moment I have the following Model, ViewModel and View files:

型号:

import Foundation
import Combine

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

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

ViewModel (where I have attempted to use the data from the Model to conform to the MVVM pattern, but I am not sure I have done this in the cleanest way/correct way so please correct me if wrong)

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

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

And then am trying to pass this all to the View:

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'"

I am getting the build errors with the View: "Value of type 'ObservedObject.Wrapper' has no dynamic member 'totalRent' using key path from root type 'FairRentViewModel'"

'ObservedObject.Wrapper'类型的值没有使用根类型'FairRentViewModel'的关键路径的动态成员'myMonthlyIncome'"

"Value of type 'ObservedObject.Wrapper' has no dynamic member 'myMonthlyIncome' using key path from root type 'FairRentViewModel'"

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

"Value of type 'ObservedObject.Wrapper' has no dynamic member 'housemateMonthlyIncome' using key path from root type 'FairRentViewModel'"

我的问题是:

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

正如我所说,我是一名 Swift 初学者,只是想学习,因此不胜感激.

As I said I am a Swift beginner just trying to learn so any advice would be appreciated.

对回答的更新

  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 的值?'必须解包为Double"类型的值;我以为我是用 ??操作数?

I am getting errors here that "Value of optional type 'Double?' must be unwrapped to a value of type 'Double'" which I thought I was achieving with the ?? operands?

推荐答案

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

Your view model should have properties for each of the model properties you want to work with in the view and the view model should also be responsible for converting them to a format suitable for the view. The properties should be marked as @Published to so that the view gets updated if they are changed.

例如

@Published var myMonthlyIncome: String

这是一个字符串,我们可以在 init

and this is as you see a String and we can convert it in the init

myMonthlyIncome = FairRentViewModel.formatter.string(for: inputAmounts.myMonthlyIncome) ?? ""

这是我的视图模型的完整版本

Here is my complete version of the view model

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 方法只是一个简单的例子,说明如果你将函数绑定到按钮操作或类似操作.

You should do something similar for yourShare and I the save method is just a simple example of how to update the model from the view if you tie the function to a Button action or similar.

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

Also note that the number style I used for the formatter is just a guess, you might need to change that. And it is recommended to work with Decimal instead of Double when dealing with money.

这篇关于SwiftUI + Combine,一起使用Models和ViewModels的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆