问题描述
在我学习 SwiftUI 的过程中,我真的很纠结于这个 MVVM 架构。话虽如此,我有一个项目,我一直在努力学习这一点。在其他人的帮助下,我走到了这一步。
当前的问题是,我需要使包裹在视图模型上的 @Published
属性具有可选参数。不幸的是,我尝试的一切都不起作用。我想要做的是获取用户在 TextFields 上输入的内容并在数学函数中使用它们。
模型.swift
// Model.
protocol Height {
var heightInCentimeters: Int { get }
}
struct ImperialHeight: Height {
var feet: Int
var inches: Int
var heightInCentimeters: Int {
Int(((Double(feet) * 12.0)) + Double(inches) * 2.54)
}
}
struct MetricHeight: Height {
var heightInCentimeters: Int
}
protocol Weight {
var weightInKilograms: Int { get }
}
struct ImperialWeight: Weight {
var pounds: Int
var weightInKilograms: Int {
Int(Double(pounds) * 0.4535924)
}
}
struct MetricWeight {
var weightInKilograms: Int
}
struct UserData {
var age: Int = 18
var weight: Weight
var height: Height
}
查看模型
class UserDataviewmodel: ObservableObject {
@Published var userData: UserData = UserData(weight: ImperialWeight(pounds: 220),height: ImperialHeight(feet: 6,inches: 2))
...
如上所述,当前的问题是 @Published
属性包装器需要重量和高度。理想情况下,我想制作那些通过用户 TextField 输入填充数据的选项。
我尝试阅读大量文档、教程和指南。不幸的是,在如何将其应用于我的项目方面,我仍然没有掌握这个概念。
解决方法
我认为你把它复杂化了一点。您可以使用 Measurement
为您完成大部分工作。如果您想以不同的方式编辑测量,只需有一个单一的事实来源。下面是一个带有 height
import SwiftUI
//The key for something like this is to save everything to one variable in this case I will use centimeters as the source of truth
struct Height{
///Source of truth
var centimeters: Double = 0
///This will have a multitude of uses and has its on Formatter for when you want to display units based on locale,etc
var heightInCentimeters: Measurement<UnitLength> {
get{
Measurement(value: centimeters,unit: .centimeters)
}
//A measurement of any UnitLength kind will be converted to cm
//Make sure you use the given units and you dont create new ones or you will get zero
set{
centimeters = newValue.converted(to: .centimeters).value
}
}
/// centimeters converted to feet using Swift.Measurement
var feet: Double {
get{
return heightInCentimeters.converted(to: .feet).value
}
set{
heightInCentimeters = Measurement(value: newValue,unit: UnitLength.feet)
}
}
/// centimeters converted to feet and inches. They depend on each other like this
var feetAndInches: (feet:Int,inches:Int){
get{
let feetDouble = heightInCentimeters.converted(to: .feet).value
//figure out the decimal
let feetForInches = feetDouble.truncatingRemainder(dividingBy: 1)
//remove the decimal
let feet = feetDouble - feetForInches
//convert decimal to inches
let inches = Measurement(value: feetForInches,unit: UnitLength.feet).converted(to: .inches).value
//return whole numbers
return (Int(feet),Int(inches))
}
set(newValue){
let ftCM = Measurement(value: Double(newValue.feet),unit: UnitLength.feet).converted(to: .centimeters).value
let inCM = Measurement(value: Double(newValue.inches),unit: UnitLength.inches).converted(to: .centimeters).value
centimeters = ftCM + inCM
}
}
}
struct UserData {
var age: Int = 18
var height: Height
}
class UserDataViewModel: ObservableObject {
@Published var userData: UserData = UserData(height: Height())
}
struct UserDataView: View {
@StateObject var vm: UserDataViewModel = UserDataViewModel()
var numFormatter: NumberFormatter{
let format = NumberFormatter()
format.maximumFractionDigits = 2
return format
}
var body: some View {
//Notice that TextField with formatter only "saves" the new value when the user presses return/done on the keyboard
List{
Section(header: Text("age"),content: {
HStack{
Text("age")
TextField("age",value: $vm.userData.age,formatter: numFormatter)
}
})
Section(header: Text("height = \(vm.userData.height.heightInCentimeters.description)"),content: {
HStack{
Text("feet")
TextField("feet",value: $vm.userData.height.feet,formatter: numFormatter)
}
VStack{
HStack{
Text("feet")
TextField("feet",value: $vm.userData.height.feetAndInches.feet,formatter: numFormatter)
}
HStack{
Text("inches")
TextField("inches",value: $vm.userData.height.feetAndInches.inches,formatter: numFormatter)
}
}
HStack{
Text("centimeters")
TextField("centimeters",value: $vm.userData.height.centimeters,formatter: numFormatter)
}
})
}
}
}
struct UserDataView_Previews: PreviewProvider {
static var previews: some View {
UserDataView()
}
}
这个代码可以是任意长度的。不仅仅是身高。