Xcodeプロジェクトを作成する時のテンプレート、Master-Detail Application。
これを使ってアプリを作るにあたって、いろいろといじらないといけない部分が出てくる。特にSplitView。
でもこれがなかなか苦労したのでまとめておく。
- Masterの名前変更
- Detailの名前変更
- Masterに戻るボタンの名前変更
- SplitVeiwで選択されている項目がない場合の対応(メイン)
Masterの名前変更
これは簡単。
override func awakeFromNib() {
super.awakeFromNib()
if UIDevice.currentDevice().userInterfaceIdiom == .Pad {
self.clearsSelectionOnViewWillAppear = false
self.preferredContentSize = CGSize(width: 320.0, height: 600.0)
}
// Masterのタイトル
self.navigationItem.title = "データ一覧" // <- コレ!
}
Detailの名前変更
たとえばタイムスタンプが表示されるようにしてみる。
タイトルなり何なり好きな文字列を設定すればいいと思う。
func configureView() {
// Update the user interface for the detail item.
if let detail: AnyObject = self.detailItem {
if let label = self.detailDescriptionLabel {
label.text = detail.valueForKey("timeStamp")!.description
}
self.navigationItem.title = detail.valueForKey("timeStamp")!.description // <- コレ
}
}
Masterに戻るボタンの名前変更
SplitViewの場合とNavigationViewの場合とで設定が別々。
一括で変更できる素敵な方法は無いものか?
こちらはSplitView用。なかなか変え方が分からず苦労した…。
override func awakeFromNib() {
super.awakeFromNib()
if UIDevice.currentDevice().userInterfaceIdiom == .Pad {
self.clearsSelectionOnViewWillAppear = false
self.preferredContentSize = CGSize(width: 320.0, height: 600.0)
}
// SplitViewのときのMasterを呼び出すボタン名
self.title = "一覧" // <- コレ!
// Masterのタイトル
self.navigationItem.title = "データ一覧"
}
こちらは普通のNavigationView用
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.navigationItem.leftBarButtonItem = self.editButtonItem()
// NavigationViewのときのMasterを呼び出すボタン設定
let backButton = UIBarButtonItem(title: "一覧", style: UIBarButtonItemStyle.Plain, target: nil, action: nil) // <- コレ!
self.navigationItem.backBarButtonItem = backButton; // <- コレ!
let addButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: "insertNewObject:")
self.navigationItem.rightBarButtonItem = addButton
if let split = self.splitViewController {
let controllers = split.viewControllers
self.detailViewController = controllers[controllers.count-1].topViewController as? DetailViewController
}
}
SplitVeiwで選択されている項目がない場合の対応
これが今回のメイン!
- アプリを起動したとき、何も選択されてないけどDetailにStoryBoardそのままの状態が表示される。ボタンとか配置しようものなら押し放題。
- 選択中の項目が削除されたとき、Detailは特に更新されないので削除されたはずのデータの内容が表示されっぱなし。ボタンがあると削除されたデータに対して処理が走るからこわい。
iPhoneの画面で開発してて、ふとiPadでやってみたらこの問題が出てきて絶望した。
(下準備)ボタンとかのUIがある場合のサンプルとしてボタンを配置する。
ソースとの関連付けも行なっておく
class DetailViewController: UIViewController {
@IBOutlet weak var detailDescriptionLabel: UILabel!
@IBOutlet weak var sampleButton: UIButton! // <- コレ!
Master側の調整
まずは必要な変数を追加。
class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate {
...(略)
var currentTimestamp: NSDate? = nil // 選択中の項目のタイムスタンプ
var deletedCurrentObject: Bool = false // 選択中の項目が削除された
項目が選択されたときに、その項目のタイムスタンプを変数currentTimestamp
に保存しておく。
また、そのままだとobject
がnullだった場合にボタンが設定されなくなるので、ボタンの設定を外側に出した。
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController
controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem()
controller.navigationItem.leftItemsSupplementBackButton = true
if let indexPath = self.tableView.indexPathForSelectedRow() {
let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject
controller.detailItem = object
self.currentTimestamp = object.valueForKey("timeStamp") as? NSDate
}
}
}
項目削除時に通るところ。
画面がSplitViewで、かつ削除する項目のタイムスタンプがcurrentTimestamp
と一緒だったらフラグを立てておく。
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
...(略)
case .Delete:
// 分割表示を行なっていて、選択中の項目が削除されたらフラグを立てておく
var window :UIWindow = UIApplication.sharedApplication().keyWindow!
if let split = window.rootViewController as? UISplitViewController {
if split.collapsed == false {
if anObject.valueForKey("timeStamp") as? NSDate == self.currentTimestamp {
self.deletedCurrentObject = true
}
}
}
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
...(略)
削除処理が完了したところ。
フラグが立っていれば、ここで画面を未選択状態にするべく、データ無しでshowDetail
を実行する。
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.tableView.endUpdates()
// 分割表示時に選択中の項目が削除された
if (self.deletedCurrentObject == true) {
self.performSegueWithIdentifier("showDetail", sender: self)
}
self.deletedCurrentObject = false
}
Detail側の調整
Detailの画面がロードされたときに行われる処理を追加。
操作不可にして、タイトルも未選択の旨を表示。
override func awakeFromNib() {
super.awakeFromNib()
self.view.userInteractionEnabled = false
self.navigationItem.title = "選択されていません"
}
上の処理は普通にデータを選択した場合も通るけど、その場合はこちらも通るので、
操作可否の設定やタイトル等を上書き。
func configureView() {
// Update the user interface for the detail item.
if let detail: AnyObject = self.detailItem {
let timestamp = detail.valueForKey("timeStamp")!.description
if let label = self.detailDescriptionLabel {
label.text = timestamp
}
self.detailDescriptionLabel.text = timestamp
self.sampleButton.enabled = true
self.view.userInteractionEnabled = true
self.navigationItem.title = timestamp
}
}
実際に動くものはこちら
参考(主にSplitView)
[iOS 8] UISplitViewController が iPhone に対応しました
[iOS 8] マルチデバイス対応の新機能「Trait Collection」
iOS8から変更になったUISplitViewControllerについて調べてみた