Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
8
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

@takebayashi

FinderSyncでDropboxモドキを作る

YosemiteではDropboxがFinderと深く連携して動作するようになりました。同期ステータスを示す✅バッジは専用の場所で描画されるようになり、Dropboxディレクトリ内のアイテムを選択した時のみ専用のコンテクストメニューも表示されるようになりました。

これは、Yosemiteで新たに追加されたフレームワークであるFinderSyncを利用して実現されています。このFinderSyncを利用することで、Dropboxっぽい見た目のアプリを作ることができるようになります。

FinderSyncでできること

FinderSyncの実体はApp Extensionです。FinderSyncを利用することで、特定のディレクトリに対して以下のことが実現できるようになります。

  • ディレクトリ内のアイテムに対してバッジを設定する
  • ディレクトリ内のアイテムが選択された時のコンテクストメニューを追加する
  • Finderのツールバー項目を追加する
  • ユーザがディレクトリを開いたことを検知する

FinderSyncは同期機能をもつアプリをFinder UI(と各種アプリのファイル操作パネル)と連携させるためのものであり、単体でファイルの同期機能を実現することはできません。これはコンテナアプリ側でFSEvents等のAPIを利用することで実現できるでしょう。

FinderSyncの使い方

XcodeでCocoaアプリのプロジェクトを開き、Finder Sync Extensionターゲットを追加すると、FinderSyncの雛形が作成されます。FinderSyncフレームワークにはFIFinderSyncFIFinderSyncControllerの2つのクラスしかないため、容易に全体像を把握できると思います。FIFinderSyncのサブクラスを作成し、その中からFIFinderSyncControllerのメソッドを呼び出す形で実装します。

以下に、実装例を示します。

class MyFinderSync: FIFinderSync {

    let controller = FIFinderSyncController.defaultController()

    override init() {
        super.init()

        // 対象のディレクトリを登録する
        controller.directoryURLs = NSSet(object: NSURL(fileURLWithPath: "/Path/To/Mybox")!)

        let doneImage: NSImage! = ... // ファイルのステータスに対応するバッジ画像の生成 ✅
        controller.setBadgeImage(doneImage, label: "Done" , forBadgeIdentifier: "Done")
    }

    // アイテムが表示されるときに呼び出される
    override func requestBadgeIdentifierForURL(url: NSURL!) {
        let isDone = ... // ファイルの何らかのステータスを取得
        if (isDone) {
            // init内で用意したDoneバッジをアイテムにセットする
            controller.setBadgeIdentifier("Done", forURL: url)
        }
    }

    // 各種メニューを返す
    override func menuForMenuKind(menuKind: FIMenuKind) -> NSMenu! {
        var menu: NSMenu!
        switch menuKind {
        case .ContextualMenuForItems:
            menu = ... // ディレクトリ内のアイテムを選択した際のコンテクストメニュー
        case .ContextualMenuForContainer:
            menu = ... // 対象ディレクトリ自体を選択した際のコンテクストメニュー
        case .ContextualMenuForSidebar:
            menu = ... // サイドバーにある対象ディレクトリを選択した際のコンテクストメニュー
        case .ToolbarItemMenu:
            menu = ... // ツールバーボタンをクリックした際に表示されるプルダウンメニュー
        }
        return menu
    }

    // ツールバーにボタンを追加する場合には、下記の3メソッドをオーバーライドする

    override var toolbarItemName: String! {
        return "Mybox Sync"
    }

    override var toolbarItemToolTip: String! {
        return "Mybox Sync"
    }

    override var toolbarItemImage: NSImage! {
        let toolbarIcon: NSImage! = NSImage(named: NSImageNameCaution)
        return toolbarIcon
    }

    // 下記の2メソッドをオーバーライドすると、ユーザが対象のディレクトリ内に入るとき、出るときに通知される

    override func beginObservingDirectoryAtURL(url: NSURL!) {
        ...
    }

    override func endObservingDirectoryAtURL(url: NSURL!) {
        ...
    }

}

作成したFIFinderSyncのサブクラスをInfo.plistのNSExtensionPrincipalClassキーの値として設定し、NSExtensionPointIdentifierキーの値をcom.apple.FinderSyncとすることで、FinderSync Extensionとしてビルドすることができます。

まとめ

上記に示したような短いコードで、ハリボテではあるもののFinder上でDropboxのような見た目と挙動を実現することができ、開発者は同期機能などの実装に注力できるようになります。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
8
Help us understand the problem. What are the problem?