NSToolbar でサイズが切り替わる設定画面のような WIndow を作る

下のようなXcode の設定ウィンドウのように NSToolbar が上部にあり、表示内容のサイズをアニメーション付きで変わるものを作ります。

f:id:soh335:20160907153338p:plain

storyboard

  • NSWindowController を用意します。
  • NSToolbar をセットし、下のように必要な NSToolbarItem を追加したりします。

f:id:soh335:20160907153518p:plain

  • NSWIndowController に紐付いている ViewController の view (NSView) を flipped = true を返すものにします。(理由は後述します。)

  • それぞれの設定画面の ViewController として作ります。必要な場合、レイアウトをします。

  • NSToolbarItem の action 先を紐付けます。

code

  • 上で紐付けると NSToolbarItem として event が来るので設定した identifier から必要な ViewController を生成します。
  • 生成した ViewController のサイズを計算し( autolayout している場合は fittingSize )、現在の View の高さとの差分をとります。(これも理由は後述します)
  • ContentViewController の childViewController から既存のものを削除し、生成した ViewController を追加し、view も追加します。
  • 生成した view の frame を指定します。(幅は既存の view, window と同じ、高さは上で計算したサイズの高さ, origin は 0, 0 で大丈夫です。これは上で flipped になっているからです。flipped でない場合は座標系が左下基準なので Window の下部に張り付いてアニメーションします)
  • NSWindow.setFrame(_:display:animate:) で animation 付きでサイズを指定します。これも上と同じ理由で、origin は現在の場所から上で計算した差分分を引きます。そうしないとサイズのみを変更した場合 Window の下部が固定されて上部が移動しますので不自然です*1

長くて文章にするとわかりづらいですがコードだとこういう感じです。

        let fittingSize = newVC.view.fittingSize
        
        let addition = fittingSize.height - self.contentViewController.view.frame.size.height
        
        if let oldVC = self.contentViewController?.childViewControllers.first {
            oldVC.removeFromParentViewController()
            oldVC.view.removeFromSuperview()
        }
        
        self.contentViewController?.addChildViewController(newVC)
        self.contentViewController?.view.addSubview(newVC.view)
        
        let windowFrame = self.window!.frame
        newVC.view.frame = NSMakeRect(0, 0, windowFrame.size.width, fittingSize.height)
        
        self.window!.setFrame(NSMakeRect(windowFrame.origin.x, windowFrame.origin.y - addition, windowFrame.size.width, windowFrame.size.height + addition ), display: true, animate: true)