Swift語言Storyboard教程:第二部

本文由CocoaChina翻譯小組@TurtleFromMars翻譯自raywenderlich,原文:Storyboards Tutorial in Swift: Part 2swift

更新記錄:該Storyboard教程由Caroline Begbie更新iOS 8和Swift相關內容。原文做者爲教程編纂組的成員Matthijs Hollemans數組

2014/12/5更新:更新至 Xcode 6.2 Beta。app

若是你想學習Storyboard,你來對地方了!編輯器

本系列Storyboard教程的第一部分,咱們已經學習瞭如何使用Interface Builder建立並鏈接不一樣的視圖控制器,還有如何直接在Storyboard編輯器中建立自定義表項。ide

本教程的第二部分,也是最終部分,內容包括segue(轉場),static table view cell(靜態表項),添加玩家頁面和遊戲選擇頁面!函數

咱們從上部分結束的地方開始,請先打開以前的項目,或者下載上半部分教程的示例代碼工具

好,如今讓咱們一塊兒探索Storyboard的其餘酷炫特性吧!佈局

轉場(Segue)性能

讓咱們向Storyboard中繼續添加視圖控制器,建立一個讓用戶添加新玩家的頁面。學習

打開Main.storyboard,在包含表視圖的那個Players場景的導航欄右側拖入一個Bar Button Item(欄按鈕項),在屬性檢查器中將Identifier設爲Add,使其成爲標準添加(加號)按鈕。

01.png

當用戶點按這個按鈕時,你但願App會彈出一個模態頁面讓用戶輸入新玩家的詳細信息。

在Players場景的右邊拖入一個新的Navigation Controller(導航控制器)。記得雙擊面板能夠縮放畫面騰出空間。新加入的導航控制器附帶一個表視圖控制器,很方便。

這裏有個小技巧:選擇剛纔在Players頁面里加入的加號按鈕,按住control鍵把它拖向新建的導航控制器,鬆手,在彈出的小選單中選擇modal(模態)。

還記得嗎:當Storyboard面板處於縮小狀態時,沒法添加或修改內容。若是在建立轉場時遇到問題,請嘗試雙擊放大!

02.gif

如今Players頁面和導航控制器之間多了一個新箭頭。

03.png

這種鏈接的類型叫作segue(轉場,讀做seg-way,源自電影術語,原指兩個場景間的過渡銜接),表示一個頁面到另外一個頁面的過渡。此前咱們所見的Storyboard鏈接描述的都是視圖控制器的包含關係,而轉場是用來切換頁面的。轉場能夠由點擊按鈕、表項、手勢等條件觸發。

使用轉場的好處是,不再用爲呈現新頁面寫代碼了,也不用把按鈕鏈接到IBAction方法上,你只須要在Storyboard中從一個欄按鈕項拖到下一個頁面就能夠建立過渡了。(注:若是你的控件已經綁定了IBAction鏈接,該鏈接會被轉場屏蔽。)

運行App,點擊加號按鈕,一個新的表視圖會從屏幕下方滑入。

04.gif

這就是所謂的模態轉場。新頁面徹底覆蓋原頁面,在關閉模態頁面以前,用戶只能在新頁面進行交互。後面咱們還會看到push(入棧)轉場,這種轉場會把新頁面壓入導航控制器的導航棧(navigation stack)。

如今新頁面還沒什麼用,連關閉頁面返回都作不到,有去無回,由於轉場是單向操做。

爲返回頁面,Storyboard提供了unwind(回退)轉場。接下來咱們要實現返回功能,主要分三個步驟:

1.  建立讓用戶點選的控件,一般是個按鈕。

2.  在你想返回的控制器建立回退方法。

3.  在Storyboard中將控件與回退方法鏈接。

首先打開Main.storyboard,選擇新的表視圖控制器場景(叫「Root View Controller」的那個)。雙擊導航欄,把標題改爲「Add Player」。而後在導航欄添加兩個欄按鈕項,在屬性檢查器中設置左側按鈕的Identifier爲Cancel,右側按鈕爲Done,並將右側按鈕的Style改爲Done。

05.png

接下來在項目中用Cocoa Touch Class模板添加一個新文件,命名爲PlayerDetailsViewController並令其繼承`UITableViewController`。要把這個類關聯到Storyboard,先切回Main.storyboard,選擇添加玩家的場景,而後在身份檢查器(Identity inspector)中設Class爲PlayerDetailsViewController。這個步驟我常常忘掉,在此特意提醒,還請讀者牢記。

如今終於能夠建立回退轉場了。在PlayersViewController.swift(不是detail那個)的類定義下面添加以下的回退方法:

1
2
3
4
5
6
7
@IBAction func cancelToPlayersViewController(segue:UIStoryboardSegue) {
   dismissViewControllerAnimated( true , completion: nil)
}
  
@IBAction func savePlayerDetail(segue:UIStoryboardSegue) {
   dismissViewControllerAnimated( true , completion: nil)
}

這兩個方法在調用時都會解除這個控制器。後面你會改寫`savePlayerDetail`,讓它名副其實地履行本身的職責。

最後回到Interface Builder,把Cancel按鈕和Done按鈕鏈接到相應的action方法上。按住control從欄按鈕拖到視圖控制器上面的出口(exit)對象上,而後從彈出的選單中選擇正確的action名稱。

06_sb2_connectdone2.gif

記住取消方法的方法名,建立回退轉場時,App中的全部回退方法(形如`@IBAction func methodname(segue:UIStoryboardSegue)`)都會在列表中顯示,因此命名方法時要多加註意,避免混淆。

運行App,點擊加號按鈕,而後測試Cancel和Done按鈕。僅僅幾行代碼就能夠實現如此功能。

靜態表項(Static Cell)

完成這部分後,添加玩家頁面會像這樣:

07.jpg

固然這是一個分組表視圖(grouped table view),但沒必要爲該表建立數據源,也沒必要爲此編寫`cellForRowAtIndexPath`方法,你能夠直接在Interface Builder中完成設計。這個特性叫作靜態表項(static cell)。

選中Add Player場景的表視圖,在屬性檢查器中設Content爲Static Cells,把Style由Plain改爲Grouped,併爲表視圖設置兩個分段(section)。

08.png

修改Sections屬性值時,編輯器會複製已有的分段。(你也能夠在左側的文檔大綱中選擇特定分段並複製。)

最終頁面每一個分段應該只有一行,請在面板或文檔大綱中選中並刪除多餘的表項。

在文檔大綱中選擇最上面的表視圖分段,在屬性選擇器中設Header字段值爲Player Name。

11_sb2_staticcellsheader.png

向該分段內拖入一個新的Text Field(文本字段),橫向拉長並移除邊框,使文本字段控件融入周圍環境。設字體爲_System 17.0_ ,勾掉Adjust to Fit選項。

textField-on-cell-480x253.png

接下來咱們要用Xcode的Assistant Editor(輔助編輯器)功能爲該文本字段在`PlayerDetailsViewController`中建立一個outlet。在Storyboard中,點擊工具欄上的按鈕(圖標是兩個套在一塊兒的圓圈)打開輔助編輯器,應該會自動打開PlayerDetailsViewController.swift(若是沒有,在右側的跳轉欄中選擇相應文件)。

選擇新建的文本字段,按住control拖到swift文件的類定義下面。在彈出框中將新outlet命名爲nameTextField並點擊Connect。在點擊Connect後Xcode會在PlayersDetailViewController類中添加屬性並在Storyboard中創建鏈接:

12_sb2_dragoutlet.gif

爲表項上的視圖建立outlet對於原型表項來講可能會遇到問題,這在上一部分的教程中提到過,不過靜態表項就沒必要擔憂了,由於每一個靜態表項都只會有惟一的實例,把子視圖與視圖控制器的outlet鏈接徹底沒問題。

把第二分段的靜態表項的Style設爲Right Detail,這會套用一個標準表項樣式,雙擊左側的label,把文本改成Game,而後爲該表項設定Disclosure Indicator(展開方向標)附件。

12_sb2_gamecell.png

仿照剛纔的Name文本字段,爲右面的label("Detail"的那個)建立outlet並命名爲detailLabel,該表項上的label都是常規`UILabel`對象。在創建鏈接前選擇Detail文本字段時可能須要屢次點擊,請確保選擇的是label而不是整個表項。完成後如圖:

13_sb2_selecdetaillabel-700x223.png

添加玩家頁面的最終設計效果如圖:

14_sb2_staticcellsfinal.png

目前在Storyboard中設計的頁面尺寸都符合iPhone 5的4英寸屏幕,高度爲568點。固然你的App應當在不一樣的屏幕尺寸下正常工做,你能夠在Storyboard中預覽全部的尺寸。

在工具欄上點開輔助編輯器,選擇跳轉欄中的Preview。點擊輔助編輯器左下角的加號添加新的預覽尺寸,若是想刪除一個屏幕尺寸,選中並按delete鍵便可。

15_screen_preview-405x500.png

一個簡單的評分App不須要什麼花哨的東西,只是使用表視圖控制器,頁面自動縮放以填滿屏幕空間。當你想爲不一樣的屏幕尺寸適配佈局時,你須要使用Auto Layout和Size Classes。

構建並運行App,你會注意到添加玩家頁面依然是空白!

16.png

表視圖控制器在使用靜態表項時不須要數據源,而以前你用Xcode模板建立的`PlayerDetailsViewController`類中依然有部分數據源相關代碼,靜態表項所以沒法正常工做,因此靜態內容沒有顯示出來。咱們這就來解決問題!

打開PlayerDetailsViewController.swift文件,刪除這一條代碼往下的全部內容(注意不要刪掉類本身的括號):

1
// MARK: - Table view data source

如今,自從加入這個類之後Xcode顯示的那幾條警告(warning)也應該消失了。

運行App,檢查使用靜態表項的新頁面。徹底沒有寫代碼,其實剛纔還刪了一段代碼!

還要了解一點:靜態表項只在`UITableViewController`中有效,雖然Interface Builder容許你在常規`UIViewController`中的表視圖對象裏添加靜態表項,運行時不會發揮做用,緣由是`UITableViewController`中額外實現了一些用來處理靜態表項數據源的操做。在項目中誤用的話Xcode甚至會拒絕編譯,輸出報錯信息:「Illegal Configuration: Static table views are only valid when embedded in UITableViewController instances」。

另外一方面,原型表項在常規視圖內的表視圖中能夠正常工做,但在nib中就沒戲了。目前來說,使用原型表項或靜態表項就必須使用Storyboard。

你也有可能想在一個表視圖中混合使用靜態表項和常規的動態表項,很遺憾的是目前的SDK對此支持欠佳。若是你的App有這種需求,請參考蘋果開發者官方論壇上的相關帖子尋求可行方案。

注:若是構建的頁面上包含的靜態表項多到沒法在可視範圍內所有展現,你能夠在Interface Builder中直接利用滾動手勢查看,這個功能可能不容易發現,但確實管用。

不過總的來講該寫代碼的地方只能靠代碼,甚至靜態表項的表視圖也是如此。前面在把文本字段拖進第一個表項的時候,你可能發現尺寸不大合適,文本字段周圍有一點白邊,並且用戶看不到文本字段的實際範圍,若是正好點在邊框上,沒有彈出鍵盤,用戶會感到困惑。

爲避免這種狀況,你應該讓那一行任意位置接受的點擊均可以喚出鍵盤。要這樣作很容易,打開PlayerDetailsViewController.swift並以下添加

1
2
3
4
5
6
tableView(_:didSelectRowAtIndexPath:)`方法:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
   if  indexPath.section == 0 {
     nameTextField.becomeFirstResponder()
   }
}

代碼的意思是若是用戶點按第一個表項,App應該激活相應文本字段。該分段只有一個表項,你只需使用分段的索引。設文本字段爲第一響應者會自動喚出鍵盤。這只是一小處用戶體驗優化,但就是這樣一個小細節能夠給用戶省去一點煩惱。

小訣竅:添加delegate委託方法或重寫視圖控制器方法時,直接輸入方法名開頭的幾個字母(前面不加func),便可在自動補全列表中選擇正確的方法。

另外,還應該在Storyboard的屬性檢查器中把相應表項的Selection Style設爲None(本來是Default),不然用戶點按文本字段周圍的邊框時該行會高亮。

16_sb2_selectionstyle-700x314.png

好啦,添加玩家頁面設計完成。如今咱們要實現功能。

爲添加玩家頁面實現功能

如今先無論Game這行,只輸入玩家名稱。

當用戶點擊Cancel按鈕時,頁面關閉,用戶剛剛輸入的數據隨之做廢。這部分功能直接用回退轉場已經實現好了。

而當用戶點擊Done時,你應該建立一個新的Player對象,參照用戶輸入填充屬性後更新玩家列表。

轉場即將發生時,`prepareForSegue(:sender:)`會被調用。你能夠重寫這個方法,在退出視圖以前將數據保存到一個新的Player對象中。

注:不要擅自調用`prepareForSegue`方法,這是UIKit通知你一個轉場剛剛被觸發的消息。

在PlayerDetailsViewController.swift中,先在類上添加一條屬性:

1
var  player:Player!

這條語句並不會將屬性實例化,但其中的感嘆號把該變量定義爲隱式解包可選量(implicitly unwrapped optional),意思是該變量必須被實例化,並且你肯定它在被使用前必定有值。

接下來在PlayerDetailsViewController.swift中添加如下方法:

1
2
3
4
5
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
   if  segue.identifier ==  "SavePlayerDetail"  {
     player = Player(name: self.nameTextField.text, game:  "Chess" , rating: 1)
   }
}

prepareForSegue(_:sender:)`方法判斷轉場的標識符是否爲`SavePlayerDetail`,當且僅當斷定結果爲真時,建立一個新的Player實例,其中game和rating均取默認值。若是此時運行,App會崩潰,由於不存在標識符`SavePlayerDetail`,player不會被實例化,結合前面的隱式解包可選量定義,引起運行時錯誤。

小提示:若是App出現詭異的崩潰問題,並且代碼看起來彷佛並沒有邏輯錯誤,那麼多是在代碼中刪除過對象或修改過對象名,以至Storyboard引用對象出錯。

在Main.storyboard中,在文檔大綱裏找到Add Player場景,選擇鏈接到`savePlayerDetail`這個action的回退轉場,將其標識符改成`SavePlayerDetail`:

16_sb2_unwind_identifier-700x360.png

而後選擇鏈接到`cancelToPlayersViewController`的回退轉場,將其標識符改成`CancelPlayerDetail`。以供`prepareForSegue(_:sender:)`方法判斷標識符。

轉到PlayersViewController類,以下修改回退轉場方法`savePlayerDetail(segue:)`:

1
2
3
4
5
6
7
8
9
10
11
12
13
@IBAction func savePlayerDetail(segue:UIStoryboardSegue) {
   let playerDetailsViewController = segue.sourceViewController as PlayerDetailsViewController
  
   //add the new player to the players array
   players.append(playerDetailsViewController.player)
  
   //update the tableView
   let indexPath = NSIndexPath(forRow: players.count-1, inSection: 0)
   tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
  
   //hide the detail view controller
   dismissViewControllerAnimated( true , completion: nil)
}

這會經過傳入方法的轉場引用獲取一個指向`PlayerDetailsViewController`的引用,並藉此向數據源中使用的Player數組添加新的Player對象,而後通知表視圖在末尾新增了一行,由於表視圖和數據源應當保持同步。

你可能會直接調用`tableView.reloadData()`,但仍是爲新行插入的操做加入動畫效果比較好。`UITableViewRowAnimation.Automatic`會以插入新行的位置自動選用合適的動畫,十分方便。

試試看,如今應該能夠向列表中加入新玩家了!

App-with-new-players-273x500.png

性能

如今Storyboard已經有好幾個視圖控制器了,你或許會擔憂性能問題,不過一次載入整個Storyboard並非什麼苦活,Storyboard不會當即實例化全部的視圖控制器,當即載入的只有初始視圖控制器。而因爲這裏的初始視圖控制器是一個分頁欄控制器,包含的兩個視圖控制器也會被載入(第一個分頁標籤的Players場景和第二個分頁標籤的場景)。

其餘視圖控制器只有在轉場過去的時候纔會被實例化。而當關閉視圖控制器的時候,它們會當即被釋放,因此內存中只有活躍使用的視圖控制器,就好像分別使用nib同樣。

實踐是檢驗真理的惟一標準,在PlayerDetailsViewController類中添加構造器(initializer)和析構器(deinitializer):

1
2
3
4
5
6
7
8
required init(coder aDecoder: NSCoder) {
   println( "init PlayerDetailsViewController" )
   super .init(coder: aDecoder)
}
  
deinit {
   println( "deinit PlayerDetailsViewController" )
}

你剛剛重寫了`init(coder:)`和`deinit`方法,讓它們向Xcode調試面板輸出信息。如今運行App,打開添加玩家頁面,你會發現視圖控制器只有在被打開的時候纔會分配。

關閉添加玩家頁面的時候,不管是點擊Cancel仍是Done都會看到deinit析構器的`println()`輸出。若是再次打開這個頁面,你還會看到`init(coder:)`的輸出,這樣你應該相信這個事實了:視圖控制器是按需加載的,就像手動載入nib同樣。

注:若是你之前用過nib,那麼你應該會很熟悉構造器`init(coder:)`,這部分機制延續到了Storyboard中:使用的方法依然是`init(coder:)`,`awakeFromNib()`和`viewDidLoad()`。Storyboard能夠當作附帶了過渡信息和關聯信息的一系列nib的集合,而Storyboard內的視圖和視圖控制器使用與nib相同的方式編碼並解析。

遊戲選擇頁面

在添加玩家頁面中點選Game行應該打開一個新頁面並讓用戶從列表中選擇一個遊戲,這意味着下一步要加入另一個表視圖控制器,不過此次的頁面不是模態顯示,而是壓入導航棧。

向Storyboard中拖入一個新的表視圖控制器,在添加玩家頁面中選擇Game表項(確保選中的是整個表項,而不是其中的label),而後按住control拖到新建的表視圖控制器,在二者之間建立轉場。在彈出的選單中選擇轉場類型爲Push,而後在屬性檢查器中把轉場的Identifier標識符設爲PickGame。

雙擊導航欄,將新場景命名爲Choose Game。設原型表項的Style爲Basic(基本),設重用標識符爲GameCell,如圖:

177.png

在項目中使用Cocoa Touch Class模板新建一個Swift文件,命名爲GamePickerViewController,繼承UITableViewController。回到Storyboard中將遊戲選擇頁面的Custom Class設爲`GamePickerViewController`。

如今爲新頁面添加數據。在GamePickerViewController.swift中,在開頭添加games屬性,而後重寫viewDidLoad函數,像這樣:

1
2
3
4
5
6
7
8
9
10
11
var  games:[String]!
  
override func viewDidLoad() {
   super .viewDidLoad()
   games = [ "Angry Birds" ,
            "Chess" ,
            "Russian Roulette" ,
            "Spin the Bottle" ,
            "Texas Hold'em Poker" ,
            "Tic-Tac-Toe" ]
}

你剛剛新增了一個叫作`games`的字符串數組,並在`viewDidLoad()`中用寫定的內容填充數組。

而後以下替換數據源方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
   return  1
}
  
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
   return  games.count
}
  
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
   let cell = tableView.dequeueReusableCellWithIdentifier( "GameCell" , forIndexPath: indexPath) as UITableViewCell
   cell.textLabel?.text = games[indexPath.row]
   return  cell
}

上述代碼將`games`數組設爲數據源並替換表項的textLabel中的字符串值。

只要數據源準備就緒就應該能正常工做。運行App,點選Game行,新的遊戲選擇頁面會滑入屏幕。如今點擊各項不會有什麼效果,但因爲該頁面呈如今導航棧上,你能夠直接點擊返回按鈕,返回原來的添加玩家頁面。

188.png

不用寫代碼就能夠喚出新頁面,是否是很贊?只要按住control從靜態表項拖到新場景,寫的代碼只有填充表視圖的內容,並且通常來說比原地設計好的列表要靈活些(由於games數組更方便修改)。

固然新頁面要返回數據纔有用,爲此你要添加一個新的回退轉場。

在GamePickerViewController類的上面添加持有選中的遊戲的名稱和索引的屬性:

1
2
var  selectedGame:String? = nil
var  selectedGameIndex:Int? = nil

而後修改`cellForRowAtIndexPath:`:

1
2
3
4
5
6
7
8
9
10
11
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
   let cell = tableView.dequeueReusableCellWithIdentifier( "GameCell" , forIndexPath: indexPath) as UITableViewCell
   cell.textLabel?.text = games[indexPath.row]
  
   if  indexPath.row == selectedGameIndex {
     cell.accessoryType = .Checkmark
   else  {
     cell.accessoryType = .None
   }
   return  cell
}

這會在當前所選遊戲對應的表項附上選中標記(對號),這對用戶體驗來講不可或缺。

接着添加`tableview(tableview:didSelectRowAtIndexPath:)`方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
   tableView.deselectRowAtIndexPath(indexPath, animated:  true )
  
   //Other row is selected - need to deselect it
   if  let index = selectedGameIndex {
     let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: index, inSection: 0))
     cell?.accessoryType = .None
   }
  
   selectedGameIndex = indexPath.row
   selectedGame = games[indexPath.row]
  
   //update the checkmark for the current row
   let cell = tableView.cellForRowAtIndexPath(indexPath)
   cell?.accessoryType = .Checkmark
}

這段代碼首先會取消選擇剛剛點選的行,外觀會從灰色高亮變回常規的白色,而後移除對號,並在剛剛點選的行上附加選中標記。

運行App,測試是否正常。點選一個遊戲名,相應行會附上選中標記,點選另外一個遊戲名,選中標記也隨之移動。

Game-picker-checkmark-408x500.png

按要求來講點選某行以後應該關閉該頁面,不過如今並無自動返回,由於還沒有綁定回退轉場。

在PlayerDetailsViewController.swift的類上面添加一個持有被選遊戲的屬性,以便以後在Player對象中保存。令其默認值爲"Chess",這樣一來新玩家總會有一個選定的遊戲。

1
var  game:String =  "Chess"

一樣在該文件中改寫`viewDidLoad()`以在靜態表項中游戲名稱:

1
2
3
4
override func viewDidLoad() {
   super .viewDidLoad()
   detailLabel.text = game
}

添加回退轉場方法:

1
2
3
4
5
6
7
8
@IBAction func selectedGame(segue:UIStoryboardSegue) {
   let gamePickerViewController = segue.sourceViewController as GamePickerViewController
   if  let selectedGame = gamePickerViewController.selectedGame {
     detailLabel.text = selectedGame
     game = selectedGame
   }
   self.navigationController?.popViewControllerAnimated( true )
}

上述代碼會在用戶從選擇遊戲場景選中一個遊戲後執行。該方法按照選中的遊戲更新頁面上的label和game屬性,而後將GamePickerViewController彈出導航棧。

在Main.storyboard中按住control從表項拖到Exit出口對象,而後從彈出列表中選擇`selectedGame:`。

18_sb2_unwind_game.gif

設該回退轉場標識符爲SaveSelectedGame。

運行App試試看,建立新玩家,點選Game行並選擇一個遊戲。

19.jpg

不幸的是,這個回退轉場方法是在`tableView(_:didSelectRowAtIndexPath:)`方法前執行的,因此`selectedGameIndex`並未及時更新。幸運的是你能夠重寫`prepareForSegue(_:sender:)`方法,在轉場以前完成更新操做。

在GamePickerViewController中添加`prepareForSegue(segue:)`方法:

1
2
3
4
5
6
7
8
9
10
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
   if  segue.identifier ==  "SaveSelectedGame"  {
     let cell = sender as UITableViewCell
     let indexPath = tableView.indexPathForCell(cell)
     selectedGameIndex = indexPath?.row
     if  let index = selectedGameIndex {
       selectedGame = games[index]
     }
   }
}

`prepareForSegue(_:sender:)`的sender參數是引起轉場的對象,在這裏對應選中的遊戲表項,因此你能夠利用表項的indexPath來在games數組中肯定選中的遊戲並在轉場發生以前更新`selectedGame`。

如今運行App,選擇遊戲後玩家的遊戲信息會隨之更新了。

20_sb2_it_works-336x320.png

接下來改寫PlayerDetailsViewController的prepareForSegue方法來返回選中的遊戲,而不是寫定的"Chess"。這樣一來,完成添加玩家的操做後,Players場景中會顯示玩家實際選擇的遊戲。

在PlayerDetailsViewController.swift中以下改寫`prepareForSegue(_:sender:)`方法:

1
2
3
4
5
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
   if  segue.identifier ==  "SavePlayerDetail"  {
     player = Player(name: nameTextField.text, game:game, rating: 1)
   }
}

完成添加玩家頁面並點擊Done後,玩家列表會更新正確的遊戲信息。

還有一點,當你選擇一個遊戲,返回添加玩家頁面,而後嘗試從新選擇遊戲的時候,以前選定的遊戲應該顯示選中標記。解決方法是在轉場時把PlayerDetailsViewController中保存的選中的遊戲傳給GamePickerViewController。

仍是在PlayerDetailsViewController.swift中,於`prepareForSegue(segue:,sender:)`方法的末尾添加如下代碼:

1
2
3
4
if  segue.identifier ==  "PickGame"  {
   let gamePickerViewController = segue.destinationViewController as GamePickerViewController
   gamePickerViewController.selectedGame = game
}

注意:如今有兩條檢查`segue.identifier`的`if`語句。SavePlayerDetail是返回玩家列表的回退轉場,PickGame是前往遊戲選擇頁面的入棧轉場。添加的代碼會在GamePickerViewController的視圖加載以前更新其中的`selectedGame`。

打開GamePickerViewController.swift並在`viewDidLoad()`末尾添加如下代碼:

1
2
3
if  let game = selectedGame {
   selectedGameIndex = find(games, game)!
}

這兩行代碼獲取從PlayerDetailsViewController傳進的selectedGame並將其轉換成正確的索引。`find()`函數會在games數組中查找匹配selectedGame的String,而後返回匹配元素的索引,賦值給selectedGameIndex,這個索引用來在對應表項上設置選中標記。

好。如今選擇遊戲頁面功能實現完成!

Marin-Danger-280x500.png

何去何從?

這是整個教程的示例項目,包含上述全部源代碼。

可喜可賀,如今你已經瞭解Storyboard編輯器的基本用法,可以建立包含多個視圖控制器並能經過轉場在場景之間切換的App!在一處集中管理多個視圖控制器和互相的關聯,讓總體把握App的樣子更加容易。

你也看到了自定義表視圖和表項有多麼容易。有了靜態表項,不用實現全部的數據源方法也能夠構建一些界面。

若是想深刻了解Storyboard,請參閱咱們的書籍iOS 8教程,其中涵蓋了最新的通用Storyboard(Universal Storyboard)。

相關文章
相關標籤/搜索