iOS 9 Storyboard 教程(二下)

接上鏈接

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會自動地找出合適地動畫,取決于你插入新行的位置.非常方便.

試一下,現在你應該可以向列表添加新的玩家了!

Paste_Image.png

性能

現在在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控制器.

Paste_Image.png

這很酷,不是嗎?你沒有寫任何代碼調用新的控制器.你只是按住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測試一下吧.點擊一個游戲的名稱,那一行就會顯示一個對號.點擊另一個游戲的名稱,標記就會隨至移動到那一行.

Paste_Image.png

只要你點擊一行這個控制器應該就會消失,但是現在卻不是那樣,因為你還真正的連接一個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然后選擇游戲的時候,它就會更新與動員的游戲了!

Paste_Image.png

接下來,你需要改變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,用來設置一個對號.
非常好!你現在有一個功能選擇游戲的控制器了!

Paste_Image.png

本教程的所有源代碼:
請到這里下載:下載鏈接
翻譯過程中,有個別地方不是十分準確,希望大家批評指正有好的建議也可以回復

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,214評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,315評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,699評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,882評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,441評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,189評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,388評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,613評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,112評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,334評論 2 377

推薦閱讀更多精彩內容