NowPlayingを自動でツイートするMacアプリの開発 – その1

前回の記事で書いたアプリの開発についての記録
執筆時のdevelopブランチを基準に書いて行きます
今回はアプリの骨組みと Twitter アカウントで認証するための準備までを書きます

主な仕様

  • ステータスバーにアイコンが出る
  • 複数のTwitterアカウントを利用できる
  • 曲が再生されるたびにツイートできる
  • 再生中(トラックに入っている)曲をツイートできる
  • ツイートするフォーマットを変更できる
  • ツイートに画像を含むことができる

現在は以上の機能です。今後バージョンアップで機能を追加したり削除したりしていくと思います

開発

それぞれの仕様ごとに書いていきます

NSStatusBar statusItem – ステータスバーのアイコンとメニュー

まずはアプリの基礎から。
Storyboard で Application Scene に NSMenu を追加して、それを AppDelegate に IBOutlet で接続します
NSMenu には Main Menu の ApplicationName の中にある About や Quit と同じ FirstResponder に繋いだものを入れておきます

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    // statusItem に NSStatusBar.system.statusItem を代入
    let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)

    @IBOutlet weak var menu: NSMenu!

    // ...

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        if let button = self.statusItem.button {
            // StatusBarIcon という名前の画像を Assets.xcassets に追加しておく
            let image = NSImage(named: NSImage.Name("StatusBarIcon"))
            // Dark mode で色反転するように
            image?.isTemplate = true
            // statusbar.button の image に画像を設定
            button.image = image
        }
        // statusbar.menu に IBOutlet の self.menu を設定
        self.statusItem.menu = self.menu

        // ...
    }

    // ...

}

statusItemAppDelegate で宣言します
statusItembutton の画像を設定するのに、入れる NSImage の isTemplate を true にすることで dark mode の時に黒が白に反転して表示されます
そして statusItemmenu に IBOutlet で追加した self.menu を入れてあげることでクリックするとそのメニューが表示されるようになります

ステータスバーに表示するので Dock にアプリケーションアイコンが表示される必要性はありません
なので Info.plist に Application is agent (UIElement) のキーを追加して YES にします
*Info.plistファイルの中身は LSUIElement というキーの名前です

<key>LSUIElement</key>
<true/>

環境設定ウィンドウの骨組みを作る

Main.storyboard に最初からある WindowController をそのまま使います
まずは PreferencesWindowController.swift を作成、NSWindowController を継承した PreferencesWindowController クラスを作ります

Storyboardの方に移って WindowController を選択します
アプリを起動して最初に開く設定になっているので Attributes Inspector で Is Initial Controller のチェックを外します
そうしたら Identity Inspector で Class に PreferencesWindowController を設定します
さらに Storyboard ID に PreferencesWindowController と入れます

次に、Toolbar を WindowController に追加して中身の Flexible Space 以外を削除
Attributes Inspector で Customizable のチェックを外します
Separator と Visible at Launch にはチェックを入れておきます
まず1つだけ Image Toolbar Item を追加して、Attributes Inspector で
Label/Palette Label を General
Image Name を NSPreferencesGeneral
Tag を 0
にします

そして PreferencesWindowControllertoolbargeneralItemIBOutlet で接続します

class PreferencesWindowController: NSWindowController {

    @IBOutlet weak var toolbar: NSToolbar!
    @IBOutlet weak var generalItem: NSToolbarItem!

    static let shared: PreferencesWindowController = {
        let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil)
        let windowController = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("PreferencesWindowController"))
        return windowController as! PreferencesWindowController
    }()

    // ...

}

シングルトンにしたいので shared を設定しています

これを StatusBarstatusItem から開くために NSMenu に Preferences… の menu を追加して IBActionAppDelegate に繋ぎます
関数名は showPreferences で中での処理は PreferencesWindowController.shared を呼び出して showWindow を実行しています

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    // ...

    @IBAction func showPreferences(_ sender: Any) {
        PreferencesWindowController.shared.showWindow(sender)
    }

    // ...

}

ひとまずここで区切ります
次回の記事で Toolbar Item をクリックしていくと ViewController が切り替わっていくようなよくみる環境設定ウィンドウに作り込んで行きます

URL Scheme を受け取る

このアプリでは Swifter という iOS/macOS の両方で使える Swift 用の Twitter API ラッパーを利用します
Swifter では認証して access token を取得するために callback でアプリケーションがひらくURLを設定します
そのために URL Scheme でアプリを開けるようにしなければいけません

URL Scheme を設定する

まずはアプリケーションがひらく URL Scheme を設定します

Project の Info に URL Type という項目があります。ここに Identifier と URL Schemes を設定して Role を None にします
今回は Identifier を com.kr-kp.NowPlayingTweet、URL Scheme を nowplayingtweet, npt にしました

Info.plist には以下のように記述されます

<array>
    <dict>
        <key>CFBundleURLName</key>
        <string>com.kr-kp.NowPlayingTweet</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>nowplayingtweet</string>
            <string>npt</string>
        </array>
    </dict>
</array>

URL Scheme を受け取り EventHandler に渡す

次に、AppDelegateNSAppleEventManager に EventHandler を設定します

// Swifter を import
import SwifterMac

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    // ...

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // NSAppleEventManager.shared() で setEventHandler
        NSAppleEventManager.shared().setEventHandler(self,
                                                     // イベントを受け取る関数
                                                     andSelector: #selector(self.handleEvent(_:withReplyEvent:)),
                                                     // Internet(Browser) からのアクセスのクラス
                                                     forEventClass: AEEventClass(kInternetEventClass),
                                                     // URL を取得する EventID
                                                     andEventID: AEEventID(kAEGetURL))

        // ...
    }

    @objc func handleEvent(_ event: NSAppleEventDescriptor!, withReplyEvent: NSAppleEventDescriptor!) {
        // Cell SwifterMac handler
        Swifter.handleOpenURL(URL(string: event.paramDescriptor(forKeyword: AEKeyword(keyDirectObject))!.stringValue!)!)
    }

    // ...

}

forEventClass には AEEventClass(kInternetEventClass)andEventID には AEEventID(kAEGetURL) を入れ、handleEvent という関数を宣言して、それを andSelector に入れます
handleEvent のなかで Swifter.handleOpenURL を呼び出し、受け取ったURLを渡すことで Swifter 側で callback を受け取ることができるようになります

小休止

ここまで、アプリケーションをステータスバーで動くアプリにして環境設定ウィンドウを作り、URL Scheme を受け取れるようになりました
次回はTwitterアカウントを扱うクラスや環境設定ウィンドウを作り込んで行きます

Leave a Reply

Your email address will not be published. Required fields are marked *