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