LoginSignup
26
32

More than 5 years have passed since last update.

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

Posted at

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

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

Masterの名前変更

スクリーンショット_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

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

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

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

ボタンは無効にしておく
スクリーンショット_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
        }
    }

実際に動くものはこちら

参考(主にSplitView)

[iOS 8] UISplitViewController が iPhone に対応しました
[iOS 8] マルチデバイス対応の新機能「Trait Collection」
iOS8から変更になったUISplitViewControllerについて調べてみた

26
32
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
32