Creating a ViewController & xib, and Loading them from Storyboard

ViewController と xib ファイルを作って Storyboard から呼び出す手順をまとめておく

!注意!これは Cocoa App (macOS アプリ) に関する記事です

環境

手順

ViewController と xib ファイルを作成する

f:id:bitsandbobs:20181210012507p:plain New File から

f:id:bitsandbobs:20181210012735p:plain macOS / Cocoa Class を選択して Next

f:id:bitsandbobs:20181210012803p:plain Subclass of は NSViewController を選択
Also create XIB file for .. のチェックオン (手間が省けるので)
クラス名を入力して Next → Create

f:id:bitsandbobs:20181210012837p:plain ViewController のソースファイルと、同名の xib ファイルが作成される

xib の編集

f:id:bitsandbobs:20181210012907p:plain File's owner の Custom Class 名に先ほど作ったクラスが設定されていることを確認しておく
その後、好きなように View にアレコレ追加していく(割愛)

Storyboard の編集

f:id:bitsandbobs:20181210013134p:plain View Controller の Custom Class 名に作ったクラスを指定する

f:id:bitsandbobs:20181210013152p:plain デフォルトで作られている View を Storyboard から削除しておく
(Storyboard に View が残っていると xib ファイルではなく、この View を優先して表示するため)

あとがき

当初 xib ファイルが読み込まれずどハマりしました。 Storyboard で ViewController を追加したときに初期作成される View を削除してなかったからですが、単純なことほど気づきにくいものですね。 この問題の原因を調べる過程でわかったことを以下にまとめておきます。

  • ViewController が View を初期化するとき(loadView がコールされるとき) nibName プロパティが nil の場合はクラス名と同名の xib ファイルをロードする(loadView() - NSViewController | Apple Developer Documentation)

    Prior to OS X v10.10, the loadView() method did not provide well-defined behavior if the nibName property’s value was nil. In macOS 10.10 and later, however, you get correct behavior without specifying a nib name as long as the nib file’s name is the same as that of the view controller.

  • ViewController が Storyboard からインスタンス化されるときは init(nibName: bundle:) ではなく init(coder:) コンストラクタがコールされる
  • Storyboard にて ViewController に View が設定されている場合は、nibName = ViewController の Object ID1 + "-view-" + View の Object ID (例: "XfG-lQ-9wD-view-Qcj-BY-oc5")となる
  • Storyboard にて ViewController に View が設定されていない場合は nibName = nil となる

ちなみに調査の過程で以下の方法を試しました。 困ったことに、これらは全て期待する動きをしたのですよね。

  • NSViewController の修正
    • viewDidLoad で NSNib#instantiate
    • loadView をオーバーライドして NSNib#instantiate
    • init(coder:) をオーバーライドして super.init(nibName: bundle:)
    • nibName プロパティをオーバーライドして nil またはクラス名を返す
  • Storyboard の修正
    • User Defined Runtime Attributes に nibName:String を追加する
    • Storyboard の ViewController から View を消す(!!!)

Runtime Attributes に nibName を追加する方法は ViewController クラスは作らないけど View は xib で外だしにしたいときとかに使えそうです。

nibName や init(coder:) をオーバーライドする方法は xib のファイル名がクラス名と異なるとき(例えば、動的にViewを切り替えるときなど)に使えるかもしれません。

参考


  1. Object ID は Identity inspector の Document に表示されている