Creating a ViewController & xib, and Loading them from Storyboard

_hatenablogから2018-12-10の記事を移行しました。
https://bitsandbobs.hatenablog.jp/entry/2018/12/10/025556_

----

ViewController と xib ファイルを作って Storyboard から呼び出す手順をまとめておく。
！注意！これは Cocoa App (macOS アプリ) に関する記事です。

## 環境
- macOS High Sierra 10.13.6
- Xcode 10.1

## 手順
### ViewController と xib ファイルを作成する
<img width="512" alt="20181210012507.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341937/781a135c-6332-42ca-a4d1-cac6ee2afa97.png">
New File から

<img width="512" alt="20181210012735.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341937/7ec475ba-b206-810e-9659-1ff0d3191cd5.png">
macOS / Cocoa Class を選択して Next

<img width="512" alt="20181210012803.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341937/7325d455-7317-b80d-d6ee-7ece61d07a76.png">
Subclass of は NSViewController を選択  
Also create XIB file for .. のチェックオン (手間が省けるので)  
クラス名を入力して Next → Create

<img width="512" alt="20181210012837.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341937/aba0d9c0-b69a-c145-ebe6-3ab88dd2ada6.png">
ViewController のソースファイルと、同名の xib ファイルが作成される

### xib の編集
<img width="512" alt="20181210012907.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341937/27267dd6-7c59-77af-0df9-1434c4dbbd95.png">
File's owner の Custom Class 名に先ほど作ったクラスが設定されていることを確認しておく  
その後、好きなように View にアレコレ追加していく(割愛)

### Storyboard の編集
<img width="512" alt="20181210013134.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341937/7e31694f-0f8b-ce0e-6f3f-e83ec0fdf112.png">
View Controller の Custom Class 名に作ったクラスを指定する

<img width="512" alt="20181210013152.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341937/18015dd5-54a2-4a7f-f57b-fbf55c392d9c.png">
デフォルトで作られている View を Storyboard から削除しておく  
(Storyboard に View が残っていると xib ファイルではなく、この View を優先して表示するため)

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

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

> 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.
>
> [loadView() - NSViewController | Apple Developer Documentation](https://developer.apple.com/documentation/appkit/nsviewcontroller/1434405-loadview)

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

[^1]: Object ID は Identity inspector の Document に表示されている

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

- 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を切り替えるときなど)に使えるかもしれません。

## 参考
- [NSViewController - AppKit | Apple Developer Documentation](https://developer.apple.com/documentation/appkit/nsviewcontroller)