代码库
教程中的项目代码都保存在这里:
https://github.com/NDFour/swiftui01
前言
在这一章里,我们会做一个BMI计算器,进一步加深我们在之前的章节里学习到的知识。这一章里我们会使用TextField 用来获取用户的输入内容。
MBI指数用来帮你判断自己的体重是否超标,计算方法如下:
使用体重(单位:千克)除以身高(单位:米)的平方。
如:你的身高 175cm,体重70kg,那么BMI指数就是
70/1.75/1.75=22.9
BMI指数分级:
过轻:BMI < 18.5
正常:18.5 <= BMI <= 24.9
超重:25 <= BMI <= 29.9
肥胖:BMI >= 30
开始
新建项目
在第一章中我们介绍了如何使用 Xcode新建一个项目,如果忘记了可以回到第一章再复习一下。
新建好的项目长这个样子:
开始界面
在ContentView.swift 中我们添加了两个TextField 用来输入体重和身高。当你需要获取用户的输入时都可以使用TextField 。
同时,我们还添加了一个Text 用来显示BMI计算器。
上面这些元素我们都放到一个VStack 中。
struct ContentView: View {
@State private var weightText: String = ""
@State private var heightText: String = ""
var body: some View {
VStack {
Text("BMI计算器").font(.largeTitle)
TextField("体重(kg)", text: $weightText)
.textFieldStyle(RoundedBorderTextFieldStyle())
TextField("身高(m)", text: $heightText)
.textFieldStyle(RoundedBorderTextFieldStyle())
.border(Color.black)
}.padding()
}
}
TextField 的第一个参数是一个提示用户输入内容的占位符,第二个参数绑定了我们一开始声明的两个参数:weightText , heightText 。 注意:我们在绑定自定义变量到TextField 时在变量名前加了个$ 符号。
至此,当用户在TextField 输入框中输入内容时,我们绑定的@State 变量的值也会同步更新,换句话说,当@State 变量的值发生变化时,TextField 的内容同样也会改变。
我们为TextField 添加了几个modifier ,你可以删掉他们然后重新运行看看会产生什么结果。
假如你删除.border 那么TextField 将不会存在明显的边框,这回让用户难以区分输入框边界究竟在哪里。
textFieldStyle (RoundedBorderTextFieldStyle()) 则会让TextField 产生圆角效果。
有了这些modifier 我们就可以按照自己的喜好来展示不同风格的TextField (或者其他的 View)。
计算和输出按钮
接下来,我们要在界面上添加一个 计算BMI 按钮。
@State private var weightText: String = ""
@State private var heightText: String = ""
@State private var bmi: Double = 0
var body: some View {
VStack {
...
Button(action: {
let weight = Double(self.weightText)!
let height = Double(self.heightText)!
self.bmi = weight/(height * height)
}) {
Text("计算BMI")
.padding()
.foregroundColor(.white)
.background(Color.blue)
}
Text("BMI: \(bmi)").font(.title).padding()
}.padding()
}
在上面的代码中,我们将用户输入的内容赋值给weight 和height 。同时我们将其从 String 类型转换到了 Double 类型,因为我们要使用这两个值参与数学运算。
你可能会对代码后面的! 产生疑问,先别着急,我会在后面的可选值部分中详细介绍。
在按钮下方我们添加了一个Text 标签,用于展示计算出的 BMI 的值,我们在声明变量bmi 时为其加上了@State ,这样当 bmi 的值发生变化时,Text 标签就会及时更新自己显示的内容。
运行
当你运行app并且输入身高体重后,点击计算 BMI按钮,你将会得到你的 BMI 计算结果。
但现在的计算结果小数点之后的部分太长(丑)了,我们来格式化一下输出内容。
下面我们使用代码规定只展示小数点后1位数字:
Text(“BMI: (bmi, specifier: “%.1f”)”).font(.title).padding()
specifier 用于字符串插值格式化,%.1f 表示只保留浮点数小数点后1位数字。
BMI分级
接下来让我们为app添加BMI分级的功能,比如:
BMI:29.4,超重
我们需要在app中实现以下逻辑:
过轻: bmi < 18.5
健康:18.5 <= bmi <= 24.9
超重:25 <= bmi <= 29.9
肥胖:bmi >= 30
添加以下代码:
struct ContentView: View {
...
@State private var classification: String = ""
var body: some View {
VStack {
...
Button(action: {
let weight = Double(self.weightText)!
let height = Double(self.heightText)!
self.bmi = weight/(height * height)
if self.bmi < 18.5 {
self.classification = "过轻"
} else if self.bmi < 24.9 {
self.classification = "健康"
} else if self.bmi < 29.9 {
self.classification = "超重"
} else {
self.classification = "肥胖"
}
}) {
Text("计算BMI")
.padding()
.foregroundColor(.white)
.background(Color.blue)
}
Text("BMI: \(bmi, specifier: "%.1f"), \(classification)").font(.title).padding()
}.padding()
}`
}
我们通过声明一个@State 变量calssification 来存储计算后的 BMI 分级。
然后通过一系列的 if else 语句来判断计算出的 bmi 所属的等级。
最后通过字符串插值的方式将classification 展示到Text 上。
可选值 Optionals
在上面的代码中,我们有这样的两行:
let weight = Double(self.weightText)!
let height = Double(self.heightText)!
为什么要在最后添加一个感叹号呢?如果你尝试着删除感叹号,Xcode 将会对下面这行代码报错:
self.bmi = weight/(height * height)
这是因为Double(<String>) 实际上返回的是Double? ,也就是可选的Double。对于任何变量来说,如果变量名后面加上了? ,这就会将此变量看作optional 类型(也就是可选的类型),这样的话这个变量要么有值,要么= nil。
举个栗子:
Double? 包含一个 Double 类型的值或者 nil
Int? 包含一个 Int 类型的值或者 nil
你可能还会有疑问,为什么Double(self.weightText) 会返回一个可选的类型呢?他不应该返回一个 Double 类型么??
假如用户在输入框中输入的不是自己的身高体重,而是一个字符串,比如输入你好,swift零一,显而易见代码可没法把这个字符串转换成一个 Double !!
当用户输入的值没法按照预期目标转换为一个 Double 类型的数字时,就会返回一个 nil。
所以Double(self.weightText) 将会返回一个可选的类型,如果用户输入的值是数字,将会成功被转换成 Double 类型返回,如果用户没有输入内容或者输入的内容无法转换成 Double 类型时就会返回 nil。
当我们要取一个optional类型变量的值时,需要在变量名后面加! 。
聪明的你应该想到了吧??这可是一个非常危险的操作!!
当一个optional变量的值为nil时,我们使用! 来取值的话代码就会崩溃!
你可以尝试运行 BMI计算器 app不要输入任何内容直接点击计算,app会崩溃然后报错如下:
Fatal error: Unexpecteddly found nil while unwrapping an Optional value.
为了避免optiona的值为nil时通过! 取值导致app崩溃,我们应该使用if let t 语法。if let 首先会检查optional变量是否包含一个不为nil的值,然后将该值绑定到一个临时变量。换句话说,这是==解包(取optional类型变量的值)==可选值的一个安全操作。
修改后的代码如下:
Button(action: {
var weight: Double = 0
var height: Double = 0
if let weightDouble = Double(self.weightText) {
weight = weightDouble
}
if let heightDouble = Double(self.heightText) {
height = heightDouble
}
...
})
在上面的代码中,只有 Double(self.weightText) 不为 nil 时代码才会进入 if 里,并把self.weightText 转换为 Double 类型后的值赋给临时变量 weightDouble 。当 Double(self.weightText) 为 nil 时代码不进入 if 循环,直接往下执行。
在黑暗模式下预览项目
至今为止,我们都是在默认的明亮模式下预览项目,Swift UI还支持在黑暗模式(Dark mode)下预览项目。
运行时使用黑暗模式
app在模拟器中运行时,你可以前往模拟器的Settings => Developer,然后打开Dark Appearance选项。
然后你的模拟器就会切换到黑暗模式,你的app也会自动切换到黑暗模式。
写代码时使用黑暗模式
为了在你写代码时预览在黑暗模式下的效果,你可以在preview 代码段中田间environment 修饰符。
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environment(\.colorScheme, .dark)
}
}
在暗黑模式下,Text 控件的字体会变为白色,但是背景依旧还是白色(在模拟器或者真机上不会存在这个问题)。可以将ContentView 放到ZStack 中然后设置背景色为黑色来解决这个小问题。
static var previews: some View {
ZStack {
Color(.black)
ContentView().environment(\.colorScheme, .dark)
}
}
小结
在这一章节中,我们通过使用Text , Button 等控件学习了Swift UI的基本代码结构,我们学会了如何使用TextField 获取用户的输入,还学会了如何为按钮增加点击事件。在这一过程中,我们完成了很有使用价值的一款app BMI计算器。
在下一章节中,我将会带你学习List 控件的使用。
|