讓不懂程式設計的人愛上iPhone開發(2018秋iOS12+Swift4.2+Xcode10版)-06
歡迎繼續回來一起學習iPhone開發。
熱身結束,讓我們真正來做遊戲吧!
到目前為止我們已經完成了基本的使用者介面,而且也學習瞭如何確定滑動條的位置,這樣我們的to-do清單上已經解決了一大部分內容。
剩下的主要事情就是生成目標隨機數,然後計算玩家的得分了。
不過在此之前讓我們先對滑動條做一些改進。
Outlets
這貨不是奧特萊斯這種土豪長逛的購物場所,更不是插座,而是某種介面。

在之前的學習中,我們已經學會了如何把滑動條的數值保持在變數中,然後將其顯示在提示對話方塊中。這個算是不小的進步了,不過我們還可以做的更好。
想想看,如果你想把storyboard裡面的滑動條初始數值設定為1或者100,又該怎麼辦呢?currentValue這個時候的數值就是錯的,因為應用始終假定它的初始值是50。
如果你的記憶力足夠好,最簡單的方法似乎是給currentValue賦予一個新的初始值。這樣似乎是理所當然的。
不過當你的專案越來越大時,裡面會有幾個,十幾個甚至更多的檢視控制器,你不可能一個個去手動修改。更可怕的是,你很可能忘了還有這個事情要去處理。
所以我們最好用下面的方法來處理:
回到Xcode,點選ViewController.swift,找到viewDidLoad()方法,其中的程式碼如下:
override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. }
當我們使用Xcode模板來建立新的專案時,Xcode就會自動把viewDidLoad()方法放進原始碼。這裡我們需要新增點新的程式碼。
當檢視控制器從storybaord檔案中載入使用者介面時,UIKit會立即傳送viewDidLoad()訊息。此時檢視控制器仍然不可見,因此在這裡設定變數的初始值會非常的合適。
更改viewDidLoad()的方法如下:
override func viewDidLoad() { super.viewDidLoad() currentValue = lroundf(slider.value) }
為什麼這樣來處理呢?因為這裡我們會直接獲取storyboard中滑動條的初始值(不管是50,1還是100),然後把它作為currentValue的初始值。
作為一個強迫症重度患者,如果編譯執行,你可能已經看到紅色的錯誤提示了,Use of unresolved identifier ‘slider’

腫麼會這樣?因為viewDidLoad()方法對slider這個東西一無所知。
且慢,之前在sliderMoved()方法裡面似乎用到過吧?不信你來看:
@IBAction func sliderMoved(slider: UISlider){ // println("滑動條的當前數值是: \(slider.value)") currentValue = lroundf(slider.value) }
這裡我們同樣用到了slider.value,我們對slider.value四捨五入,然後儲存到currentValue。那麼為毛在sliderMoved()方法中有用,但是在viewDidLoad()方法中沒用呢?
區別在於這裡的slider指的是sliderMoved()這個方法的引數。
@IBAction func sliderMoved(slider: UISlider){
所謂的引數就是方法名稱後面圓括號中的東西。這裡只有一個名為slider的引數,它代表的是用於傳送這個動作訊息的UISlider物件。
動作方法通常有一個引數,代表觸發該方法的UI控制元件。當我們需要在方法中使用該物件的時候,這種做法就會非常有用。
在我們這個例子裡,當用戶拖動滑動條時,UISlider滑動條物件會說,“檢視控制器你好~我是一個滑動條物件,剛才我被人動了。順便說一下,這是我的QQ號碼或微信號,你可以通過它來聯絡我。”
至於檢視控制器這個大哥會不會來給滑動條報仇,就不是我們要討論的事情了~
此時的slider變數包含了“QQ號碼或微訊號”,可惜的是它只在這個特定的方法中存活。
換句話說,它是一個本地變數(local)。一旦方法執行完畢,它的生命也就終結了。
科普時間:本地變數
之前我們介紹變數的時候,曾經提到過每一個變數都有特定的生命週期,又被成為scope(我朝NB人士喜歡叫作用域,也有叫範圍的)。一個變數的作用範圍取決於我們定義該變數的位置。
在Swift語言中有三種作用範圍水平:
1.Global scope 全域性範圍(或者全域性域,叫神馬不重要)。
此類變數可以在應用的整個生命週期裡面存活,而且可以從任何一個地方來訪問。
2.Instance scope 例項範圍
此類變數可以在物件的生命週期裡面存活,比如currentValue這個變數。這些變數也活的比較久,只要擁有它們的物件還活著,它就不會掛。
3.Local scope 本地範圍
比如sliderMoved()方法中的slider引數,它只能在方法體記憶體活。只要程式執行離開當前方法,這些本地變數就掛了。
讓我們看看showAlert()方法中的程式碼:
@IBAction func showAlert(){ let message = "滑動條的當前數值是: \(currentValue)" let alert = UIAlertController(title:"把油桶踢過來", message:message, preferredStyle: .alert) let action = UIAlertAction(title:"2019年要跟著學幾個拿手菜",style: .default, handler: nil) alert.addAction(action) present(alert, animated: true, completion: nil) }
因為message,alert和action物件是在方法體內建立的,因此它們都屬於本地變數。它們只能在showAlert()動作執行期間存活。
一旦showAlert()方法執行完畢,計算機就會銷燬message,alert和action物件。它們的儲存空間也會被釋放出來。
currentValue這個變數則是不老仙翁。事實上它幾乎是永生的,只要它賴以生存的ViewController小宇宙還在,它就會一直存在。這種型別的變數又被稱之為例項變數,因為它的作用範圍和它的主人是一樣的。

換句話說,我們可以用例項變數來儲存一個數值,而且在不同的動作方法中都可以使用。
因此,真正有效的方法是將滑動條的引用儲存到一個例項變數中,正如我們對currentValue所做的那樣。
只不過這一次的變數資料型別不是int(整數),而是UISlider(滑動條物件)。而且我們不是用的常規例項變數,而是一個特殊的例項變數,其型別是outlet。
回到Xcode,在ViewController.swift中新增如下的一行程式碼:
@IBOutlet weak var slider:UISlider!
那麼這行程式碼應該新增在哪裡呢?
其實並不重要,只要你確保這行程式碼在class ViewController 的花括號中,當然方便起見最好是緊接著class ViewController: UIViewController { 這行程式碼的下面。
修改後的程式碼如下:
// // ViewController.swift // BullsEye // // Created by eseedo on 11/29/18. // Copyright © 2018 eseedo. All rights reserved. // import UIKit class ViewController: UIViewController { //1.定義了一個outlet型別的slider變數 @IBOutlet weak var slider: UISlider! var currentValue: Int = 50 override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. currentValue = lroundf(slider.value) } @IBAction func showAlert(){ let message = "滑動條的當前數值是: \(currentValue)" let alert = UIAlertController(title:"把油桶踢過來", message:message, preferredStyle: .alert) let action = UIAlertAction(title:"2019年要跟著學幾個拿手菜",style: .default, handler: nil) alert.addAction(action) present(alert, animated: true, completion: nil) } @IBAction func sliderMoved(_ slider:UISlider){ // print("滑動條的當前數值是: \(slider.value)") currentValue = lroundf(slider.value) } }
以上程式碼中只有註釋編號為1的註釋和緊接著它的程式碼是新新增的,其它沒有任何變化。
這行程式碼告訴Interface Builder ,現在我們有一個名為slider的變數,它可以和某個UISlider物件關聯在一起。
正如Interface Builder喜歡將方法稱之為actions動作,而對這些變數習慣稱之為outlet。 Interface Builder會根據變數前面的@IBOutlet來判斷。
至於這行程式碼中的weak先不必緊張。在後面的教程中會向大家解釋為什麼要加這個奇怪的關鍵詞。現在只需要記住當我們建立一個outlet型別變數時,應該使用@IBOutlet weak var的形式。(好吧,最後還有個驚歎號比較難懂,有時你也會看到一個問號,這些東西會在後面來介紹。)
在新增完這行程式碼之後,可以看到在程式碼的最左側出現了一個空心圓(注意:下面的showAlert方法那裡是個實心圓)。
那麼這樣問題就解決了嗎?顯然不是。
點選工具欄上的編譯執行按鈕,會看到下面的錯誤提示:

那麼為什麼還是會報錯呢?
記住:一個outlet變數必須跟storyboard中的某個物件關聯在一起。我們剛剛定義了outlet變數,但是忘了將其和storyboard中的物件關聯,所以程式就崩潰了~
點選工具欄上的Stop停止按鈕停止編譯,然後 開啟storyboard,右鍵點選slider,會看到一個彈出選單,顯示了slider的所有關聯。
這個彈出選單的作用和Connections inspector完全相同。我只是希望告訴你還有這種替代方式。
點選New Referencing Outlet旁邊的空心圓,然後拖出一條線到ViewController:
在顯示的彈出式選單中選擇slider。

這個slider就是我們剛剛在程式碼中所新增的物件。這裡我們已經成功的將storyboard中的slider物件和檢視控制器中的slider outlet關聯在一起。
ok了,點選Run按鈕執行應用,直接觸碰按鈕,它會告訴你滑動條的當前數值是50。
關閉應用,切換到Interface Builder,更改滑動條的初始值,改成你喜歡的某個數值(注意儲存!)。再次執行應用,觸碰按鈕,你會看到提示對話方塊中的數值就是你更改後的初始值。
當然,玩過了之後,最好還是把滑動條的初始值改為50,這樣方便後面操作~
科普:關於錯誤和警告
在開發iOS應用時,Xcode有時候會給你發黃牌警告,嚴重的則會直接紅牌驅逐出場。黃色的警告資訊出現後,你還是可以執行自己的應用,只不過在未來的某個時候會發現自己的應用莫名崩潰。當你看到紅色的錯誤提示時,就表示你犯的錯是致命的,應用根本就沒辦法執行。
通常情況下,作為程式猿,我們要確保程式碼中沒有一行錯誤。但很多程式猿會選中忽略程式碼中的黃色警告,因為它們看起來不會讓程式直接崩潰。不過我個人的原則是,儘可能消滅一切的黃色警告。當然,這個也不是絕對的,在某些情況下我們也需要容忍黃色的警告。具體來說,當代碼中用到了第三方的類庫時,因為第三方類庫未必相容最新版本的iOS,就會導致大量的警告資訊。最典型的如cocos2d,每次iOS版本更新後,cocos2d的舊版本並不能保證第一時間支援最新的iOS類庫。
此外,當我們使用github,google code上的一些開源專案時,也會出現類似的情況。對於此類的警告,我們有兩種處理方式。一種是選擇使用較低版本的iOS,以消除警告;一種是等待最新版本的第三方類庫,以消除警告。還有第三種方式就是自己手動更改,但如果是cocos2d這樣的規模較大的第三方類庫,就必須慎重了。
但即便是此類情況所造成的警告,程式猿也應該在出現警告的地方說明原因,以及未來可能的解決方法,以及可能造成的bug,以便測試的時候來確認和改進。
如果你是產品經理,在程式猿提交的原始碼中發現了大量的黃色警告,而且沒有在出現警告的地方註釋說明是什麼原因造成的,就一定要打回去讓他們仔細再看看。不要被一大堆專業術語忽悠,不行就是不行,沒有什麼廢話。
0錯誤,0警告,是程式猿所必須追求的事情。即便當前版本不能實現,也一定要在後續的版本中糾正,而不能放任不管,最終釀成大錯。
順便提一下,到現在為止,如果你仔細觀察,會看到當前專案已經有10個黃牌警告:warning:了,之所以暫時還沒有處理,是因為這些警告全部都是跟自動佈局相關的,而不會影響遊戲的執行邏輯。當然到最後的時候我們也還是要逐漸解決的。
附贈小技巧
在結束之前,再附上一個使用Xcode的小技巧,相信對新老朋友都會有所幫助。這個小技巧是在WWDC2013上由蘋果的某個美女程式媛分享的。
假定你使用的是Macbook Air等螢幕相對比較小的Mac電腦,那麼會經常感覺自己的螢幕空間太小,特別是有多個檢視控制器的時候。
那麼Xcode提供了一個類似Safari 瀏覽器Tab的概念。也就是可以在一個介面中開啟多個子介面,然後可以輕鬆跳轉而互不影響。
具體是怎樣的呢?請看下圖。

首先,我會讓整個Xcode介面全屏顯示,從而focus在開發之中,而不受其它程式的影響。
其次,我使用Tab建立了多個子介面,分別用於專案導航,介面編輯和程式碼編輯。
建立Tab的方式是在Xcode中使用Command +T的快捷鍵,然後雙擊修改相關子介面的名稱即可。
注意到這裡的Tab就和瀏覽器開啟的不同網頁一樣可以隨意跳轉而不受影響,如果不再需要其中的某個子介面了,直接點x關掉就好了。
3.通過在Xcode的Preferences—>Behaviors裡面設定還可以設定動態的Tab,比如在進行編譯的時候單獨開啟一個Debug的子介面。

工欲善其事必先利其器,使用Tab來儘可能利用螢幕空間,在程式碼註釋區寫小說,就和之前設定程式碼區的背景和字型一樣,看似對程式設計沒有任何實際幫助,但實際上這些不起眼的舉動至少會讓你的程式設計工作沒那麼枯燥,甚至多一些意想不到的樂趣。當然,在程式碼註釋區寫小說神馬的還是算了,畢竟會影響到別人。不過如果是你自娛自樂或者學習的話,完全可以這麼做。
ok,在一大堆概念之後,又到了傳送福利的時間。

另外據說古劍奇譚3還不錯,有童鞋玩過的可以分享下~

本人聯絡方式:
微信:iseedo
郵件: ofollow,noindex" target="_blank">[email protected]
QQ討論群: 375143733
如有疑問,請先發送郵件到我的郵箱: [email protected]
我會在收到郵件後儘早答覆。
也可以加微信,但可能不是很合適的答疑途徑。
另外,為了節省大家的寶貴時間,提高溝通效率,請在提問的時候儘量附上 專案原始碼以及以下資訊 :
1.開發環境(系統版本,Xcode和iOS版本)
2.問題描述及重現(想實現什麼效果,結果是怎樣的,具體涉及到什麼操作)
3.為解決問題所做的努力(做了哪些嘗試,分別是怎樣的結果)