你的第一个 Bug
在这个 App 里其实有个小问题,你可能已经发现了,那么如何当这个问题再现的时候该怎么办呢?
- 首先先按下暂停按钮来停止正在运行的这个 App,然后重新点击运行,不去拖动滑块,直接点击 Hit Me 按钮。这时,弹出来的提示信息是「The value of the slider is: 0」,但实际上滑块正处在中间的位置上(这是因为在我们设置 slider 的属性(Attribute)的时候默认值设置的为 50),所以我们期望的显式出来的数值也因该是 50。
练习:想一下为什么显式的值是 0,而不是 50?
答案:存储滑块数值的变量currentValue
是在sliderMoved()
方法里被赋值的,当你不移动滑块的时候,也就不会执行sliderMoved()
方法,所以currentValue
的值只是被定义时候的 0,即var currentValue: Int = 0
。
-
所以修改默认的被定义时候的
currentValue
的值就好了,var currentValue:Int = 50
-
验证一下吧,现在即便你不拖动滑块,直接点击按钮显式出来的值就是 50 啦。
让我们继续完善这个游戏吧
你已经构建好了 Interface Builder,也已经知道了如何获取和显式滑块的值。也花掉了几项 to-do list。还有一个要花时间做的事就是生成一个随机数当做拖动滑块的目标。首先要做的还是要提升关于滑块操作。
先试想这样一个问题,当你的滑动条的范围不是从 1 到 100 了,那在没有滑动滑块时候的取值也不会是 50 了。当你的项目越写越大,你要可能要在 ViewController 里关心数十个方法,很容易就会忘掉这些小的细节问题。所以我们要找一个更好的方法来处理滑块初始值的问题,几乎一劳永逸的解决问题。
于是我们在 ViewController.swift 中找到了这样一个方法 viewDidLoad()
,最初的时候它看起来是这样的:
override func viewDidLoad() {
super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib.
}
当你创建一个基于 Xcode 模板的应用时,这段代码早就已经在你的源码中了。这个方法是干什么的呢?一旦 View Controller 开始加载用户界面,在你还没有看到界面样子的时候,这个方法就已经通过 UIKit 发送给它消息了。你可以简单地理解为,在你打开 App 但还没看到界面的时候,这个方法就已经执行了。刚好获取滑块的值既要及时又不需要让用户看到,所以在这里添加获取滑块当前值的代码再适合不过了,现在你要在这里面添加一些东代码。
override func viewDidLoad(){
super.viewDidLoad()
currentValue = lroundf(slider.value)
}
- 尝试点击运行 App , 你会发现出现了错误提示「Build Failed」,紧跟着有错误的提示:「Error:Use of unresovlved identifier
slider
」。
这个问题是因为在 viewDidLoad()
方法并不知道 slider
是什么。那么问题来了,为甚 sliderMoved()
方法就没有出现这个问题呢?让我们再来看一眼 sliderMoved()
方法。
@IBAction func sliderMoved(_ slider: UISlider) {
currentValue = lroundf(slider.value)
}
在这里你做了同样的事情,采用了 lroundf()
方法,相当于采用四舍五入把slider.value
这样一个浮点数转换成了整数。然后将这个数存入进currentValue
这个变量中。这个为什么不在viewDidLoad()
里还可以正常起作用呢?
不同之处在于 slider 是所谓sliderMoved()
方法的参数。参数就是方法名称括号后面的内容,在这个案例里,就只有一个叫做 slider 的参数名称,它是一个 UISlider 类型的对象,可以接受 UISlider 对象发送过来的消息。
所以动作方法可以通过一个 UI 控制相关的参数,通过 UI 控件变化来触发方法。这也带来了一个便利性,当你想在方法中使用这个这个对象时,它就已经在那了。
所以当用户拖动滑块控件的时候,就会发生这一幕:UISlider 对象会说「 Hi ,view controller,我是 slider 对象,我刚刚被移动了,顺便说一下,这就是我的俩系方法,你可以在这得知关于我的一些消息」。
这里所谓的「联系方式」就是参数 slider 了。在你使用的时候 slider 就成了变量,在这里 slider 这个变量只能在这个方法内部用,换做术语来表达就是本地变量,也就意味着你不能在其他的地方使用这个 slider 变量。
本地变量
当我们初次提到变量概念的时候,讲到每个参数都有自己的声明周期,也就是它的范围。这个范围取决于你在程序的什么地方定义了这个变量。
在 Swift 里有三种可能的变量范围:
- 全局变量:也就是这个变量的生命周期贯穿整个 App 的生命周期,你也可以在任何地方使用这个变量。
- 实例变量:这就像
currentValue
一样,它的生命周期依赖它所属于的对象。 - 本地变量:这个就像是
sliderMoved()
方法的 slider 参数一样,它的生命周期只是存在于这个方法的运行时间。这个变量你也不可以应用在除此方法外的任何地方。
让我们来看一下 showAlert()
这个方法的上半部分:
@IBAction func showAlert() {
let message = "The value of the slider is: \(currentValue)"
let alert = UIAlertController(title: "Hello, World", message: message, preferredStyle: .alert)
let action = UIAlertAction(title: "OK", style: .default, handler: nil) . . .
像 message
、alert
、action
这样的变量都是在方法内被声明的,也只能用于这个方法内部,所以就是本地变量。一旦 showAlert()
这个方法执行完毕,这些内部变量也就被销毁了。
而像 currentValue
这样的变量生命周期就长得多了,随着 ViewController 对象的消失而消失(本例中就是要等到用户终结掉此 App)。这也就是所谓的实例变量,换句话说,如果你想从一个操作到下一个操作保存一些数据,就可以使用实例变量了。就像你用 currentValue
保存滑块的值以便在下一个操作中使用一样。除此之外,为了解决刚才在viewDidLoad()
中的错误,我们要采用一个特殊类型的实例变量——outlet。
添加以下代码到 ViewController.swift
@IBOutlet weak var slider: UISlider!
这一行代码在哪里其实并不是非常重要,只要把它放在 ViewController 类里就行了。我的习惯是通常会把 outlet 类型的变量跟实例变量放在一起。
这一行代码告诉 Interface Builder 你有一个和 UISlider 对象连接的名叫 slider 的变量。就像 Interface Builder 调用方法一样,它调用这些变量的出口。Interface Builder 只能看到标记 @IBOutlet 的变量。
现在切不着急理解 weak 和 感叹号的含义,我们将会在下一个教程理解是这些的必要性。现阶段只要记住声明 outlet 变量的格式是 @IBOutlet weak var ... !
- 打开 storyboard,按住 Ctrl 并单击 slider,先不需要拖拽到任何地方,松开鼠标,会弹出来一个 slider 的连接窗口。这个窗口和 Connection inspector 一样,我只是告诉你另一个代替的操作方法。
- 点击 New Referencing Outlet 的空心圆,然后拖动到 View Controller。

- 然后在弹出来的窗口中选择 slider 。
你现在已经成功将 storyboard 的 slider 和 view controller 的 slider 使用 outlet 连接起来了。
现在所有的设置都完成了,你可以在 view controller 里随心的使用 slider 变量并获取到它的准确值了。
- 在 Interface Builder 里修改 slider 的初始值,然后重新运行 App ,这时你看到的就不是 50 啦。
你可能早就注意到了那些以 //
开始的一行绿色的代码,也有 /*...*/
的一段都是绿色的代码。这就是注释啦,注释本身并不会被执行,通常注释是来起到解释作用的,解释一些方法的作用和参数,解释一些难以理解的代码的意图,注释最大的目的是为了自己和他人在日后的代码修复优化的过程中更加容易理解当时代码的含义。
快复习一下这一章节讲的内容吧,这一节数字稍微有点多,下一节我们会解决另一个 to-do list 的一个大事,如何生成随机数以及更多对于游戏的优化。
网友评论