SwiftUI + Combine,一起使用模型和视图模型
SwiftUI + Combine, using Models and ViewModels together
我是 Swift 的新手,我目前正在尝试通过使用 SwiftUI + Combine 构建租金分割应用程序来学习。我想遵循 MVVM 模式并尝试实现它。目前我有以下模型、视图模型和视图文件:
型号:
import Foundation
import Combine
struct InputAmounts {
var myMonthlyIncome : Double
var housemateMonthlyIncome : Double
var totalRent : Double
}
ViewModel(我试图使用模型中的数据来符合 MVVM 模式,但我不确定我是否以最干净的 way/correct 方式完成了此操作,所以如果错误请纠正我)
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' 的值没有动态成员 'totalRent' 使用来自根类型 'FairRentViewModel' 的键路径”
“类型 'ObservedObject.Wrapper' 的值没有动态成员 'myMonthlyIncome' 使用来自根类型 'FairRentViewModel' 的键路径”
“类型 'ObservedObject.Wrapper' 的值没有动态成员 'housemateMonthlyIncome' 使用来自根类型 'FairRentViewModel' 的键路径”
我的问题是:
- 这个错误是什么意思,请指出正确的方向来解决?
- 我在这里尝试实现 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?' 的值解包为 'Double' 类型的值”,我认为我是通过 ??操作数?
您的视图模型应具有您要在视图中使用的每个模型属性的属性,并且视图模型还应负责将它们转换为适合视图的格式。属性应标记为@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。
我是 Swift 的新手,我目前正在尝试通过使用 SwiftUI + Combine 构建租金分割应用程序来学习。我想遵循 MVVM 模式并尝试实现它。目前我有以下模型、视图模型和视图文件:
型号:
import Foundation
import Combine
struct InputAmounts {
var myMonthlyIncome : Double
var housemateMonthlyIncome : Double
var totalRent : Double
}
ViewModel(我试图使用模型中的数据来符合 MVVM 模式,但我不确定我是否以最干净的 way/correct 方式完成了此操作,所以如果错误请纠正我)
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' 的值没有动态成员 'totalRent' 使用来自根类型 'FairRentViewModel' 的键路径”
“类型 'ObservedObject.Wrapper' 的值没有动态成员 'myMonthlyIncome' 使用来自根类型 'FairRentViewModel' 的键路径”
“类型 'ObservedObject.Wrapper' 的值没有动态成员 'housemateMonthlyIncome' 使用来自根类型 'FairRentViewModel' 的键路径”
我的问题是:
- 这个错误是什么意思,请指出正确的方向来解决?
- 我在这里尝试实现 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?' 的值解包为 'Double' 类型的值”,我认为我是通过 ??操作数?
您的视图模型应具有您要在视图中使用的每个模型属性的属性,并且视图模型还应负责将它们转换为适合视图的格式。属性应标记为@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。