2015年

4月

16日

Apple Watch WatchKit <- iPhone App データ共有する方法(NSFileCoordinator)

AppleWatchとiPhone間でのデータ交換方法について


データ交換において3つの方法があります。

NSUserDefaultsを利用する(オーソドックスな共有)
NSFileCoordinatorを利用する(ファイルを共有)
Keychain Sharingを利用する(暗号化して共有)


このページは、NSFileCoordinator(ファイルを共有)についてまとめています。


NSFileCoordinatorとは

NSFileCoordinatorはバイナリデータを書き込むので大きなファイルやキャッシュが必要な頻繁にアクセスするファイルを保存する場合に利用することが出来ます。

用途に応じてNSUserDefaultと使い分けすると良いと思います。

 

まずiPhone WatchKit とデータ共有するには、Bundle Identifier のような識別子によって管理されApp Groupに登録する必要があります。

App Groupとは
iPhoneアプリのデータをApple Watchで見られるようにするには、App Groupを使う必要があります。App Groupは、複数のアプリの間でデータを共有できるようになる新しい機能です。

今までも Keychainの仕組みを使うことでアプリ間のデータ共有はできましたが手続きが面倒だったり機種変更のデータ復元などで問題がありました。

App Groupを使うと、今までと同じNSUserDefaultsなどを利用してデータ共有をすることができます。App Extensionではこの仕組みを使うとアプリとExtension間のデータ受け渡しが楽になります。


App Groupで共有できる範囲
App Groupでは、アプリとアプリの間、アプリとApp Extensionの間でデータ受け渡しをすることができます。

ただし、違う会社からリリースされているアプリの間での情報共有はできません。
App Groupの設定をするためには、同じ Developerからリリースされているアプリである必要があります。

App Groupを使うと、NSUserDefaultsを使った設定情報の共有とNSFileManagerを使ったファイルの共有ができます。

 

App Group を使った情報共有方法(準備)

App Groupを使うためには、まず iOS Dev Centerで App GroupのGroup IDを登録します。
「group.com.xxxxx.watchlist」というように、Bundle Identifierと同じようにドメイン名などから生成した一意な文字列の先頭に「group」をつけるのが推奨されています。

Dec Centerでの設定が終わったら、共有したいアプリをXcodeで開きます。
ProjectファイルのCapability タブを開き、 App Groupの設定をonにします。
App Groupの機能をonにすると、さきほど iOS Dev Centerで設定した Group IDが表示されるので、チェックボックスをonにします。

すると、プロジェクトにEntitlementファイルが追加されます。
共有をしたいアプリやExtensionのすべてについてこの設定を行ってください。

詳細は、アプリ制作にて示していますので参照願います。



Xcode 6.3 以上(6.3にて制作)

Download Xcode for Free
https://developer.apple.com/xcode/downloads/
6.2からAppleWatch開発環境(watchkit)が入っています。


iOS8.2以上 (iPhone)
8.2にバージョンアップするとアプリ「Apple Watch」が追加されるのですぐわかります。

iPhoneアプリケーション プロジェクトを生成
xcode起動後、「Create a new Xcode project」を選択します。
次にiOS -> Application -> Single View Applicationを選択します。

 

プロダクト名をWatchKitFileCoordinatorSwiftとします。

Watch Kit Appを追加
Watch Kitを追加します。File -> New -> Target を選択します。


AppleWatch ->WatchKit Appを選択します。


Include Glance、Include Notification Scene は、今回使わないのでチェックを外す。

Finishiをクリックします。

 

Activateをクリックします。

 

App Group の有効化

データを共有したい containing app や Extension は個別に App Group を設定する必要があります。まずは、containing app の設定を行います。

App Group の設定は、ターゲットの Capabilities で行います。containing app のターゲットを選択して、Capabilities タブを選択して下さい。



その中に App Groups というカテゴリがありますので、スイッチを ON にして有効化して下さい。


すると、どの Development Team を対象にするかを選択するダイアログが表示されますので、containing app と Extension をリリースしたい Development Team を選択して Choose をクリックして下さい。


Capabilities の画面に戻ります。App Group のカテゴリ内に、Development Team に対して登録済みの App Group のリストが表示されます。下図では、先程選択した Development Team に App Group が登録されていないため、リストは空になっています。

 

App Group の作成

App Group リストの左下に App Group を追加する + ボタンがありますので、それをクリックします。

 

App Group の識別子を入力するダイアログが表示されます。App Group の識別子は group. で始まる必要があります。group. の後ろは、Bundle Identifier と同じように逆ドメインを指定するようにしましょう。識別子を入力したら OK をクリックします。


例)group.com.xxxxx.watchlist

識別名:watchlist (任意ですからそれぞれ重複しない名前にすれば良いです。)


Capabilities の画面に戻ると、登録済み App Group リストに先程追加した App Group の識別子が追加されているのが確認できます。


追加した App Group を iOS Developer Center で確認してみましょう。Certificates, Identifiers & Profiles のページ内に App Groups という項目があります。App Group を作成したアカウントでログインして App Groups を選択すると、一覧に作成した App Group が追加されているのが確認できます。

 

これで iPhone containing app 側の App Group の設定は完了しました。iPhone App TARGETのExtension にも先程作成した App Group の設定を追加します。

iPhone AppのTARGETとWatch KitのTARGET両方に設定する必要があります。片方にしか設定していないとデータ共有がうまく行われません。

 

Watch Kit TARGETExtension にも先程作成した App Group の設定を追加します。


Action Extensionとは

Action Extension は iOS 8 より利用できるようになった App Extension が提供する機能の一部です。これは、アプリが提供する機能を他のアプリから利用できるようにする Extension で、Extension の中でも割り合い汎用的な機能を備えています。

Action Extension の主な利用用途としては、他のアプリが保持しているデータを独自の形式で表示したり、他のアプリが保持しているデータに対して、ユーザーが編集を行うための独自の UI を提供するといったことが挙げられます。


 

Main.storyboardでUIを作成します。
いつものiOS開発と同じです。Label、TextField、Buttonを貼り付けます。

 

Interface.storyboardでUIを作成します。
いつものiOS開発と同じです。LabelとButtonを貼り付けます。

位置調整はこのままです。

プログラムでの位置調整ができませんのでその点はご注意ください。

 

※Apple WatchにはGlance(ぱっと見る)、Notification(通知)という機能に関するものもありますが、これらについては今回は使用しません。

※今回は「Static Interface」や「Dynaminc Interface」の機能は使用しないので、特にこの部分に関しては特に追加や修正を行う必要はありません。


iPhone側のソースコード
ViewController.mに記述します。

App Groupを設定して指定アプリURLにTextFieldのバイナリデータを書き込んでいます。NSUserDefaultと使い方はほぼ同じです。 使い分けとしてはNSFileCoordinatorはバイナリデータを書き込むので大きなファイルやキャッシュが必要な頻繁にアクセスするファイルを保存する場合に向いています。用途に応じてNSUserDefaultと使い分けすると良いのではと思います。

import UIKit


// UITextFieldDelegate のプロトコルをViewControllerに設定

class ViewController: UIViewController, UITextFieldDelegate {


    // テキストラベル

    @IBOutlet weak var textLabel: UILabel!

    // テキストフィールド

    @IBOutlet weak var textField: UITextField!

    

    override func viewDidLoad() {

        super.viewDidLoad()

        // Do any additional setup after loading the view, typically from a nib.

        

        // textFiel の情報を受け取るための delegate を設定

        textField?.delegate = self

        

        textLabel.text = "Type Something..."

    }


    // セーブアクション

    @IBAction func onSaveTap(sender: AnyObject) {

        

        // NSFileCoordinatorとは、同じプロセス内で複数のプロセスとオブジェクト間の

        // ファイルおよびディレクトリの読み込みと書き込みを調整します。

        let fileCoordinator = NSFileCoordinator()

        

        // App Group アプリ間でデータのストレージ共有機能

        // App Group によって定義される共有領域のことを shared container と言います。

        // App Group の識別子で shared container にアクセス

        let groupURL = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("group.com.iScene.watchlist")

        // 共有ディレクトリへのパスを取得

        let fileURL = groupURL?.URLByAppendingPathComponent("todoItems.bin")

        

        fileCoordinator.coordinateWritingItemAtURL(fileURL!, options: nil, error: nil)

            { [unowned self] (newURL) -> Void in

                

                // 入力テキストデータをsaveDataに保存する

                let saveData = self.textField.text

                // NSKeyedArchiver(アーカイブ)を使ってデータをファイルに保存

                let dataToSave = NSKeyedArchiver.archivedDataWithRootObject(saveData!)

                

                // 出来ているか判定チェック

                if dataToSave.writeToURL(newURL, atomically: true) {

                    

                    // テキストラベルに表示

                    self.textLabel.text = saveData

                    

                    //let ret = dataToSave.writeToURL(newURL, atomically: true)

                    //println(ret);//true

                    

                } else {

                    

                    println("何かがおかしい…")

                }

            }

    }


    

    //return」を押すと、キーボードを隠す

    func textFieldShouldReturn(textField: UITextField) -> Bool{

        // textField の文字列を受け取り mLabel の文字に設定する

        //mLabel?.text = textField.text

        

        // キーボードを閉じる

        textField.resignFirstResponder()

        

        return true

    }

    

    override func didReceiveMemoryWarning() {

        super.didReceiveMemoryWarning()

        // Dispose of any resources that can be recreated.

    }


}

 

Watch Extension側の設定

InterfaceController.swiftに記述します。

受け取り時にNSKeyedUnarchiverをString型で解凍していますが、 この箇所を画像等それぞれ切り替えてデータを受け取ってください。

import WatchKit

import Foundation



class InterfaceController: WKInterfaceController {

    

    // テキストラベル

    @IBOutlet weak var textLabel: WKInterfaceLabel!

    

    // 最初に呼び出されるメソッド

    override func awakeWithContext(context: AnyObject?) {

        super.awakeWithContext(context)

        

    }


    // Updateアクション

    @IBAction func onUpdateTap() {

        

        // NSFileCoordinatorとは、同じプロセス内で複数のプロセスとオブジェクト間の

        // ファイルおよびディレクトリの読み込みと書き込みを調整します。

        let fileCoordinator = NSFileCoordinator()

        

        // App Group アプリ間でデータのストレージ共有機能

        // App Group によって定義される共有領域のことを shared container と言います。

        // App Group の識別子で shared container にアクセス

        let groupURL = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("group.com.iScene.watchlist")

        // 共有ディレクトリへのパスを取得

        let fileURL = groupURL?.URLByAppendingPathComponent("todoItems.bin")

        

        fileCoordinator.coordinateReadingItemAtURL(fileURL!, options: nil, error: nil)

            { [unowned self] (newURL) -> Void in

                

                if let savedData = NSData(contentsOfURL: newURL) {


                    // ログ表示

                    //println(NSKeyedUnarchiver.unarchiveObjectWithData(savedData) as! String)

                    // アンアーカイブした savedDataを表示

                    let data: String? =  

                        NSKeyedUnarchiver.unarchiveObjectWithData(savedData) as? String

                    // ログ表示

                    println("data %@", data)

                    

                    // ラベルに表示

                    self.textLabel.setText(data)

                }

        }


    }

    

    // ユーザーにUIが表示されたタイミングで呼び出されるメソッド

    override func willActivate() {

        // This method is called when watch view controller is about to be visible to user

        super.willActivate()

    }


    // UIが非表示になったタイミングで呼び出されるメソッド

    override func didDeactivate() {

        // This method is called when watch view controller is no longer visible

        super.didDeactivate()

    }

}


補足

Objective-Cでの参考例(App Groupでアプリ間データ共有


    // Save to sharedContainer
    NSURL *fileURL = [[NSFileManager defaultManager]    

                  containerURLForSecurityApplicationGroupIdentifier:@"group.com.xxxxx.watchgroup"];
    fileURL = [fileURL URLByAppendingPathComponent:@"data.plist"];
   
    NSDictionary* dict = @{ @"place":@"Osaka", @"tel":@"06-1234-5678"};
    [dict writeToURL:fileURL atomically:YES];

shared containerからの読み出しも同様です。

    // Load from sharedContainer
    NSURL *fileURL = [[NSFileManager defaultManager]

                  containerURLForSecurityApplicationGroupIdentifier:@"group.com.xxxxx.watchgroup"];
    fileURL = [fileURL URLByAppendingPathComponent:@"data.plist"];
   
    NSDictionary* dict = [[NSDictionary alloc] initWithContentsOfURL:fileURL];
    NSString* strPlace = [dict valueForKey:@"place"];

    NSLog(@"strPlace is %@",strPlace);

App Groupで保存された情報

  

目 次