概述
摘要:用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在它的擴展路徑前面,比如hello@2x.png。這是用在視網膜屏設備上的——即iPhone4,4s,5,5s,6,iPad3,4,iPad Air和iPad Air2。
3x有1x的三倍大小,名字里帶個@3x,比如hello@3x.png,用于視網膜高清屏,現在就是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屬性還有字符串插值。祝你好運!