概述
摘要:用UIKit制作一款游戏,学习整型变量、按钮、颜色和动作。
概念:@2x和@3x图像,资源目录,整型变量,双精度,浮点数,操作符(+=,++,--),UIButton,枚举,CALayer(图层),UIColor,随机数,动作,字符串插值(string interpolation),UIAlertController。
1.设置
2.设计你的布局
3.用UIButton和CALayer制作最简单的游戏
4.猜旗子:随机数字
5.从outlets到actions:IBAction和字符串插值
6.总结
设置
在这个项目中你要制作一款游戏呈现给用户几面随机生成的旗子然后让他们选择哪一面属于制定的国家。跟之前的大工程相比,这个要简单的多——毕竟你已经学过outlets,图像视图,数组和自动布局等。
(PS:如果你因认为都是历史或其他单调乏味的东西而跳过了project1,你就大错特错了。没有项目1的基础只会让这个项目变得十分苦难。)
众所周知,一种十分重要的学习方法是在不同的情况下多次使用你学过的东西,这样你的新知识才会真的进入大脑。这个项目就是以这个目的为出发点的:它并不复杂,就是为了给你机会来让你内化你已经学过了的东西。
现在,启动Xcode,新建一个项目,选择Single View Application然后点击下一步。名字是Project2,选择Swift(语言)和iPhone(设备)。点击下一步然后选择文件位置来保存。
设计你的布局
当我制作自己的app时,我发现设计用户界面是最简单的开始项目的方式。你的想法是否可行变得一目了然,而且它还会促使你去思考用户在使用时经历了怎样的流程。
简单视图应用程序模板就给了一个叫做ViewController的UIViewController和一个叫Main.storyboard的故事板,故事板中有我们的简单视图控制器。点击Main.storyboard来打开IB,你会看到一个空白的方框等你开始设计。
在我们的游戏中,我们将要提供给用户三面旗子和顶部导航条中被猜的国家的名字。导航条在哪?好吧,现在还没有。简单视图并不会直接提供一个导航控制器,但很简单:左键点击视图控制器的中间部分,然后进入Editor菜单,选择Embed In>Navigation Controller。
现在已经有导航控制器了,接下来往视图控制器中添加三个UIButton。这是个新类型,但显而易见就是个用户可以触碰的按钮。每个都要是200宽,100高,你可以在右上角的尺寸观察器中进行设置。
在iOS6等早期版本中,UIButton有白色的背景色和倒圆角所以是可以点击的,但iOS7扁平化之后就只剩文本了。没关系,很快就可以让它更有趣。
你可以通过快捷键Alt+Cmd+5直接进入尺寸观察器。先别管X轴方向的位置,但是Y方向三面旗子的位置分别是100,230和360。这样看上去他们均分了整个视图。
第二步是添加Auto Layout,这样我们就不用担心不同设备上的显示差异。现在规则还没有设置完成,但我希望这能让你知道Auto Layout有多好用。
我们会用一种新的方式来创建Auto Layout 规则。我们并不认为这种方法会比项目1中提到的方法要好,只是你需要在多种实现方法中选择一种最适合自己的。
选择顶部的按钮,从按钮中Ctrl拖拽出它自己的范围——比如上面的空白处。此时,白色区域会变成蓝色表示它将要被Auto Layout使用。
松开鼠标左键时会弹出一个窗口,其中是一系列的可能被创建的限制条件。有两条是我们需要注意的:Vertical Spacing to Top Layout Guide (跟顶部的距离)和Center Horizontally in Container(在容器正中间的位置上)。
创建多重限制时你有两种可选做法:你可以重复Ctrl拖拽两次来选择两个限制条件,又或者你可以在Ctrl拖拽之后,选择这个菜单中的内容之前按住shift,这样你就可以一次性选择多个限制条件。
第一面旗子完成了,在我们更深入之前,让我们给它增加些内容这样你就可以看到它是怎么工作的。
在项目1中,我们通过拖拽一个叫Content的文件夹到Xcode中来添加图像。这里我们也可以这么干,但我更想给你介绍另一个选项:Asset Catalogs(资源目录)。它们是iOS项目中高度优化过的引入和使用图像的方法,而且跟内容文件夹一样简单。
在你的Xcode项目中,选择名为Images.xcassets的文件。这不是一个真正的文件,而是我们Xcode默认的资源目录。如果你还没有下载相关文件,请先从GitHub中下载。
选择项目文件中所有的36面旗子,拖进Assets.xcassets中的AppIcon下面。这会创建12个新入口,每个代表一个国家。
虽然我很不喜欢改道,但这个很重要:iOS资源有三个尺寸:1x,2x和3x。1x的名字就是它常规的名字,比如hello.png,它被用在所有非视网膜屏设备上——即iPhone,iPhone 3G,iPhone3GS,iPad,iPad2和iPad Mini。
2x的尺寸有1x的两倍,而且有个@2x在它的扩展路径前面,比如[email protected]。这是用在视网膜屏设备上的——即iPhone4,4s,5,5s,6,iPad3,4,iPad Air和iPad Air2。
3x有1x的三倍大小,名字里带个@3x,比如[email protected],用于视网膜高清屏,现在就是iPhone6Plus。
显然根据设备分别载入不同尺寸的图是相当无聊的事情,所以iOS向我们展示出了它的魔法。首先,图像总是默认选择1x名字的。程序中你可以直接用hello代表hello的所有图像。iOS会根据用户的设备自动选择你提供的各种版本的图像。
第二个魔法是关于布局空间的。iOS中尺寸的单位是points(点),而不是pixels(像素)。非视网膜屏的iPhone是320x480的像素尺寸,而视网膜屏的是640x960(4和4s)和640x1136(5和5s)的像素尺寸。但是为了能让开发者们不用重写他们的代码,Apple去掉了这个伤痛——所有的设备都是320x480的点尺寸。所以,你可以认为点是一种虚拟尺寸,然后系统会根据设备的实际尺寸来重绘图像的尺寸。
这些很重要是因为当我们把图像放进我们的资源目录时,它们都是被自动放入各自的格子中,所以正确的命名很重要。
一旦图像引入完成,你就可以在代码或者IB中像调用其他文件一样使用它们。现在,回到故事板中,选择第一个按钮然后打开属性观察器(Alt+Cmd+4)。你会看到标题“Button”(就在Title:Plain下面),删掉它,点开往下数的第四行Image右边的下拉菜单,选择“us”。
完成这一步时,我们对按钮的限制也就完成了:它有Y坐标限制,X坐标限制还有高度和宽度的。继续把US国旗放进另外两个按钮中。
为了完成所有的Auto Layout限制,我们需要把中间和下面的按钮的限制也加上去。选中中间的按钮,Ctrl拖拽到第一个按钮上——不是到视图控制器中。松开后你会看到“Vertical Spacing(垂直距离)”和“Center X(X轴中心)”,两个都要勾选。第三个操作与第二个相同。
现在Auto Layout差不多完成了,你可能会注意到虽然我们让旗子都居中了,但好像看上去它们并没有发生任何的变化。这是因为你还没让IB去更新布局框架。
很简单——点击IB底部的四个按钮中的最右边的一个,名字是“Resolve Auto Layout issues”,然后会出现一个都是选项的菜单。在菜单的下半部分,你会看到一个灰色的All Views in View Controller,然后紧接着的就是一个而黑色的Update Frame。点击Update Frame,然后三个按钮一下就都会对齐真正的中心位置。
IB的最后一步操作是为我们的三个按钮添加outlet,这样我们可以在代码中引用它们。打开辅助编辑器,然后从第一个按钮上Ctrl拖拽到代码窗格中创建一个outlet叫button1,button2,button3也一样。
现在,IB部分已经彻底完成了。选择ViewController.swift返回标准编辑模式,现在开始写点代码。
用UIButton和CALayer制作最简单的游戏
我们会创建一个字符串数组来存放所有这个游戏要用到的国家,同时还要创建两个保存玩家当前得分的属性——毕竟这也算是个游戏。
我们从新属性开始。把下面这两行代码添加到ViewController.swift中你之前添加@IBOutlet的位置下面:
var countries = [String]()
var score = 0
第一行你在Project1中已经见过:创建一个名为countries的属性来存放一个新的字符串数组。第二行是新的,但意思很好猜:创建一个名为score的新属性并赋予0。这两行最终完成的是一样的事情,只是工作方式不太一样。
var a = 0 告诉Swift我们想把0放进a。0对于Swift来说是整型数据,也就是整数。556是个整型,1000000001也是个整型。3.14159不是,因为它不是整数。
var a = [String]() 意思是我们想把一个字符串数组放进a。这里的句法看上去有点奇怪是因为它同时声明了我们要的类型,也就是[String],还创建了它,括号表示调用了一个方法来创建了字符串数组。
这里你看到的是类型推导。类型推导意思是Swift会根据赋予的内容来推断变量/常量的类型。这意味着:a)你需要把正确的东西放进变量中,否则就会出现你没预料到的类型;b)你不能在后来改变想法,然后就把一个整型放进一个数组中;c)如果Swift没有推理对的话,你只能给一个明确的数据类型。
开始之前,这里有些类型推导的例子:
var score = 0
var score = 0.0
var score = "hello"
var score = ""
var score = ["hello"]
var score = ["hello", "world"]
你都知道这些score分别是什么类型吗?
尽可能的让Swift的类型推导去推导类型。如果你想表达的更清楚,你可以这样做:
var score: Double = 0 Swift看到0会认为你想要一个Int,但我们要求它变成一个Double。
var score: Float = 0.0Swift看到0.0会以为你想要一个Double,但我们强制让它变成一个Float。Double比Float的精度要高一个等级。
让我们来实践一下,首先,把我们有的国旗都放入到countries数组中,代码如下:
countries.append("estonia")
countries.append("france")
countries.append("germany")
countries.append("ireland")
countries.append("italy")
countries.append("monaco")
countries.append("nigeria")
countries.append("poland")
countries.append("russia")
countries.append("spain")
countries.append("uk")
countries.append("us")
还有种更高效的办法来添加,如下所示:
countries += ["estonia", "france", "germany", "ireland", "italy", "monaco", "nigeria", "poland", "russia", "spain", "uk", "us"]
这行代码做了两件事:首先它创建了一个新数组来存放所有的国旗,类型是字符串。然后使用了新的操作符+=。+=意思是将操作符右边的内容加上左边的得到一个结果,然后将这个结果赋值给左边的变量。这里的作用就是将右边的字符串数组加入到左边的字符串数组后面去。
这样我们就有了设置好的国旗数组,viewDidLoad()还有一行代码需要添加:
askQuestion()
这行代码调用了askQuestion()方法。现在它还不存在,所以Swift会抱怨它不存在。但它很快就会出现了。在我们要从数组中选取一些国旗放入到按钮中,以便用户选择正确的那面时会用到这个方法。
在viewDidLoad()下面添加这个新方法:
func askQuestion() {
button.setImage(UIImage(named: countries[0]), forState: .Normal)
button.setImage(UIImage(named: countries[1]), forState: .Normal)
button.setImage(UIImage(named: countries[2]), forState: .Normal)
}
第一行很简单,我们定义了一个新方法,没有参数。接下来的三行使用UIImage(named:),通过位置来读取数组,都是我们在project1中用过的。剩下的新东西有两个:
button1.setImage()把UIImage赋值给按钮。刚才我们使用了US国旗,但在askQuestion()被调用时它就会改变。
forState: .Normal setImage()方法的第二个参数:什么状态的按钮应该要改变?我们指定.Normal,意思是“按钮的标准状态”。
.Normal隐含了两个复杂的信息,都是需要你理解的。首先这是一个叫“枚举”的数据类型,比如你想象下按钮的三个状态:普通,高亮和无效。我们可以用0,1,2来代表这三个状态,但是这样很难编程——1是无效,还是高亮?
枚举让我们可以使用带有意义的名字。在0的地方我们可以写上.Normal,1写上.Disabled等等。这让代码更好写也更好懂,而且对运行表现没有任何影响,完美!
另一个隐藏信息是前面的“.”。为什么这里会有个点?这里我们是要给UIButton设定标题,所以我们需要给它指定一个按钮状态。但.Normal可能指向其他东西的任何数字,所以,Swift怎么知道我们指的是一个按钮的普通状态?
setImage()寻求的数据类型实际上是UIControlState,而Swift很聪明:它知道那儿需要一个UIControlState,所以当我们写.Normal时Swift就将它理解成“UIControlState的Normal值”。在Swift中,省略一些前缀很常见。
现在,游戏没有任何毛病,所以Cmd+R运行起来试试。你会注意到两个问题:1)Estonian和French国旗有一部分是全白的让人分不清哪里是国旗哪里是外面。2)游戏非常无趣,因为国旗不会发生变化!
我们先来解决第一个问题。iOS中的视图功能强大是因为有CALayer。它是一种核心动画的数据类型管理着视图的视觉效果。
概念上来说,CALayer在所有UIView(这是UIButton,UILabel等等的父类)的底层,所以它给你很多选项从底层上来调整视图的外表,只要你不介意用起来有那么一点复杂。现在我们就要使用其中的一个外表选项:borderWidth。
Estonian国旗底部有白色条纹跟视图完全融合。我们可以通过给予按钮图层宽度为1的边沿,即周围一个宽度为1的黑色线框来修复这个问题。在viewDidLoad()中askQuestion()之前加入以下代码:
button1.layer.borderWidth = 1
button2.layer.borderWidth = 1
button3.layer.borderWidth = 1
还记得点和像素之间的区别嘛?这里我们的边框是非视网膜屏上的1像素,视网膜屏上的2像素和视网膜高清屏上的3像素。感谢从点到像素的乘法运算,无论什么屏幕上这些边框看上去都会差不多。
默认条件下,CALayer的边框是黑色的,但你可以用UIColor数据类型来改变。我说过CALayer有点儿复杂,这里就是其中一点:CALayer是在UIButton的底下一层,所以它不知道什么是UIColor。UIButton知道是因为它跟UIColor都在同一技术层,但CALayer是下一层,所以UIColor就是个迷。
不要绝望:CALayer有它自己的设置颜色的方式:CGColor,来自Apple的核心图形处理框架。这同样是比UIButton低一层的数据类型,只要你觉得可以应对它的复杂性就好。甚至UIColor可以和CGColor轻松地相互转换,也就是说,你不需要担心其复杂性,哈哈!
好了,让我们把它们放到一块儿去改变边框的颜色。把下面的三行代码添加到viewDidLoad()中的borderWidth下面:
button1.layer.borderColor = UIColor.lightGrayColor().CGColor
button2.layer.borderColor = UIColor.lightGrayColor().CGColor
button3.layer.borderColor = UIColor.lightGrayColor().CGColor
如你所见,UIColor有个方法叫lightGrayColor 能返回一个UIColor实例代表亮灰色。但我们没办法把UIColor放进borderColor属性中因为它属于CALayer,而CALayer不知道啥是UIColor。所以我们在结尾处加上一个.CGColor让它自动转换成CGColor。完成。
如果你不喜欢亮灰色,你可以创建自己的颜色:
UIColor(red: 1.0, green: 0.6, blue: 0.2, alpha: 1.0).CGColor
你需要指定一些值:红色,绿色,蓝色还有透明度,都是从0~1.0。上面的代码产生一种橘色然后将其转换成CGColor这样它就能被赋值给CALayer的borderColor属性。
样式方面已经差不多了,该把它做成一个真正的游戏了……
猜国旗:随机数
现在的代码会选中countries数组中的前三个项目,然后把它们放到视图控制器的三个按钮中。从这里开始没问题,但每次我们都需要选择三个随机国家。有两种实现方法:
选三个随机数,然后读取数组中这三个位置的国家。
随机打乱数组中元素的顺序,然后选取前三个。
两种办法都可行,就是前一种稍微麻烦点,因为我们还得确保三个数字都是不一样的——如果三个都是French会很没劲!
第二种方法很简单,但这里有个需要注意的地方:我们将要使用一个新的iOS开发库——GameplayKit。随机数是个复杂的东西,而且很容易让你以为是完美随机化一个数组的代码产生一个可预测的顺序。所以,我们会用iOS9中新提供的GameplayKit库来替我们做这件事。
你可能会问,“为什么我想在app中用GameplayKit?”但理由很简单:它就在那儿,所有的设备都内建了它,而且你的项目都可以用。GameplayKit可以做的还有很多很多。
现在,在ViewController.swift的顶部你会看到一行代码:import UIKit。就在它的前面,添加一行新代码:
import GameplayKit
这下我们就可以开始使用GameplayKit为我们提供的功能了。在askQuestion()方法的开始,就在你第一个setImage()方法之前,添加一行代码:
countries = GKRandomSource.shareRandom().arrayByShufflingObjectsInArray(countries) as! [String]
这会自动将数组中的国家顺序随机化,意思是每次调用askQuestion()方法时,countries[0],countries[1],countries[2]所指向的国旗都改变。可以运行下试试。
下一步是追踪哪一个才是答案,要做这件事得先给视图控制器创建一个叫correctAnswer的新属性。把这个放在顶部,就在var score = 0上面:
var correctAnswer = 0
这个新的整型属性用来存储正确答案的国旗序号,0或1或2。
确定答案需要再次用到GameplayKit,因为答案也是随机数。GameplayKit有个特殊的方法来做这件事,叫nextIntWithUpperBound(),可以指定生成数的上限。GameplayKit会返回0和上限减一之间的一个整数,所以如果你想要0,1,2中间的一个数,你可以把上限设置为3。
把这些都放在一起产生你需要的0~2之间的随机数,你可以把它放在askQuestion()中三行setImage()调用下面:
correctAnswer = GKRandomSource.shareRandom().nextIntWithUpperBound(3)
现在我们有了正确答案,我们只需把它的文本放到导航栏就可以了。这可以由视图控制器的title属性完成,但我们还需要再加点东西:我们不想在导航栏里用小写的国家名,太丑了。我们可以大写首字母,但这对于US,UK来说还是不行。
办法很简单:所有字母都大写就好。这可以用任何字符串的uppercaseString属性来完成,所以我们需要做的是从countries数组的correctAnswer位置上读取答案,然后大写显示。把这一步加到askQuestion()方法的最后,就在correctAnswer后:
title = countries[correctAnswer].uppercaseString
现在你可以运行这个游戏来玩一下了:每次都会得到三面不同的国旗,需要指出的国旗的名字就显示在顶部。
当然,还差了一点东西:用户可以点击这些国旗按钮,但不会发生任何事情。让我们接下来修复它……
从输出口到动作:IBAction和字符串插值
我说过我们会回到IB,就是现在:我们要把“轻触(tap)”这个UIButton的动作跟一些代码连接。选中Main.storyboard,然后打开辅助编辑器。
警告:请仔细阅读下面的文本,我不想让你觉得很迷惑,因为挺绕的。
选中第一个按钮,然后直接Ctrl拖拽到下面的askQuestion()方法下面。如果没出错,你会看到一个提示“Insert Outlet,Action,or Outlet Collection”。一旦松开左键,创建outlet时同样的弹窗会出现,要点来了:不要选outlet!
在弹窗的顶部有个“Connection: Outlet”,我想要你把它改成Action,如果你选了Outlet,你就等于是给自己制造了大麻烦!
当你选择了Action,弹窗会发生一点变化。你还是要填一个名字,但现在你会看到Event field,而且类型栏从UIButton变成了AnyObject。请把类型改回UIButton,然后输入buttonTapped作为名字,然后点击连接。然后Xcode就会自动为你生成一下代码:
@IBAction func buttonTapped(sender: UIButton) {
}
同样的,左边的灰色带圈原点,表示它已经跟IB关联上了。
在我们细看它在做什么之前,我要你再做两个连接。这次有点不同,因为我们要把其他两个国旗按钮都连接到同一个buttonTapped()方法上。操作如下,选中剩下两个按钮,然后Ctrl拖拽到刚才的buttonTapped()方法上,整个方法会变蓝表示它即将被连接,松开左键就完成了。如果松手后方法闪烁了一下,表示连接已成功。
现在我们手里有了些什么?我们有一个方法名叫buttonTapped(),但是跟三个UIButton按钮连接着。附件使用的事件叫做TouchUpInside,iOS利用它来表示“用户点击了这个按钮,然后当手指还在上面时松开了手指。”——比如,按钮被触碰过了。
再说一次,Xcode在这一行的开头插入了一个属性所以知道它跟IB有关联,这次是@IBAction。@IBAction跟@IBOutlet很像,但是方向不同:@IBOutlet是把代码连接到故事板的布局中,而@IBAction是故事板布局触发代码的一种方式。
这个方法只有一个参数:sender。我们可以确定它属于UIButton类型是因为我们知道什么会调用这个方法。三个按钮都会调用同个方法,所以我们得知道哪个按钮被触碰了,这样我们就可以判断哪个答案是正确的。这很重要。
但是我们怎么知道正确的按钮是否被触碰了?现在,所有的按钮看起来一模一样,但场景后的所有视图控件都有一个特殊的识别号码,叫Tag,这是我们可以设置的。你可以设置成任何你想要的数字,我们选取0,1,2作为识别号。这不是巧合:我们的代码已经设置成把国旗0,1,2放进这些按钮中,所以如果我们给它们同样的识别号,我们就能知道哪面国旗被触碰了。
选择第二面国旗(不是第一面),然后在属性观察器中找到Tag。你需要拉下滑动条不然你不一定能找得到Tag,因为UIButton类型有很多属性!一旦你找到了,确保它被设置成1。
现在,选中第三面国旗然后把tag设置成2。我们不需要修改第一面国旗的tag因为它默认为0。
IB部分算是彻底完成了,现在返回标准编辑器并选择ViewController.swift——是时候补全buttonTapped()方法了。
这个方法需要做三件事:
1.检查答案是否正确。
2.修改玩家的分数,往上或者往下。
3.显示一个消息,提示玩家他们新的分数是多少。
第一个任务非常简单,因为每个按钮都有一个tag跟它在数组中的位置配对,而我们已经把正确答案的位置存放在correctAnswer变量中。所以,如果sender.tag等于correctAnswer,那么就表示答案正确。
第二个任务也很简单,因为你已经接触过+=操作符。我们要用它和它的反面(-=)来实现。
第三个任务比较复杂,所以我们等会儿处理。只需说明它使用了一种新的会给用户展示一个带有标题和他们当前的分数的消息窗口的数据类型就可以了。
把以下代码放入buttonTapped()方法中:
var title: String
if sender.tag == correctAnswer {
title = "Correct"
score += 1
} else {
title = "Wrong"
score -= 1
}
这里有两样新东西:
1. 我们使用了==操作符。等于操作符,比较左右两边的值是否相等。如果被触碰的按钮的tag等于我们在askQuestion()中保存的correctAnswer变量值,那等于操作符就会返回真。
2. 我们还有个else语句。当你写任何if条件句时,你打开了一个{,写些代码,然后给个},如果条件成立大括号中的代码就会被执行。但你也可以给Swift一些在条件不成立时执行的代码,也就是else代码块。这里,答案正确与否我们都会设置一个标题。
现在来到最难的环节:我们要使用一个新的数据类型UIAlertController()。这是在iOS8中新增的,用来弹出一个带选项的警报。要让它工作你还得学三个新东西,所以让我们先把三个新东西过一遍。
第一个是字符串插值。Swift的这个功能可以让你直接把变量和常量放进字符串中,而且当代码执行时它会跟着改变当前的指。现在,我们有一个整型变量score,所以我们得像这样把它放进一个字符串中:
let mytext = "Your score is \(score)."
如果score = 10,它就会说“Your score is 10.” 只需“\(”+变量+“)”,Swift就可以完成各种各样的字符串插值,详细的我们以后再说。
第二个是闭包(closure)。这是一种特殊的代码块,它可以被当成变量一样使用——Swift会复制整块代码这样晚点它就可以被调用。Swift也会复制任何被闭包引用的东西,所以你必须非常小心地使用。以后我们会大面积使用闭包,但现在我们先来看两个简单的简便方法。
全都是之前学过的,所以我们看下实际用到的代码。把下面的代码输入到buttonTapped()方法的最后:
let ac = UIAlertController(title: title, message: "Your score is \(score).", preferredStyle: .Alert)
ac.addAction(UIAlertAction(title: "Continue", style: .Default, handler: askQuestion))
presentViewController(ac, animated: true, completion: nil)
在if语句中,变量title被设置成不是“correct”就是“wrong”,而且你已经学过字符串插值了,所以这里的第一个新东西是preferredStyle使用的参数.Alert。如果你还记得UIButton的setImage()方法中的.Normal,你应该能认出这也是个枚举量。
在UIAlertController()中,有两种类型:.Alert是在屏幕中央弹出一个消息框;而.ActionSheet是从底部向上滑出各种选项。他们很相似,但Apple建议当你需要提醒用户状态改变时用.Alert,而在让用户选择一些选项时用.ActionSheet。
第二行用数据类型UIAlertAction来增加一个“Continue”按钮,并指定类型为“Default”。有三种可能的类型:.Default,.Cancel和.Destructive。它们的外观虽然是由iOS决定的,但小细节是由它们提供给用户的,所以最好合理地使用它们。
最难的是尾巴部分:handler: askQuestion。参数handler需要一个闭包,即当按钮被触碰时可以被执行的代码。你可以写一些自己的代码,但在这里我们希望按钮触碰以后继续游戏,所以我们把askQuestion放到这里,这样iOS就会调用askQuestion()方法。
警告:这里的askQuestion必须没有()。没有小括号表示“这是要调用的方法名”;而有小括号则会变成另外一种状况“现在调用askQuestion()方法,然后它会告诉你要运行的方法的名字。”
使用闭包有很多好处,但在这个例子中就是把askQuestion()传递进来——虽然还有些小问题需要修复。
最后一行调用了presentViewController(),它有三个参数:要呈现的视图控制器,是否带有动画效果,还有当动画效果结束时要被执行的另外一个闭包。第一个我们给的参数是UIAlertController,第二个是true(动画一直很酷炫!),然后是另外一个闭包的缩写:nil。这表示“啥都不做”,而且你会在使用闭包时用到很多!
在代码完成前,有个问题,就是Xcode可能会提示说:“UIAlertAction!不是()的子类型。”这是一个很好的Swift的严重错误消息的例子,也是今后要习惯的东西。它的意思是对闭包使用方法没问题,但Swift想要这个方法接受一个说明哪个UIAlertAction被触碰的UIAlertAction参数。
我们需要通过改变askQuestion()方法的定义来解决这个问题。所以翻回到之前askQuestion()被定义的地方,把
func askQuestion() {
改成:
func askQuestion(action: UIAlertAction!) {
这样就能修复UIAlertAction错误。但它还会带来另外一个问题:在app的一开始,viewDidLoad()调用了askQuestion(),而且没有参数。有两个解决办法:
1. 在viewDidLoad()中的askQuestion()的括号中加入nil,表示“这里没有UIAlertAction要调用”
2.也可以重新定义askQuestion(),让它的默认参数为nil,表示“没有特别说明时参数自动为nil”
两个方法没有对错好坏之分,所以两种我都会告诉你怎么操作。如果你想要用第一种解决办法,就把viewDidLoad()中的askQuestion()改成askQuestion(nil)。
如果你想用第二种方法,就把方法askQuestion()的定义行改成:
func askQuestion(action: UIAlertAction! = nil) {
好了,现在你可以在模拟器中运行你的程序了,因为已经完工了!
总结
项目依然很简单,但通过它你可以更详细地回顾一下学过的概念,外加一些其他的新概念。从另一种途径重新来过有利于学习,所以我希望你不会把这个游戏当成是浪费时间。
这次我们重新回顾了IB,Auto Layout,outlet和其他东西,还有新的比如@2x和@3x图像,资源目录,整型,双精度,单精度,操作符(+=和-=),UIButton,枚举,CALayer,UIColor,随机数,动作,字符串插值和UIAlertController等等,而且你还完成了一个游戏!
如果你想再多花点时间,看看你是否可以吧UILabel放在视图控制器上来显示玩家的分数,你需要用到text属性还有字符串插值。祝你好运!