[SwiftUI练级-3] 餐费计算器 · part3
更多内容欢迎关注公众号:BoBo清吧
为小费比例添加分段的Picker现在,让我们为app添加第二个 picker 视图。不过这次我们希望方式稍有不同:我们会用到分段风格的 Picker。这是一种特别的 Picker,所有的选项被水平排开。当你的选项数量很少时,这是一种绝佳的 Picker 样式。我们的表单已经有两个 section:一个用于展示账单金额和人数,另一个用于展示最终结果。 在两个 section 之间,我们加上第三个 section,用来展示小费比例:
Section {
Picker("Tip percentage", selection: $tipPercentage) {
ForEach(0 ..< tipPercentages.count) {
Text("\(self.tipPercentages[$0])%")
}
}
}
上面的代码遍历了 tipPercentages 数组,把每个值转换成文本视图。就像前面的 Picker 一样, SwiftUI 会为每个文本视图提供一行,并且在点击时滑出新的一屏。不过这里我们要换成分段式的控件,用修改器实现:
.pickerStyle(SegmentedPickerStyle())复制代码最终代码长这样:Section {
Picker("Tip percentage", selection: $tipPercentage) {
ForEach(0 ..< tipPercentages.count) {
Text("\(self.tipPercentages[$0])%")
}
}.pickerStyle(SegmentedPickerStyle())
}
运行程序,你会发现功能已经是完成了十之八九:用户可以输入账单金额,选择人数,选择小费比例。还不赖!但事情没有你想象的那么简单。应用开发者面临的一个问题是:我们需要确保app按照我们的期望工作。我们设计它是为了解决某个特定的问题,因此我们自然知道每个部分的含义。但用户是否也完全理解呢?尝试重新审视一下我们的UI:“Amount” 看起来合理 – 是一个用户可以输入数量的小盒子。“Number of people” 也可以完美自我诠释了。底部我们会显示一个"total"的标签,目前为止可以先忽略。中间的 section 呢?这个百分比代表什么意思呢?是的,我们当然知道这是在选择小费的比例,但这一点对用户并不是显而易见的。我们可以做的更好。一个选项是在分段控件之前再添加一个说明文本视图,就像这样:
Section {
Text("How much tip do you want to leave?")
Picker("Tip percentage", selection: $tipPercentage) {
ForEach(0 ..< tipPercentages.count) {
Text("\(self.tipPercentages[$0])%")
}
}.pickerStyle(SegmentedPickerStyle())
}
这当然可行,但视觉上不够好。因为看起来这个文本视图是孤立的,而不是分段控件的标签。一个更好的方案是修改 section 本身。SwiftUI 允许我们给一个 section 添加 header 和 footer,在这里我们可以利用 header 来添加说明。实际上,还是相同的文本,只不过是移到了 section 的 header。代码如下:
Section(header: Text("How much tip do you want to leave?")) {
Picker("Tip percentage", selection: $tipPercentage) {
ForEach(0 ..< tipPercentages.count) {
Text("\(self.tipPercentages[$0])%")
}
}.pickerStyle(SegmentedPickerStyle())
}
计算AA费用目前为止,最后一个 section 只是一个文本视图重复了用户输入的账单金额。现在我们要改造它,这是这个工程最重要的部分:我们要让文本视图显示每一个人应当分摊多少费用。实现这个目标有许多个方案。恰好最简单的那个同时也是最清晰的那个。之所以这么说,是因为这个方案给我们最清晰易懂的代码:我们将添加一个计算属性,用以计算每个人应当分摊的费用。小学数学:总的费用等于账单金额,加上小费,然后除以人数就是每个人应当分摊的费用。在完成这道小学数学题之前,让我们重新检视一下人数是什么,小费比例是什么,账单金额又是什么。听起来可能很简单,但有一些小细节需要处理:如你所见,numberOfPeople 其实是基于2偏移 – 当它存的是3时,实际上代表5个人tipPercentage 整数存储的是 tipPercentages 数组的索引,而不是实际的小费比例checkAmount 属性是一个用户输入的字符串,有可能并不是一个合法的数字,也有可能是空的好了,现在我们要创建一个叫 totalPerPerson 的计算属性,表示每个人最终要付的钱,它的工作会从搞定上面说的三个细节开始。首先,先添加计算属性本身,在 body 属性之前插入代码就好:var totalPerPerson: Double {
// 计算每个人的应付金额
return 0
}复制代码我们将把注释替换成实现。首先是人数,numberOfPeople + 2。let peopleCount = Double(numberOfPeople + 2)复制代码注意,我们用的是 Double,以避免后面的除法丢失精度。接下来我们要搞定实际的小费比例。我们的 tipPercentage 属性存储的是用户选择的值,这个值实际上代表 tipPercentages 数组里的一个位置。因此,我们需要查看 tipPercentages 以搞清楚究竟选的是什么,然后再转换成 Double:let tipSelection = Double(tipPercentages[tipPercentage])复制代码最后一个用于计算的数字是账单金额。你应该记得它目前是一个字符串,因为它来自文本框的双向绑定。尽管我们显示了数字键盘,这并不能阻止用户输入非数字的字符串。因此我们在把字符串转换成数字时,需要小心处理。幸运的是,Swift 提供了字符串转换成Double的方法,像下面这样:let stringValue = "0.5"
let doubleValue = Double(stringValue)复制代码看起来足够简单对吧?有一点你需要注意:doubleValue 的类型将会变成 Double? 而不是 Double。是的,这是一个可选型。你看,Swift 无法知道字符串里是否包含了可以被安全地转换成 Double 的内容,所以它用了可选型:如果转换成功那么我们的可选型将包含结果值,否则可选型会被设置为nil。有几种方法可以处理可选型,最简单的是使用空合运算符 ?? 来确保我们最终拿到一个合法的数字。在 totalPerPerson 计算属性的方法体的最前面添加这行代码:let orderAmount = Double(checkAmount) ?? 0复制代码这行代码会试图把 checkAmount 转换成 Double,如果失败了则采用0代替。现在我们已经拿到了三个输入,是时候做计算了。一共分三步:我们通过将 orderAmount 除以100 再乘上 tipSelection 得到小费的金额我们将小费金额加上账单金额得到总费用我们把总费用除以人数得到每人应付的费用完成之后,返回这个每人应付费用。把属性里的 return 0 这一句换成下面的代码:
let tipValue = orderAmount / 100 * tipSelection
let grandTotal = orderAmount + tipValue
let amountPerPerson = grandTotal / peopleCount
return amountPerPerson
//如果你的每一步都正确,那代码应该长这样:
var totalPerPerson: Double {
let peopleCount = Double(numberOfPeople + 2)
let tipSelection = Double(tipPercentages[tipPercentage])
let orderAmount = Double(checkAmount) ?? 0
let tipValue = orderAmount / 100 * tipSelection
let grandTotal = orderAmount + tipValue
let amountPerPerson = grandTotal / peopleCount
return amountPerPerson
}
现在totalPerPerson已经返回给我们正确的值,我们可以把最后一个 section 的内容修改掉了:把之前的Section {
Text("$\(checkAmount)")
}复制代码替换成Section {
Text("$\(totalPerPerson)")
}复制代码尝试运行app,然后思考。希望现在你对 SwiftUI 的“视图是它们状态的函数”这句话现在已经有所理解 —— 当状态改变,视图自动更新以匹配状态。在我们结束前面,我们还要修复界面上的一点小问题:它就是每个人应付费用的显示方式。 记得吗,我们用于计算的是 Double 类型,这意味着 Swift 会给我们额外的我们并不需要的精度。我们可能只需要 ¥120.80 这样的数字,但它会给我们 ¥120.8000000...。我们可以用 SwiftUI 提供的一个很方便的字符串解析特性来解决这个问题:它是一种决定字符串应当以何种方式被格式化的特性,实际上来自C语言,语法乍一看有点怪异:我们先写一个specifier的参数标签,然后赋值 "%.2f”,这是C的语法,表示”两个数字的浮动数“。比较粗糙的,”%f” 的意思是 “任何类型的浮点数”,在我们的例子里是整个最终数值。另外一个选项是 “%g”,它做的事情类似,但会把尾部无关紧要的0去掉。比如 ¥120.80 会变成 ¥120.8。混合一个 “.2” 表示至少要小数点后两位 ,无论这个数实际是多少。如果你好奇的话,可以到维基百科上阅读关于C语言风格的格式指定符:https://en.wikipedia.org/wiki/Printf_format_stringen.wikipedia.org言归正传,我们希望 totalPerPerson 这个变量用新的格式化指定符,所以把代码改成下面这样:Text("$\(totalPerPerson, specifier: "%.2f")")复制代码最后,运行工程,收工!
网友评论