Project2:猜旗子

概述

摘要:用UIKit制作一款游戲刑顺,學(xué)習(xí)整型變量氯窍、按鈕、顏色和動(dòng)作蹲堂。

概念:@2x和@3x圖像狼讨,資源目錄,整型變量柒竞,雙精度政供,浮點(diǎn)數(shù),操作符(+=,++布隔,--)离陶,UIButton,枚舉衅檀,CALayer(圖層)招刨,UIColor,隨機(jī)數(shù)术吝,動(dòng)作计济,字符串插值(string interpolation),UIAlertController排苍。

1.設(shè)置

2.設(shè)計(jì)你的布局

3.用UIButton和CALayer制作最簡單的游戲

4.猜旗子:隨機(jī)數(shù)字

5.從outlets到actions:IBAction和字符串插值

6.總結(jié)


設(shè)置

在這個(gè)項(xiàng)目中你要制作一款游戲呈現(xiàn)給用戶幾面隨機(jī)生成的旗子然后讓他們選擇哪一面屬于制定的國家沦寂。跟之前的大工程相比,這個(gè)要簡單的多——畢竟你已經(jīng)學(xué)過outlets淘衙,圖像視圖传藏,數(shù)組和自動(dòng)布局等。

(PS:如果你因認(rèn)為都是歷史或其他單調(diào)乏味的東西而跳過了project1彤守,你就大錯(cuò)特錯(cuò)了毯侦。沒有項(xiàng)目1的基礎(chǔ)只會(huì)讓這個(gè)項(xiàng)目變得十分苦難。)

眾所周知具垫,一種十分重要的學(xué)習(xí)方法是在不同的情況下多次使用你學(xué)過的東西侈离,這樣你的新知識(shí)才會(huì)真的進(jìn)入大腦。這個(gè)項(xiàng)目就是以這個(gè)目的為出發(fā)點(diǎn)的:它并不復(fù)雜筝蚕,就是為了給你機(jī)會(huì)來讓你內(nèi)化你已經(jīng)學(xué)過了的東西卦碾。

現(xiàn)在,啟動(dòng)Xcode起宽,新建一個(gè)項(xiàng)目洲胖,選擇Single View Application然后點(diǎn)擊下一步。名字是Project2坯沪,選擇Swift(語言)和iPhone(設(shè)備)绿映。點(diǎn)擊下一步然后選擇文件位置來保存。


設(shè)計(jì)你的布局

當(dāng)我制作自己的app時(shí)腐晾,我發(fā)現(xiàn)設(shè)計(jì)用戶界面是最簡單的開始項(xiàng)目的方式叉弦。你的想法是否可行變得一目了然,而且它還會(huì)促使你去思考用戶在使用時(shí)經(jīng)歷了怎樣的流程赴魁。

簡單視圖應(yīng)用程序模板就給了一個(gè)叫做ViewController的UIViewController和一個(gè)叫Main.storyboard的故事板卸奉,故事板中有我們的簡單視圖控制器。點(diǎn)擊Main.storyboard來打開IB颖御,你會(huì)看到一個(gè)空白的方框等你開始設(shè)計(jì)榄棵。

在我們的游戲中凝颇,我們將要提供給用戶三面旗子和頂部導(dǎo)航條中被猜的國家的名字。導(dǎo)航條在哪疹鳄?好吧拧略,現(xiàn)在還沒有。簡單視圖并不會(huì)直接提供一個(gè)導(dǎo)航控制器瘪弓,但很簡單:左鍵點(diǎn)擊視圖控制器的中間部分垫蛆,然后進(jìn)入Editor菜單,選擇Embed In>Navigation Controller腺怯。

現(xiàn)在已經(jīng)有導(dǎo)航控制器了袱饭,接下來往視圖控制器中添加三個(gè)UIButton。這是個(gè)新類型呛占,但顯而易見就是個(gè)用戶可以觸碰的按鈕虑乖。每個(gè)都要是200寬,100高晾虑,你可以在右上角的尺寸觀察器中進(jìn)行設(shè)置疹味。

在iOS6等早期版本中,UIButton有白色的背景色和倒圓角所以是可以點(diǎn)擊的帜篇,但iOS7扁平化之后就只剩文本了糙捺。沒關(guān)系,很快就可以讓它更有趣笙隙。

你可以通過快捷鍵Alt+Cmd+5直接進(jìn)入尺寸觀察器洪灯。先別管X軸方向的位置,但是Y方向三面旗子的位置分別是100竟痰,230和360婴渡。這樣看上去他們均分了整個(gè)視圖。

第二步是添加Auto Layout凯亮,這樣我們就不用擔(dān)心不同設(shè)備上的顯示差異。現(xiàn)在規(guī)則還沒有設(shè)置完成哄尔,但我希望這能讓你知道Auto Layout有多好用假消。

我們會(huì)用一種新的方式來創(chuàng)建Auto Layout 規(guī)則。我們并不認(rèn)為這種方法會(huì)比項(xiàng)目1中提到的方法要好岭接,只是你需要在多種實(shí)現(xiàn)方法中選擇一種最適合自己的富拗。

選擇頂部的按鈕,從按鈕中Ctrl拖拽出它自己的范圍——比如上面的空白處鸣戴。此時(shí)啃沪,白色區(qū)域會(huì)變成藍(lán)色表示它將要被Auto Layout使用。

松開鼠標(biāo)左鍵時(shí)會(huì)彈出一個(gè)窗口窄锅,其中是一系列的可能被創(chuàng)建的限制條件创千。有兩條是我們需要注意的:Vertical Spacing to Top Layout Guide (跟頂部的距離)和Center Horizontally in Container(在容器正中間的位置上)。

創(chuàng)建多重限制時(shí)你有兩種可選做法:你可以重復(fù)Ctrl拖拽兩次來選擇兩個(gè)限制條件,又或者你可以在Ctrl拖拽之后追驴,選擇這個(gè)菜單中的內(nèi)容之前按住shift械哟,這樣你就可以一次性選擇多個(gè)限制條件。

第一面旗子完成了殿雪,在我們更深入之前暇咆,讓我們給它增加些內(nèi)容這樣你就可以看到它是怎么工作的。

在項(xiàng)目1中丙曙,我們通過拖拽一個(gè)叫Content的文件夾到Xcode中來添加圖像爸业。這里我們也可以這么干,但我更想給你介紹另一個(gè)選項(xiàng):Asset Catalogs(資源目錄)亏镰。它們是iOS項(xiàng)目中高度優(yōu)化過的引入和使用圖像的方法扯旷,而且跟內(nèi)容文件夾一樣簡單。

在你的Xcode項(xiàng)目中拆挥,選擇名為Images.xcassets的文件薄霜。這不是一個(gè)真正的文件,而是我們Xcode默認(rèn)的資源目錄纸兔。如果你還沒有下載相關(guān)文件惰瓜,請(qǐng)先從GitHub中下載。

選擇項(xiàng)目文件中所有的36面旗子汉矿,拖進(jìn)Assets.xcassets中的AppIcon下面崎坊。這會(huì)創(chuàng)建12個(gè)新入口,每個(gè)代表一個(gè)國家洲拇。

雖然我很不喜歡改道奈揍,但這個(gè)很重要:iOS資源有三個(gè)尺寸:1x,2x和3x赋续。1x的名字就是它常規(guī)的名字男翰,比如hello.png,它被用在所有非視網(wǎng)膜屏設(shè)備上——即iPhone纽乱,iPhone 3G蛾绎,iPhone3GS,iPad鸦列,iPad2和iPad Mini租冠。

2x的尺寸有1x的兩倍,而且有個(gè)@2x在它的擴(kuò)展路徑前面薯嗤,比如hello@2x.png顽爹。這是用在視網(wǎng)膜屏設(shè)備上的——即iPhone4,4s骆姐,5镜粤,5s捏题,6,iPad3繁仁,4涉馅,iPad Air和iPad Air2。

3x有1x的三倍大小黄虱,名字里帶個(gè)@3x稚矿,比如hello@3x.png,用于視網(wǎng)膜高清屏捻浦,現(xiàn)在就是iPhone6Plus晤揣。

顯然根據(jù)設(shè)備分別載入不同尺寸的圖是相當(dāng)無聊的事情,所以iOS向我們展示出了它的魔法朱灿。首先昧识,圖像總是默認(rèn)選擇1x名字的。程序中你可以直接用hello代表hello的所有圖像盗扒。iOS會(huì)根據(jù)用戶的設(shè)備自動(dòng)選擇你提供的各種版本的圖像跪楞。

第二個(gè)魔法是關(guān)于布局空間的。iOS中尺寸的單位是points(點(diǎn))侣灶,而不是pixels(像素)甸祭。非視網(wǎng)膜屏的iPhone是320x480的像素尺寸,而視網(wǎng)膜屏的是640x960(4和4s)和640x1136(5和5s)的像素尺寸褥影。但是為了能讓開發(fā)者們不用重寫他們的代碼池户,Apple去掉了這個(gè)傷痛——所有的設(shè)備都是320x480的點(diǎn)尺寸。所以凡怎,你可以認(rèn)為點(diǎn)是一種虛擬尺寸校焦,然后系統(tǒng)會(huì)根據(jù)設(shè)備的實(shí)際尺寸來重繪圖像的尺寸。

這些很重要是因?yàn)楫?dāng)我們把圖像放進(jìn)我們的資源目錄時(shí)统倒,它們都是被自動(dòng)放入各自的格子中寨典,所以正確的命名很重要。

一旦圖像引入完成房匆,你就可以在代碼或者IB中像調(diào)用其他文件一樣使用它們∧現(xiàn)在,回到故事板中坛缕,選擇第一個(gè)按鈕然后打開屬性觀察器(Alt+Cmd+4)。你會(huì)看到標(biāo)題“Button”(就在Title:Plain下面)捆昏,刪掉它赚楚,點(diǎn)開往下數(shù)的第四行Image右邊的下拉菜單,選擇“us”骗卜。

完成這一步時(shí)宠页,我們對(duì)按鈕的限制也就完成了:它有Y坐標(biāo)限制左胞,X坐標(biāo)限制還有高度和寬度的。繼續(xù)把US國旗放進(jìn)另外兩個(gè)按鈕中举户。

為了完成所有的Auto Layout限制烤宙,我們需要把中間和下面的按鈕的限制也加上去。選中中間的按鈕俭嘁,Ctrl拖拽到第一個(gè)按鈕上——不是到視圖控制器中躺枕。松開后你會(huì)看到“Vertical Spacing(垂直距離)”和“Center X(X軸中心)”,兩個(gè)都要勾選供填。第三個(gè)操作與第二個(gè)相同拐云。

現(xiàn)在Auto Layout差不多完成了,你可能會(huì)注意到雖然我們讓旗子都居中了近她,但好像看上去它們并沒有發(fā)生任何的變化叉瘩。這是因?yàn)槟氵€沒讓IB去更新布局框架。

很簡單——點(diǎn)擊IB底部的四個(gè)按鈕中的最右邊的一個(gè)粘捎,名字是“Resolve Auto Layout issues”薇缅,然后會(huì)出現(xiàn)一個(gè)都是選項(xiàng)的菜單。在菜單的下半部分攒磨,你會(huì)看到一個(gè)灰色的All Views in View Controller泳桦,然后緊接著的就是一個(gè)而黑色的Update Frame。點(diǎn)擊Update Frame咧纠,然后三個(gè)按鈕一下就都會(huì)對(duì)齊真正的中心位置蓬痒。

IB的最后一步操作是為我們的三個(gè)按鈕添加outlet,這樣我們可以在代碼中引用它們漆羔。打開輔助編輯器梧奢,然后從第一個(gè)按鈕上Ctrl拖拽到代碼窗格中創(chuàng)建一個(gè)outlet叫button1,button2演痒,button3也一樣亲轨。

現(xiàn)在,IB部分已經(jīng)徹底完成了鸟顺。選擇ViewController.swift返回標(biāo)準(zhǔn)編輯模式惦蚊,現(xiàn)在開始寫點(diǎn)代碼。


用UIButton和CALayer制作最簡單的游戲

我們會(huì)創(chuàng)建一個(gè)字符串?dāng)?shù)組來存放所有這個(gè)游戲要用到的國家讯嫂,同時(shí)還要?jiǎng)?chuàng)建兩個(gè)保存玩家當(dāng)前得分的屬性——畢竟這也算是個(gè)游戲蹦锋。

我們從新屬性開始。把下面這兩行代碼添加到ViewController.swift中你之前添加@IBOutlet的位置下面:

var countries = [String]()

var score = 0

第一行你在Project1中已經(jīng)見過:創(chuàng)建一個(gè)名為countries的屬性來存放一個(gè)新的字符串?dāng)?shù)組欧芽。第二行是新的莉掂,但意思很好猜:創(chuàng)建一個(gè)名為score的新屬性并賦予0。這兩行最終完成的是一樣的事情千扔,只是工作方式不太一樣憎妙。

var a = 0 告訴Swift我們想把0放進(jìn)a库正。0對(duì)于Swift來說是整型數(shù)據(jù),也就是整數(shù)厘唾。556是個(gè)整型褥符,1000000001也是個(gè)整型。3.14159不是抚垃,因?yàn)樗皇钦麛?shù)喷楣。

var a = [String]() 意思是我們想把一個(gè)字符串?dāng)?shù)組放進(jìn)a。這里的句法看上去有點(diǎn)奇怪是因?yàn)樗瑫r(shí)聲明了我們要的類型讯柔,也就是[String]抡蛙,還創(chuàng)建了它,括號(hào)表示調(diào)用了一個(gè)方法來創(chuàng)建了字符串?dāng)?shù)組魂迄。

這里你看到的是類型推導(dǎo)粗截。類型推導(dǎo)意思是Swift會(huì)根據(jù)賦予的內(nèi)容來推斷變量/常量的類型。這意味著:a)你需要把正確的東西放進(jìn)變量中捣炬,否則就會(huì)出現(xiàn)你沒預(yù)料到的類型熊昌;b)你不能在后來改變想法,然后就把一個(gè)整型放進(jìn)一個(gè)數(shù)組中湿酸;c)如果Swift沒有推理對(duì)的話婿屹,你只能給一個(gè)明確的數(shù)據(jù)類型。

開始之前推溃,這里有些類型推導(dǎo)的例子:

var score = 0

var score = 0.0

var score = "hello"

var score = ""

var score = ["hello"]

var score = ["hello", "world"]

你都知道這些score分別是什么類型嗎昂利?

盡可能的讓Swift的類型推導(dǎo)去推導(dǎo)類型。如果你想表達(dá)的更清楚,你可以這樣做:

var score: Double = 0 Swift看到0會(huì)認(rèn)為你想要一個(gè)Int,但我們要求它變成一個(gè)Double舌稀。

var score: Float = 0.0Swift看到0.0會(huì)以為你想要一個(gè)Double,但我們強(qiáng)制讓它變成一個(gè)Float扩所。Double比Float的精度要高一個(gè)等級(jí)。

讓我們來實(shí)踐一下朴乖,首先祖屏,把我們有的國旗都放入到countries數(shù)組中,代碼如下:

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"]

這行代碼做了兩件事:首先它創(chuàng)建了一個(gè)新數(shù)組來存放所有的國旗袁勺,類型是字符串。然后使用了新的操作符+=畜普。+=意思是將操作符右邊的內(nèi)容加上左邊的得到一個(gè)結(jié)果魁兼,然后將這個(gè)結(jié)果賦值給左邊的變量。這里的作用就是將右邊的字符串?dāng)?shù)組加入到左邊的字符串?dāng)?shù)組后面去。

這樣我們就有了設(shè)置好的國旗數(shù)組咐汞,viewDidLoad()還有一行代碼需要添加:

askQuestion()

這行代碼調(diào)用了askQuestion()方法。現(xiàn)在它還不存在儒鹿,所以Swift會(huì)抱怨它不存在化撕。但它很快就會(huì)出現(xiàn)了。在我們要從數(shù)組中選取一些國旗放入到按鈕中约炎,以便用戶選擇正確的那面時(shí)會(huì)用到這個(gè)方法植阴。

在viewDidLoad()下面添加這個(gè)新方法:

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)

}

第一行很簡單,我們定義了一個(gè)新方法圾浅,沒有參數(shù)掠手。接下來的三行使用UIImage(named:),通過位置來讀取數(shù)組狸捕,都是我們在project1中用過的喷鸽。剩下的新東西有兩個(gè):

button1.setImage()把UIImage賦值給按鈕。剛才我們使用了US國旗灸拍,但在askQuestion()被調(diào)用時(shí)它就會(huì)改變做祝。

forState: .Normal ?setImage()方法的第二個(gè)參數(shù):什么狀態(tài)的按鈕應(yīng)該要改變?我們指定.Normal鸡岗,意思是“按鈕的標(biāo)準(zhǔn)狀態(tài)”混槐。

.Normal隱含了兩個(gè)復(fù)雜的信息,都是需要你理解的轩性。首先這是一個(gè)叫“枚舉”的數(shù)據(jù)類型声登,比如你想象下按鈕的三個(gè)狀態(tài):普通,高亮和無效揣苏。我們可以用0悯嗓,1,2來代表這三個(gè)狀態(tài)舒岸,但是這樣很難編程——1是無效绅作,還是高亮?

枚舉讓我們可以使用帶有意義的名字蛾派。在0的地方我們可以寫上.Normal俄认,1寫上.Disabled等等。這讓代碼更好寫也更好懂洪乍,而且對(duì)運(yùn)行表現(xiàn)沒有任何影響眯杏,完美!

另一個(gè)隱藏信息是前面的“.”壳澳。為什么這里會(huì)有個(gè)點(diǎn)岂贩?這里我們是要給UIButton設(shè)定標(biāo)題,所以我們需要給它指定一個(gè)按鈕狀態(tài)巷波。但.Normal可能指向其他東西的任何數(shù)字萎津,所以卸伞,Swift怎么知道我們指的是一個(gè)按鈕的普通狀態(tài)?

setImage()尋求的數(shù)據(jù)類型實(shí)際上是UIControlState,而Swift很聰明:它知道那兒需要一個(gè)UIControlState锉屈,所以當(dāng)我們寫.Normal時(shí)Swift就將它理解成“UIControlState的Normal值”荤傲。在Swift中,省略一些前綴很常見颈渊。

現(xiàn)在遂黍,游戲沒有任何毛病,所以Cmd+R運(yùn)行起來試試俊嗽。你會(huì)注意到兩個(gè)問題:1)Estonian和French國旗有一部分是全白的讓人分不清哪里是國旗哪里是外面雾家。2)游戲非常無趣,因?yàn)閲觳粫?huì)發(fā)生變化绍豁!

我們先來解決第一個(gè)問題芯咧。iOS中的視圖功能強(qiáng)大是因?yàn)橛蠧ALayer。它是一種核心動(dòng)畫的數(shù)據(jù)類型管理著視圖的視覺效果妹田。

概念上來說唬党,CALayer在所有UIView(這是UIButton,UILabel等等的父類)的底層鬼佣,所以它給你很多選項(xiàng)從底層上來調(diào)整視圖的外表驶拱,只要你不介意用起來有那么一點(diǎn)復(fù)雜。現(xiàn)在我們就要使用其中的一個(gè)外表選項(xiàng):borderWidth晶衷。

Estonian國旗底部有白色條紋跟視圖完全融合蓝纲。我們可以通過給予按鈕圖層寬度為1的邊沿,即周圍一個(gè)寬度為1的黑色線框來修復(fù)這個(gè)問題晌纫。在viewDidLoad()中askQuestion()之前加入以下代碼:

button1.layer.borderWidth = 1

button2.layer.borderWidth = 1

button3.layer.borderWidth = 1

還記得點(diǎn)和像素之間的區(qū)別嘛税迷?這里我們的邊框是非視網(wǎng)膜屏上的1像素,視網(wǎng)膜屏上的2像素和視網(wǎng)膜高清屏上的3像素锹漱。感謝從點(diǎn)到像素的乘法運(yùn)算箭养,無論什么屏幕上這些邊框看上去都會(huì)差不多。

默認(rèn)條件下哥牍,CALayer的邊框是黑色的毕泌,但你可以用UIColor數(shù)據(jù)類型來改變。我說過CALayer有點(diǎn)兒復(fù)雜嗅辣,這里就是其中一點(diǎn):CALayer是在UIButton的底下一層撼泛,所以它不知道什么是UIColor。UIButton知道是因?yàn)樗鶸IColor都在同一技術(shù)層澡谭,但CALayer是下一層愿题,所以UIColor就是個(gè)迷。

不要絕望:CALayer有它自己的設(shè)置顏色的方式:CGColor,來自Apple的核心圖形處理框架潘酗。這同樣是比UIButton低一層的數(shù)據(jù)類型杆兵,只要你覺得可以應(yīng)對(duì)它的復(fù)雜性就好。甚至UIColor可以和CGColor輕松地相互轉(zhuǎn)換仔夺,也就是說拧咳,你不需要擔(dān)心其復(fù)雜性,哈哈囚灼!

好了,讓我們把它們放到一塊兒去改變邊框的顏色祭衩。把下面的三行代碼添加到viewDidLoad()中的borderWidth下面:

button1.layer.borderColor = UIColor.lightGrayColor().CGColor

button2.layer.borderColor = UIColor.lightGrayColor().CGColor

button3.layer.borderColor = UIColor.lightGrayColor().CGColor

如你所見灶体,UIColor有個(gè)方法叫l(wèi)ightGrayColor 能返回一個(gè)UIColor實(shí)例代表亮灰色。但我們沒辦法把UIColor放進(jìn)borderColor屬性中因?yàn)樗鼘儆贑ALayer掐暮,而CALayer不知道啥是UIColor蝎抽。所以我們在結(jié)尾處加上一個(gè).CGColor讓它自動(dòng)轉(zhuǎn)換成CGColor。完成路克。

如果你不喜歡亮灰色樟结,你可以創(chuàng)建自己的顏色:

UIColor(red: 1.0, green: 0.6, blue: 0.2, alpha: 1.0).CGColor

你需要指定一些值:紅色,綠色精算,藍(lán)色還有透明度瓢宦,都是從0~1.0。上面的代碼產(chǎn)生一種橘色然后將其轉(zhuǎn)換成CGColor這樣它就能被賦值給CALayer的borderColor屬性灰羽。

樣式方面已經(jīng)差不多了驮履,該把它做成一個(gè)真正的游戲了……


猜國旗:隨機(jī)數(shù)

現(xiàn)在的代碼會(huì)選中countries數(shù)組中的前三個(gè)項(xiàng)目,然后把它們放到視圖控制器的三個(gè)按鈕中廉嚼。從這里開始沒問題玫镐,但每次我們都需要選擇三個(gè)隨機(jī)國家。有兩種實(shí)現(xiàn)方法:

選三個(gè)隨機(jī)數(shù)怠噪,然后讀取數(shù)組中這三個(gè)位置的國家恐似。

隨機(jī)打亂數(shù)組中元素的順序,然后選取前三個(gè)傍念。

兩種辦法都可行矫夷,就是前一種稍微麻煩點(diǎn),因?yàn)槲覀冞€得確保三個(gè)數(shù)字都是不一樣的——如果三個(gè)都是French會(huì)很沒勁捂寿!

第二種方法很簡單口四,但這里有個(gè)需要注意的地方:我們將要使用一個(gè)新的iOS開發(fā)庫——GameplayKit。隨機(jī)數(shù)是個(gè)復(fù)雜的東西秦陋,而且很容易讓你以為是完美隨機(jī)化一個(gè)數(shù)組的代碼產(chǎn)生一個(gè)可預(yù)測的順序蔓彩。所以,我們會(huì)用iOS9中新提供的GameplayKit庫來替我們做這件事。

你可能會(huì)問赤嚼,“為什么我想在app中用GameplayKit旷赖?”但理由很簡單:它就在那兒,所有的設(shè)備都內(nèi)建了它更卒,而且你的項(xiàng)目都可以用等孵。GameplayKit可以做的還有很多很多。

現(xiàn)在蹂空,在ViewController.swift的頂部你會(huì)看到一行代碼:import UIKit俯萌。就在它的前面,添加一行新代碼:

import GameplayKit

這下我們就可以開始使用GameplayKit為我們提供的功能了上枕。在askQuestion()方法的開始咐熙,就在你第一個(gè)setImage()方法之前,添加一行代碼:

countries = GKRandomSource.shareRandom().arrayByShufflingObjectsInArray(countries) ? ?as! [String]

這會(huì)自動(dòng)將數(shù)組中的國家順序隨機(jī)化辨萍,意思是每次調(diào)用askQuestion()方法時(shí)棋恼,countries[0],countries[1]锈玉,countries[2]所指向的國旗都改變爪飘。可以運(yùn)行下試試拉背。

下一步是追蹤哪一個(gè)才是答案师崎,要做這件事得先給視圖控制器創(chuàng)建一個(gè)叫correctAnswer的新屬性。把這個(gè)放在頂部去团,就在var score = 0上面:

var correctAnswer = 0

這個(gè)新的整型屬性用來存儲(chǔ)正確答案的國旗序號(hào)抡诞,0或1或2。

確定答案需要再次用到GameplayKit土陪,因?yàn)榇鸢敢彩请S機(jī)數(shù)昼汗。GameplayKit有個(gè)特殊的方法來做這件事,叫nextIntWithUpperBound()鬼雀,可以指定生成數(shù)的上限顷窒。GameplayKit會(huì)返回0和上限減一之間的一個(gè)整數(shù),所以如果你想要0源哩,1鞋吉,2中間的一個(gè)數(shù),你可以把上限設(shè)置為3励烦。

把這些都放在一起產(chǎn)生你需要的0~2之間的隨機(jī)數(shù)谓着,你可以把它放在askQuestion()中三行setImage()調(diào)用下面:

correctAnswer = GKRandomSource.shareRandom().nextIntWithUpperBound(3)

現(xiàn)在我們有了正確答案,我們只需把它的文本放到導(dǎo)航欄就可以了坛掠。這可以由視圖控制器的title屬性完成赊锚,但我們還需要再加點(diǎn)東西:我們不想在導(dǎo)航欄里用小寫的國家名治筒,太丑了。我們可以大寫首字母舷蒲,但這對(duì)于US耸袜,UK來說還是不行。

辦法很簡單:所有字母都大寫就好牲平。這可以用任何字符串的uppercaseString屬性來完成堤框,所以我們需要做的是從countries數(shù)組的correctAnswer位置上讀取答案,然后大寫顯示纵柿。把這一步加到askQuestion()方法的最后蜈抓,就在correctAnswer后:

title = countries[correctAnswer].uppercaseString

現(xiàn)在你可以運(yùn)行這個(gè)游戲來玩一下了:每次都會(huì)得到三面不同的國旗,需要指出的國旗的名字就顯示在頂部昂儒。

當(dāng)然资昧,還差了一點(diǎn)東西:用戶可以點(diǎn)擊這些國旗按鈕,但不會(huì)發(fā)生任何事情荆忍。讓我們接下來修復(fù)它……


從輸出口到動(dòng)作:IBAction和字符串插值

我說過我們會(huì)回到IB,就是現(xiàn)在:我們要把“輕觸(tap)”這個(gè)UIButton的動(dòng)作跟一些代碼連接撤缴。選中Main.storyboard刹枉,然后打開輔助編輯器。

警告:請(qǐng)仔細(xì)閱讀下面的文本屈呕,我不想讓你覺得很迷惑微宝,因?yàn)橥@的。

選中第一個(gè)按鈕虎眨,然后直接Ctrl拖拽到下面的askQuestion()方法下面蟋软。如果沒出錯(cuò),你會(huì)看到一個(gè)提示“Insert Outlet嗽桩,Action岳守,or Outlet Collection”。一旦松開左鍵碌冶,創(chuàng)建outlet時(shí)同樣的彈窗會(huì)出現(xiàn)湿痢,要點(diǎn)來了:不要選outlet!

在彈窗的頂部有個(gè)“Connection: Outlet”扑庞,我想要你把它改成Action譬重,如果你選了Outlet,你就等于是給自己制造了大麻煩罐氨!

當(dāng)你選擇了Action臀规,彈窗會(huì)發(fā)生一點(diǎn)變化。你還是要填一個(gè)名字栅隐,但現(xiàn)在你會(huì)看到Event field塔嬉,而且類型欄從UIButton變成了AnyObject玩徊。請(qǐng)把類型改回UIButton,然后輸入buttonTapped作為名字邑遏,然后點(diǎn)擊連接佣赖。然后Xcode就會(huì)自動(dòng)為你生成一下代碼:

@IBAction func buttonTapped(sender: UIButton) {

}

同樣的,左邊的灰色帶圈原點(diǎn)记盒,表示它已經(jīng)跟IB關(guān)聯(lián)上了憎蛤。

在我們細(xì)看它在做什么之前,我要你再做兩個(gè)連接纪吮。這次有點(diǎn)不同俩檬,因?yàn)槲覀円哑渌麅蓚€(gè)國旗按鈕都連接到同一個(gè)buttonTapped()方法上。操作如下碾盟,選中剩下兩個(gè)按鈕棚辽,然后Ctrl拖拽到剛才的buttonTapped()方法上,整個(gè)方法會(huì)變藍(lán)表示它即將被連接冰肴,松開左鍵就完成了屈藐。如果松手后方法閃爍了一下,表示連接已成功熙尉。

現(xiàn)在我們手里有了些什么联逻?我們有一個(gè)方法名叫buttonTapped(),但是跟三個(gè)UIButton按鈕連接著检痰。附件使用的事件叫做TouchUpInside包归,iOS利用它來表示“用戶點(diǎn)擊了這個(gè)按鈕,然后當(dāng)手指還在上面時(shí)松開了手指铅歼」溃”——比如,按鈕被觸碰過了椎椰。

再說一次厦幅,Xcode在這一行的開頭插入了一個(gè)屬性所以知道它跟IB有關(guān)聯(lián),這次是@IBAction慨飘。@IBAction跟@IBOutlet很像慨削,但是方向不同:@IBOutlet是把代碼連接到故事板的布局中,而@IBAction是故事板布局觸發(fā)代碼的一種方式套媚。

這個(gè)方法只有一個(gè)參數(shù):sender缚态。我們可以確定它屬于UIButton類型是因?yàn)槲覀冎朗裁磿?huì)調(diào)用這個(gè)方法。三個(gè)按鈕都會(huì)調(diào)用同個(gè)方法堤瘤,所以我們得知道哪個(gè)按鈕被觸碰了玫芦,這樣我們就可以判斷哪個(gè)答案是正確的。這很重要本辐。

但是我們怎么知道正確的按鈕是否被觸碰了桥帆?現(xiàn)在医增,所有的按鈕看起來一模一樣,但場景后的所有視圖控件都有一個(gè)特殊的識(shí)別號(hào)碼老虫,叫Tag叶骨,這是我們可以設(shè)置的。你可以設(shè)置成任何你想要的數(shù)字祈匙,我們選取0忽刽,1,2作為識(shí)別號(hào)夺欲。這不是巧合:我們的代碼已經(jīng)設(shè)置成把國旗0跪帝,1,2放進(jìn)這些按鈕中些阅,所以如果我們給它們同樣的識(shí)別號(hào)伞剑,我們就能知道哪面國旗被觸碰了。

選擇第二面國旗(不是第一面)市埋,然后在屬性觀察器中找到Tag黎泣。你需要拉下滑動(dòng)條不然你不一定能找得到Tag,因?yàn)閁IButton類型有很多屬性缤谎!一旦你找到了聘裁,確保它被設(shè)置成1。

現(xiàn)在弓千,選中第三面國旗然后把tag設(shè)置成2。我們不需要修改第一面國旗的tag因?yàn)樗J(rèn)為0献起。

IB部分算是徹底完成了洋访,現(xiàn)在返回標(biāo)準(zhǔn)編輯器并選擇ViewController.swift——是時(shí)候補(bǔ)全buttonTapped()方法了。

這個(gè)方法需要做三件事:

1.檢查答案是否正確谴餐。

2.修改玩家的分?jǐn)?shù)姻政,往上或者往下。

3.顯示一個(gè)消息岂嗓,提示玩家他們新的分?jǐn)?shù)是多少汁展。

第一個(gè)任務(wù)非常簡單,因?yàn)槊總€(gè)按鈕都有一個(gè)tag跟它在數(shù)組中的位置配對(duì)厌殉,而我們已經(jīng)把正確答案的位置存放在correctAnswer變量中食绿。所以,如果sender.tag等于correctAnswer公罕,那么就表示答案正確器紧。

第二個(gè)任務(wù)也很簡單,因?yàn)槟阋呀?jīng)接觸過+=操作符楼眷。我們要用它和它的反面(-=)來實(shí)現(xiàn)铲汪。

第三個(gè)任務(wù)比較復(fù)雜熊尉,所以我們等會(huì)兒處理。只需說明它使用了一種新的會(huì)給用戶展示一個(gè)帶有標(biāo)題和他們當(dāng)前的分?jǐn)?shù)的消息窗口的數(shù)據(jù)類型就可以了掌腰。

把以下代碼放入buttonTapped()方法中:

var title: String

if sender.tag == correctAnswer {

? ? ?title = "Correct"

? ? ?score += 1

} else {

? ? ?title = "Wrong"

? ? ?score -= 1

}

這里有兩樣新東西:

1. 我們使用了==操作符狰住。等于操作符,比較左右兩邊的值是否相等齿梁。如果被觸碰的按鈕的tag等于我們在askQuestion()中保存的correctAnswer變量值催植,那等于操作符就會(huì)返回真。

2. 我們還有個(gè)else語句士飒。當(dāng)你寫任何if條件句時(shí)查邢,你打開了一個(gè){,寫些代碼酵幕,然后給個(gè)}扰藕,如果條件成立大括號(hào)中的代碼就會(huì)被執(zhí)行。但你也可以給Swift一些在條件不成立時(shí)執(zhí)行的代碼芳撒,也就是else代碼塊邓深。這里,答案正確與否我們都會(huì)設(shè)置一個(gè)標(biāo)題笔刹。

現(xiàn)在來到最難的環(huán)節(jié):我們要使用一個(gè)新的數(shù)據(jù)類型UIAlertController()芥备。這是在iOS8中新增的,用來彈出一個(gè)帶選項(xiàng)的警報(bào)舌菜。要讓它工作你還得學(xué)三個(gè)新東西萌壳,所以讓我們先把三個(gè)新東西過一遍。

第一個(gè)是字符串插值日月。Swift的這個(gè)功能可以讓你直接把變量和常量放進(jìn)字符串中袱瓮,而且當(dāng)代碼執(zhí)行時(shí)它會(huì)跟著改變當(dāng)前的指。現(xiàn)在爱咬,我們有一個(gè)整型變量score尺借,所以我們得像這樣把它放進(jìn)一個(gè)字符串中:

let mytext = "Your score is \(score)."

如果score = 10,它就會(huì)說“Your score is 10.” 只需“\(”+變量+“)”精拟,Swift就可以完成各種各樣的字符串插值燎斩,詳細(xì)的我們以后再說。

第二個(gè)是閉包(closure)蜂绎。這是一種特殊的代碼塊栅表,它可以被當(dāng)成變量一樣使用——Swift會(huì)復(fù)制整塊代碼這樣晚點(diǎn)它就可以被調(diào)用。Swift也會(huì)復(fù)制任何被閉包引用的東西师枣,所以你必須非常小心地使用谨读。以后我們會(huì)大面積使用閉包,但現(xiàn)在我們先來看兩個(gè)簡單的簡便方法坛吁。

全都是之前學(xué)過的劳殖,所以我們看下實(shí)際用到的代碼铐尚。把下面的代碼輸入到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被設(shè)置成不是“correct”就是“wrong”哆姻,而且你已經(jīng)學(xué)過字符串插值了宣增,所以這里的第一個(gè)新東西是preferredStyle使用的參數(shù).Alert。如果你還記得UIButton的setImage()方法中的.Normal矛缨,你應(yīng)該能認(rèn)出這也是個(gè)枚舉量爹脾。

在UIAlertController()中,有兩種類型:.Alert是在屏幕中央彈出一個(gè)消息框箕昭;而.ActionSheet是從底部向上滑出各種選項(xiàng)灵妨。他們很相似,但Apple建議當(dāng)你需要提醒用戶狀態(tài)改變時(shí)用.Alert落竹,而在讓用戶選擇一些選項(xiàng)時(shí)用.ActionSheet泌霍。

第二行用數(shù)據(jù)類型UIAlertAction來增加一個(gè)“Continue”按鈕,并指定類型為“Default”述召。有三種可能的類型:.Default朱转,.Cancel和.Destructive。它們的外觀雖然是由iOS決定的积暖,但小細(xì)節(jié)是由它們提供給用戶的藤为,所以最好合理地使用它們。

最難的是尾巴部分:handler: askQuestion夺刑。參數(shù)handler需要一個(gè)閉包缅疟,即當(dāng)按鈕被觸碰時(shí)可以被執(zhí)行的代碼。你可以寫一些自己的代碼遍愿,但在這里我們希望按鈕觸碰以后繼續(xù)游戲存淫,所以我們把a(bǔ)skQuestion放到這里,這樣iOS就會(huì)調(diào)用askQuestion()方法错览。

警告:這里的askQuestion必須沒有()。沒有小括號(hào)表示“這是要調(diào)用的方法名”煌往;而有小括號(hào)則會(huì)變成另外一種狀況“現(xiàn)在調(diào)用askQuestion()方法倾哺,然后它會(huì)告訴你要運(yùn)行的方法的名字」舨保”

使用閉包有很多好處羞海,但在這個(gè)例子中就是把a(bǔ)skQuestion()傳遞進(jìn)來——雖然還有些小問題需要修復(fù)。

最后一行調(diào)用了presentViewController()曲管,它有三個(gè)參數(shù):要呈現(xiàn)的視圖控制器却邓,是否帶有動(dòng)畫效果,還有當(dāng)動(dòng)畫效果結(jié)束時(shí)要被執(zhí)行的另外一個(gè)閉包院水。第一個(gè)我們給的參數(shù)是UIAlertController腊徙,第二個(gè)是true(動(dòng)畫一直很酷炫简十!),然后是另外一個(gè)閉包的縮寫:nil撬腾。這表示“啥都不做”螟蝙,而且你會(huì)在使用閉包時(shí)用到很多!

在代碼完成前民傻,有個(gè)問題胰默,就是Xcode可能會(huì)提示說:“UIAlertAction!不是()的子類型漓踢∏J穑”這是一個(gè)很好的Swift的嚴(yán)重錯(cuò)誤消息的例子,也是今后要習(xí)慣的東西喧半。它的意思是對(duì)閉包使用方法沒問題奴迅,但Swift想要這個(gè)方法接受一個(gè)說明哪個(gè)UIAlertAction被觸碰的UIAlertAction參數(shù)。

我們需要通過改變askQuestion()方法的定義來解決這個(gè)問題薯酝。所以翻回到之前askQuestion()被定義的地方半沽,把

func askQuestion() {

改成:

func askQuestion(action: UIAlertAction!) {

這樣就能修復(fù)UIAlertAction錯(cuò)誤。但它還會(huì)帶來另外一個(gè)問題:在app的一開始吴菠,viewDidLoad()調(diào)用了askQuestion()者填,而且沒有參數(shù)。有兩個(gè)解決辦法:

1. 在viewDidLoad()中的askQuestion()的括號(hào)中加入nil做葵,表示“這里沒有UIAlertAction要調(diào)用”

2.也可以重新定義askQuestion()占哟,讓它的默認(rèn)參數(shù)為nil,表示“沒有特別說明時(shí)參數(shù)自動(dòng)為nil”

兩個(gè)方法沒有對(duì)錯(cuò)好壞之分酿矢,所以兩種我都會(huì)告訴你怎么操作榨乎。如果你想要用第一種解決辦法,就把viewDidLoad()中的askQuestion()改成askQuestion(nil)瘫筐。

如果你想用第二種方法蜜暑,就把方法askQuestion()的定義行改成:

func askQuestion(action: UIAlertAction! = nil) {

好了,現(xiàn)在你可以在模擬器中運(yùn)行你的程序了策肝,因?yàn)橐呀?jīng)完工了肛捍!


總結(jié)

項(xiàng)目依然很簡單,但通過它你可以更詳細(xì)地回顧一下學(xué)過的概念之众,外加一些其他的新概念拙毫。從另一種途徑重新來過有利于學(xué)習(xí),所以我希望你不會(huì)把這個(gè)游戲當(dāng)成是浪費(fèi)時(shí)間棺禾。

這次我們重新回顧了IB缀蹄,Auto Layout,outlet和其他東西,還有新的比如@2x和@3x圖像缺前,資源目錄蛀醉,整型,雙精度诡延,單精度滞欠,操作符(+=和-=),UIButton肆良,枚舉筛璧,CALayer,UIColor惹恃,隨機(jī)數(shù)夭谤,動(dòng)作,字符串插值和UIAlertController等等巫糙,而且你還完成了一個(gè)游戲朗儒!

如果你想再多花點(diǎn)時(shí)間,看看你是否可以吧UILabel放在視圖控制器上來顯示玩家的分?jǐn)?shù)参淹,你需要用到text屬性還有字符串插值醉锄。祝你好運(yùn)!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末浙值,一起剝皮案震驚了整個(gè)濱河市恳不,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌开呐,老刑警劉巖烟勋,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異筐付,居然都是意外死亡卵惦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門瓦戚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沮尿,“玉大人,你說我怎么就攤上這事较解⌒蠹玻” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵哨坪,是天一觀的道長庸疾。 經(jīng)常有香客問我乍楚,道長当编,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任徒溪,我火速辦了婚禮忿偷,結(jié)果婚禮上金顿,老公的妹妹穿的比我還像新娘。我一直安慰自己鲤桥,他們只是感情好揍拆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著茶凳,像睡著了一般嫂拴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贮喧,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天筒狠,我揣著相機(jī)與錄音,去河邊找鬼箱沦。 笑死辩恼,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的谓形。 我是一名探鬼主播灶伊,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼寒跳!你這毒婦竟也來了聘萨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤冯袍,失蹤者是張志新(化名)和其女友劉穎匈挖,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體康愤,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡儡循,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了征冷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片择膝。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖检激,靈堂內(nèi)的尸體忽然破棺而出肴捉,到底是詐尸還是另有隱情,我是刑警寧澤叔收,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布齿穗,位于F島的核電站,受9級(jí)特大地震影響饺律,放射性物質(zhì)發(fā)生泄漏窃页。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望脖卖。 院中可真熱鬧乒省,春花似錦、人聲如沸畦木。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽十籍。三九已至蛆封,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間勾栗,已是汗流浹背娶吞。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留械姻,地道東北人妒蛇。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像楷拳,于是被迫代替她去往敵國和親绣夺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫欢揖、插件陶耍、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,102評(píng)論 4 62
  • 我在小姑子的建龍保健品分公司,認(rèn)識(shí)從天津來重慶墊江授課的劉教授她混。他是天津人民第二附屬醫(yī)院的退休老人烈钞,被天津建龍保健...
    吉英子閱讀 792評(píng)論 0 2
  • 或許,最美的事不是留住時(shí)光坤按,而是留住記憶毯欣,如最初相識(shí)的感覺一樣,哪怕一個(gè)不經(jīng)意的笑容臭脓,便是我們最懷念的故事酗钞。但愿,...
    殘花易敗閱讀 323評(píng)論 0 0
  • 昨天聽朋友談他女兒来累,今年考上一所重點(diǎn)高中砚作。還在放暑假,學(xué)校就給她布置了一大堆作業(yè)嘹锁。這些作業(yè)里面有一項(xiàng)是背誦《老子》...
    今墨閱讀 289評(píng)論 0 3