接上鏈接
Add Player控制器在工作
現在你會忽視Game行,僅僅讓用戶輸入玩家的名字.
當用戶點擊Cancel按鈕的時候,這個控制器將會關閉并且不管你輸了什么數據都不會保存.這個部分用unwind segue已經起作用了.
但是當用戶點擊Done按鈕的時候,你應該創建創建一個新的Player 對象并且填寫它的屬性和更新?玩家的清單.
每當segue將要創建的時候prepareForSegue(_:sender:)都會被調用.在退回(dismiss)這個視圖的時候,你需要重寫這個方法來存儲你輸入的玩家對象的數據.
Note:
你永遠不會手動調用prepareForSegue(_:sender:)方法.它是一條從UIKit發出的信息,讓你知道那個segue已經被觸發了.
在PlayerDetailsViewController.swift里,首先在類頂部添加一個屬性來存儲你添加的玩家的詳細信息.
var player:Player?
接下來,在PlayerDetailsViewController.swift里添加下面這個方法:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SavePlayerDetail" {
player = Player(name: nameTextField.text!, game: "Chess", rating: 1)
}
}
prepareForSegue(_:sender:)使用默認的游戲和評級變量,創建了一個新的Player實例.它只是為帶有SavePlayerDetail標識符的segue起作用.
在Main.storyboard里,在Document Outline找到Add Player的控制器,然后選擇unwind segue,改Identifier為savePlayerDetail.
跳到PlayersViewController,改變這個unwind segue方法savePlayerDetail(segue:)為下面這樣:
@IBAction func savePlayerDetail(segue:UIStoryboardSegue) {
if let playerDetailsViewController = segue.sourceViewController as? PlayerDetailsViewController {
//add the new player to the players array
if let player = playerDetailsViewController.player {
players.append(player)
//update the tableView
let indexPath = NSIndexPath(forRow: players.count-1, inSection: 0)
tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
}
}
這就得到一個PlayerDetailsViewController的引用,通過這個segue引用,可以傳遞到這個方法.它用來往玩家數組里添加新的Player對象來作為數據源.然后它會告訴tableView添加了新的一行(在底部),因為tableView和它的數據源始終是同步的.
你可能用tableView.reloadData()完成了書信界面,但是上面的方法伴有動畫插入一行的時候看上去更漂亮.
UITableViewRowAnimation.Automatic會自動地找出合適地動畫,取決于你插入新行的位置.非常方便.
試一下,現在你應該可以向列表添加新的玩家了!
性能
現在在storyboard中有幾個viewController,你也許想知道關于他們的性能.立刻加載整個storyboard也不是很大的問題.storyboard并沒有馬上實例化所有的viewController–只有初始viewController是被立即加載的.因為你的初始viewController是一個 TabBarontroller,它所包含的兩個viewController也被加載了.
直到你segue他們,其他的viewController才被實例化.當你關閉這些viewController的時候,他們就立即被釋放了.所以只有使用的ViewController才存在內存中.
讓我們在實踐中看看吧!在PlayerDetailsViewController中添加一個初始化方法和一個反初始化方法:
required init?(coder aDecoder: NSCoder) {
print("init PlayerDetailsViewController")
super.init(coder: aDecoder)}
deinit {
print("deinit PlayerDetailsViewController")
}
你重寫了init?(coder:)和deinit方法,并且讓它們在Xcode控制臺輸出了一條信息.現在再一次運行app,然后打開Add Player控制器,你應該看到這個viewController沒有得到分配直到它打開的時候.
當你關閉 Add Player控制器,也點擊了Cancel和Done按鈕的時候,你應該會看到deinit里print()方法輸出地狀態信息.如果你再一次打開了這個控制器,你應該也會再一次看到從init?(coder:)輸出的狀態信息.這就會是你相信了,ViewController是在使用的時候才加載的.
Game Picker控制器
在Add Player控制器里點一下Game那一行應該會打開一個新的控制器,可以讓用戶從一個列表里選擇游戲.也就意味著你將會添加另一個tableViewController,然而這一次你需要從導航棧里推出(push)它,而不是從下往上彈出.
拖拽一個新的 TableViewController到Main.storyboard里.在AddPlayerscene里選擇Game的單元格(確保你選擇的的是整個單元格,而不是標簽)并且按住ctrl并拖線到新的新的TableViewController在它們之前創建一個segue連線.在出現的彈窗中選擇Selection Segue底下的Show segue,而不是Accessory Action.
選擇這個新的segue然后在Attributes Inspector設置它的標識符為PickGame.
在 Document Outline里選擇新的TableViewController,并且在Attributes Inspector里,給這個控制器的標題命名為Choose Game.
設置單元格的樣式為Basic,然后設置它的重用標識符為GameCell.你需要為這個控制器所做的就是這些.
為這個工程添加一個新的Swift文件,使用Cocoa Touch Class模板,命名為GamePickerViewController,繼承自UITableViewController.
返回Main.storyboard里你新建的Choose Game控制器然后在Identity Inspector里設置自定義的類GamePickerViewController.
現在讓我們給這個新的控制器一些數據來顯示吧.在GamePickerViewController.swift中,把一個具有硬編碼值的games字符串數組添加到頂部:
var games:[String] = [
"Angry Birds", "Chess",
"Russian Roulette",
"Spin the Bottle",
"Texas Hold'em Poker",
"Tic-Tac-Toe"
]
現在從模板里替換數據源方法:
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)
cell.textLabel?.text = games[indexPath.row]
return cell
}
你只是使用games數組設置了數據源并且把字符串的值放到了單元格的textLabel里.
就數據源而言應該那樣做.運行app然后點擊Game行.新的Choose Game控制器將會滑出來.然而點擊這些行不會做任何事,那是因為這個控制器是在導航堆棧上被彈出來的.但是你卻總可以點擊返回按鈕返回到Add Player控制器.
這很酷,不是嗎?你沒有寫任何代碼調用新的控制器.你只是按住ctrl鍵并從靜態table view cell拖拽出了新的控制器.你寫的唯一的代碼就是填充tableView的內容,這通常是更動態的而不是硬編碼列表.
當然,如果不發送任何返回數據,這個新的控制器將不是很有用,所以你還需要為它添加一個新的unwind segue.
在GamePickerViewController類的頂部添加屬性來保存名字和當前選中游戲的索引:
var selectedGame:String? {
didSet {
if let game = selectedGame {
selectedGameIndex = games.indexOf(game)!
}
}
}
var selectedGameIndex:Int?
不管什么時候selectedGame更新了,didSet將會在games里定位到游戲字符串并且在表的正確的索引位置自動更新selectedGameIndex.
接下來,改變tableView(_:cellForRowAtIndexPath:):
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("GameCell", forIndexPath: indexPath)
cell.textLabel?.text = games[indexPath.row]
if indexPath.row == selectedGameIndex {
cell.accessoryType = .Checkmark
} else {
cell.accessoryType = .None
}
return cell
}
這就給包含當前選中游戲名稱的單元格設置了一個對號.例如被這個app的用戶贊賞的一些小的手勢.
現在添加代理方法tableview(_:didSelectRowAtIndexPath:) :
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
}
selectedGame = games[indexPath.row]
//update the checkmark for the current row
let cell = tableView.cellForRowAtIndexPath(indexPath)
cell?.accessoryType = .Checkmark
}
不管用戶何時點擊一行,這個方法都被稱為Table View 的代理.
這個方法在點擊之后就會取消選中.那使得它從灰色高亮褪色為正常的白色.然后它就會從先前選中的單元格移除對號標記,然后把對號放到剛剛點擊的那一行上.
現在運行app測試一下吧.點擊一個游戲的名稱,那一行就會顯示一個對號.點擊另一個游戲的名稱,標記就會隨至移動到那一行.
只要你點擊一行這個控制器應該就會消失,但是現在卻不是那樣,因為你還真正的連接一個unwind segue.聽起來下一步非常棒!
在PlayerDetailsViewController.swift里,在類的頂部,添加一個屬性來保存選中的游戲,那樣你就可以在Player對象存儲它.給它一個默認的名字”Chess”,那樣你就會一個新的玩家始終都會有衣蛾選中的游戲名字.
var game:String = "Chess" {
didSet {
detailLabel.text? = game
}
}
不管何時名稱發生改變,didSet將會在靜態表單元格里顯示游戲的名稱.
依然在PlayerDetailsViewController.swift里,添加unwind segue 方法:
@IBAction func unwindWithSelectedGame(segue:UIStoryboardSegue) {
if let gamePickerViewController = segue.sourceViewController as? GamePickerViewController, selectedGame = gamePickerViewController.selectedGame {
game = selectedGame
}
}
一旦用戶從Choose Game控制器里選擇了一個游戲,上面的代碼就會執行.這個方法會更新控制器里的標簽以及選中游戲的屬性.unwind segue 也會將GamePickerViewController從導航棧里彈出.
在Main.storyboard里,按住ctrl把tableview的單元格拖拽到Exit,就想你之前做的一樣,然后從彈框中選擇unwindWithSelectedGame:.
在 Attributes Inspector 里給新的unwind segue的標識符(Identifier)為SaveSelectedGame.
運行app檢查它到目前為止的功能.創建一個新的玩家,選擇玩家的游戲然后選擇一個游戲.
在Add Player控制器里游戲并沒有更新!
不幸的是,unwind segue方法在tableView(:didSelectRowAtIndexPath:)之前執行,所以selectedGameIndex沒有更新.
幸運的是,你可以重寫prepareForSegue(:sender:)方法并且在unwind發生之前完成操作.
在GamePickerViewController里重寫prepareForSegue(_:sender:):
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SaveSelectedGame" {
if let cell = sender as? UITableViewCell {
let indexPath = tableView.indexPathForCell(cell)
if let index = indexPath?.row {
selectedGame = games[index]
}
}
}
}
prepareForSegue(_:sender:)參數的發送者是初始化segue的對象,在這種情況下就是被選中的游戲單元格.所以在games里,你可以使用單元格的indexPath來定位選中的游戲,然后設置selectedGame,這樣的話,它在unwind segue就是可行的了.
現在當你運行app然后選擇游戲的時候,它就會更新與動員的游戲了!
接下來,你需要改變PlayerDetailsViewController的prepareForSegue(_:sender:)方法來返回一個選中的游戲,而不是硬編碼為”Chess”.當你完成添加一個玩家的時候,用這種方式,它們實際的游戲將會顯示在Players控制器里.
在PlayerDetailsViewController.swift里,改變prepareForSegue(_:sender:)如下:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SavePlayerDetail" {
player = Player(name: nameTextField.text, game:game, rating: 1)
}
}
當你完成添加Add Player控制器并且按下完成按鈕的時候,玩家的列表將會更新為正確的游戲.
還有一件事– 當你選擇一個游戲的時候,返回到Add Player控制器,然后嘗試再選擇一個游戲,你之前選中的游戲應該會有一個對號標記.解決方案就是當你連線(segue)的時候,通過選中的游戲存儲在PlayerDetailsViewController到了GamePickerViewController上.
仍然在PlayerDetailsViewController.swift,添加到prepareForSegue(_:sender:)的末尾:
if segue.identifier == "PickGame" {
if let gamePickerViewController = segue.destinationViewController as? GamePickerViewController {
gamePickerViewController.selectedGame = game
}
}
需要注意的是現在你有兩個if狀態來判斷segue.identifier.SavePlayerDetail就是unwind segue將會返回的Players列表,PickGame就是顯示segue將要繼續向前到Game Picker 控制器.你添加的代碼將會在GamePickerViewController里定位到視圖的位置,然后設置selectedGame.設置selectedGame將會自動更新table view cell的索引selectedGameIndex,用來設置一個對號.
非常好!你現在有一個功能選擇游戲的控制器了!
本教程的所有源代碼:
請到這里下載:下載鏈接
翻譯過程中,有個別地方不是十分準確,希望大家批評指正有好的建議也可以回復