[SwiftUI 100天] Day-3 AA餐费计算器 · part3

2020/1/31 23:07:10

本文主要是介绍[SwiftUI 100天] Day-3 AA餐费计算器 · part3,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

更多内容欢迎关注公众号:Swift花园

为小费比例添加分段的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_string​en.wikipedia.org

言归正传,我们希望 totalPerPerson 这个变量用新的格式化指定符,所以把代码改成下面这样:

Text("$\(totalPerPerson, specifier: "%.2f")")复制代码

最后,运行工程,收工!

我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~







这篇关于[SwiftUI 100天] Day-3 AA餐费计算器 · part3的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程