会社の行動規範浸透を図るため、メニューバーからいつでも確認できるアプリを作った🤔

これは[Retty Inc. AdventCalendar2018](https://qiita.com/advent-calendar/2018/retty) 17日目の記事です。 
昨日は @shyneさんの [既存AndroidアプリにFlutterを導入して12.5倍速で開発する](https://qiita.com/shyne/items/0665d8768ce42e7ce33d) でした。

## はじめに

皆さんはmacOSのメニューバーに常駐するアプリをどれくらい使われているでしょうか？
よく使われているアプリケーションはDropbox, JetBrains ToolBox, macOS設定系, ...etc と様々なアプリを使われているのではないでしょうか？ 

これらのメニューバーアプリの良さは気軽にアクセスできることだと思います。
この利点を利用して、日頃忘れがち(?)な会社の行動規範（RettyWay)をいつでも確認できるようなアプリを作って行きたいと思います。

脱線しますが、[RunCat](https://itunes.apple.com/jp/app/runcat/id1429033973?mt=12):cat2:というCPU利用率を表示するアプリがとてもユニークで良かったので愛用しています。（オススメです:fire:）

## 完成したもの

<img width="350" src="https://qiita-image-store.s3.amazonaws.com/0/20511/c8328608-169f-8ced-0458-414a4264f05d.gif" \>

完成物は簡単な作りで画像を1枚表示しているだけです。[^1]
このアプリを常駐させておけば、小休憩時に行動規範を確認しやすくなりますね!? :family: ?[^2]

## RettyWayとは？

Rettyの行動規範を指します。
詳細はこちらの[記事: Rettyらしさは努力で守る。組織拡大のフェーズで行動規範「Retty Way」が果たす役割](https://www.pr-table.com/retty/stories/998)を見て頂けると幸いです。 :pray: 

上記の記事でも取り上げられているのですが、RettyWayは組織の状況に合わせて変化しています。
そのため、単純な失念や認識の齟齬をなくす目的としても、今回のアプリは有用と言えるかもしれません。


## メニューバーアプリ[（Menu Bar Extras）](https://developer.apple.com/design/human-interface-guidelines/macos/extensions/menu-bar-extras) とは？

この記事内でメニューバーアプリと言っているモノは[Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/macos/extensions/menu-bar-extras)内の`Menu Bar Extras`を指しています。

> Display a menu—not a popover—when the user clicks your menu bar extra. Unless the app functionality you want to expose is too complex for a menu, you should avoid presenting it in a popover. See [Popovers](https://developer.apple.com/design/human-interface-guidelines/macos/windows-and-views/popovers/).

また上記ガイドラインに目を通すと、今回作るようなポップアップを表示するアプリは、ガイドライン的に適切でないようです。

前置きが長くなりましたが、早速作っていきましょう。

## 実装

常駐型アプリを作る上で必要な作業は下記の3点です。
- メニューバーのアイコン表示・指定
- Dockアイコンの無効化
- `Main Window`の非表示化（削除）

下の2つは好みですが、メニューバー機能のみのアプリであれば実装することが好ましいでしょう。

### プロジェクトの作成

Xcodeを立ち上げ、設定の `File → New → Project → macOS → Cocoa App`でプロジェクトを用意します。

| `File/New/Project` の選択 | → | `macOS/Cocoa App`の選択 |
|---|---|---|
|![image.png](https://qiita-image-store.s3.amazonaws.com/0/20511/a5c3453e-b42e-50e4-4ed2-f6527f3b7e40.png)| → |![image.png](https://qiita-image-store.s3.amazonaws.com/0/20511/d3000140-95bc-dff1-47ca-525cbee0b758.png)|

メニューバー用のTemplateが用意されている訳ではない為、 `Cocoa App`から作ることになります。


### メニューバーのアイコンを指定する

#### アイコン用のAssetsを用意

適当な画像をAssetsに追加し、`Rendar As`に`Template Image`を指定。
![icon](https://qiita-image-store.s3.amazonaws.com/0/20511/94493ce4-3841-ebab-fb83-006876e38756.png)

#### メニューバーにアイコンを表示

1. メニューアイコンとアイコンから表示するPopoverを用意
2. ボタンにアイコン画像とアクションを指定
3. Popoverで表示するViewを指定
    - また③を行うにあたって、Storyboardのidentityに`ViewController`を指定してください。


```swift
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
    // ①
    private let menuBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
    private let popover = NSPopover()

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // ②
        if let button = self.menuBarItem.button {
            button.image = NSImage(named: NSImage.Name("MenuBarIcon"))
            button.action = #selector(togglePopover(_:))
        }
        // ③
        self.popover.contentViewController = ViewController.freshController()
    }
}

// ②
extension AppDelegate {
    @objc func togglePopover(_ sender: Any) {
        if self.popover.isShown {
            self.closePopover(sender)
        } else {
            self.showPopover(sender)
        }
    }

    private func showPopover(_ sender: Any) {
        guard let button = self.menuBarItem.button else { return }
        self.popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
    }    
    
    private func closePopover(_ sender: Any) {
        self.popover.performClose(sender)
    }
} 

// ③
extension ViewController {
    static func freshController() -> ViewController {
        let storyboard = NSStoryboard(name: "Main", bundle: nil)
        let identifier: NSStoryboard.SceneIdentifier = "ViewController"
        guard let viewController = storyboard.instantiateController(withIdentifier: identifier) as? ViewController else { fatalError("...") }
        return viewController
    }
}
```

ここまでで、こんな感じのものが出来たでしょうか？（適当にNSImageViewを配置して表示しています[^3]）
![image.png](https://qiita-image-store.s3.amazonaws.com/0/20511/e5b5f29e-b513-6e41-9c9a-ea45a841f48b.png)
この段階ではアプリの`Main Window`が表示されてしまっていますが、不要なので消してしまいましょう。


### `Main Window`の非表示化（削除）

といっても、StoryBoard上から`Window Controller Scene`を消すだけでOKです。
![Main Window](https://qiita-image-store.s3.amazonaws.com/0/20511/cd3f7c40-eab5-93c3-9325-5651869035f3.png)


### Dockアイコンの無効化

現状では下記画像のようにアプリのアイコンがアクティブな状態で表示されています。
メニューバー常駐型のアプリでは、Dockにアイコンが表示されている必要性が低い & またそういうアプリが多い為[^4]、消してしまいましょう。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/20511/36fa1947-b5bd-0e85-9a92-370b67fe1b26.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/20511/250817d1-1635-e28f-9142-dc80de6dd41e.png)
`info.plist`を開き、上記プロパティを追加することで、Dock Iconを消せます。
XMLのKeyは`LSUIElement`です。


---


:fire:以上でメニューバーに常駐するアプリが完成したかと思います:fire:
表示させるコンテンツは皆さん良しなに実装してみてください。
一応[完成サンプル : GitHub](https://github.com/isaoeka/MenuBarSampleApp-macOS)を置いておきます。


## 最後に

いかがでしたでしょうか？

今回は個人的に作りたかっただけで、半ば無理やりRettyを紐づけていたのですが、比較的良さそうなモノになりそうな気がします。
メニューバーにサービスのアイコンが表示されているだけで、ちょっと気分が上がりそうな気がしています。 :upside_down: 
表示している画像を適切なモノに変えてあげれば、Archiveして社内に配っても良いかもしれません。

以上、ありがとうございました。

明日は@masadooonさんです！！Rettyワッショイ！！

[^1]: 使用している画像などはすべて仮のデータで、正式なものではありません
[^2]: 記事内で作成したアプリは導入に至ったわけではなく、単に制作しただけです
[^3]: 写真のモデルは弊社の @tkngue くんです
[^4]: 主観