Master-Detail Applicationテンプレートを実用的なところまでカスタマイズ！


Xcodeプロジェクトを作成する時のテンプレート、Master-Detail Application。
これを使ってアプリを作るにあたって、いろいろといじらないといけない部分が出てくる。特にSplitView。
でもこれがなかなか苦労したのでまとめておく。

* Masterの名前変更
* Detailの名前変更
* Masterに戻るボタンの名前変更
* SplitVeiwで選択されている項目がない場合の対応(メイン)


## Masterの名前変更
![スクリーンショット_2015-09-16_10_21_57.png](https://qiita-image-store.s3.amazonaws.com/0/39565/1f9e7c11-e254-773c-5e59-3e9044bf2dc2.png "スクリーンショット_2015-09-16_10_21_57.png")

これは簡単。

```MasterViewController.swift
    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の名前変更
![スクリーンショット_2015-09-16_10_22_22.png](https://qiita-image-store.s3.amazonaws.com/0/39565/6e9956df-e566-a90c-e24c-756295cca715.png "スクリーンショット_2015-09-16_10_22_22.png")

たとえばタイムスタンプが表示されるようにしてみる。
タイトルなり何なり好きな文字列を設定すればいいと思う。

```DetailViewController.swift
    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に戻るボタンの名前変更
![スクリーンショット_2015-09-16_10_22_22_.png](https://qiita-image-store.s3.amazonaws.com/0/39565/9def31aa-2426-aac9-223e-8577dbbfa1f3.png "スクリーンショット_2015-09-16_10_22_22_.png")

SplitViewの場合とNavigationViewの場合とで設定が別々。
一括で変更できる素敵な方法は無いものか？

こちらはSplitView用。なかなか変え方が分からず苦労した…。

```MasterViewController.swift
    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用

```MasterViewController.swift
    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がある場合のサンプルとしてボタンを配置する。
![スクリーンショット 2015-09-16 11.21.43.png](https://qiita-image-store.s3.amazonaws.com/0/39565/676c63a6-807b-1387-e036-28e9bfe64897.png "スクリーンショット 2015-09-16 11.21.43.png")

ボタンは無効にしておく
![スクリーンショット_2015-09-16_11_36_14.png](https://qiita-image-store.s3.amazonaws.com/0/39565/7717e528-59da-b7bf-3ae3-441dc7c77b79.png "スクリーンショット_2015-09-16_11_36_14.png")

ソースとの関連付けも行なっておく

```DetailViewController.swift
class DetailViewController: UIViewController {

    @IBOutlet weak var detailDescriptionLabel: UILabel!
    @IBOutlet weak var sampleButton: UIButton!  // <- コレ！
```

### Master側の調整

まずは必要な変数を追加。

```MasterViewController.swift
class MasterViewController: UITableViewController, NSFetchedResultsControllerDelegate {

	...(略)
	
    var currentTimestamp: NSDate? = nil     // 選択中の項目のタイムスタンプ
    var deletedCurrentObject: Bool = false  // 選択中の項目が削除された
```

項目が選択されたときに、その項目のタイムスタンプを変数`currentTimestamp`に保存しておく。
また、そのままだと`object`がnullだった場合にボタンが設定されなくなるので、ボタンの設定を外側に出した。

```MasterViewController.swift
    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`と一緒だったらフラグを立てておく。

```MasterViewController.swift
   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`を実行する。

```MasterViewController.swift
    func controllerDidChangeContent(controller: NSFetchedResultsController) {
        self.tableView.endUpdates()
        
        // 分割表示時に選択中の項目が削除された
        if (self.deletedCurrentObject == true) {
            self.performSegueWithIdentifier("showDetail", sender: self)
        }
        self.deletedCurrentObject = false
    }
```

### Detail側の調整
Detailの画面がロードされたときに行われる処理を追加。
操作不可にして、タイトルも未選択の旨を表示。

```DetailViewController.swift    
    override func awakeFromNib() {
        super.awakeFromNib()
        self.view.userInteractionEnabled  = false
        self.navigationItem.title = "選択されていません"
    }
```

上の処理は普通にデータを選択した場合も通るけど、その場合はこちらも通るので、
操作可否の設定やタイトル等を上書き。

```DetailViewController.swift
    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
        }
    }
```

## 実際に動くものはこちら
https://github.com/Kta-M/ios_master_detail_custom

## 参考（主にSplitView）
[[iOS 8] UISplitViewController が iPhone に対応しました](http://dev.classmethod.jp/references/ios8-uisplitviewcontroller-for-iphone/)
[[iOS 8] マルチデバイス対応の新機能「Trait Collection」](http://dev.classmethod.jp/references/ios8-trait-collection/)
[iOS8から変更になったUISplitViewControllerについて調べてみた](http://qiita.com/mini_house/items/8aa3de029fd3e0555814)