Today Extensionを実装してみた。
Today Extensionはウィジット
Today ExtensionはiOS8から導入されたウィジットを通知画面に設置する機能です。アプリの機能を拡張するExtensionの一つです。あまりに情報が少なくてハマったので、ブログに書いておきます。
Appleのドキュメントが一般公開されているので、詳しい情報は以下参照して下さい。
App Extension Programing Guide
この記事も参考にしました、 【iOS8】App Extension の実装方法 その1:ActionAdd Star
*以下はXcode6 beta3での検証結果です。画像はApp Extension Programing Guideから拝借してものです。
実装手順
- Today Extensionターゲットを作成
- Today ExtensionのViewの生成
- Info.plistを編集
- アプリ上でのコードの再利用:Framework化
- DBの共有:App Groupの作成
- データのアップデート処理
- Today Extensionからアプリを開く
- テストの実施
1. Today Extensionターゲットを作成
File > New > Target > Application Extension > Today Extensionを選択します。設定したExtensionの名前のディレクトリと、そのテストが生成されるます。生成される中身は以下のとおりです。
- info.plist
- .storyboardファイル
- ViewController.m, ViewController.h
プロジェクトファイルを確認すると、Extensionとそのテストのターゲットができていることが確認できます。
2. Today ExtensionのViewの生成
Today Extensionは通常のストーリーボードのViewと、view controllerで構成されています。Model層は本体アプリと共有されたもの、もしくはキャッシュされたフェッチ結果等がそれにあたります。
通常のViewと同様にストーリーボードで画面構成を生成します。通知センターは、TableView的にリストを表示しているので、ストーリーボード上にUITableViewを設置しました。いつもどおり、dataSourceとdelegateをストーリーボード上で設定。
このUITableViewをTodayExtensionで使う時に、背景色を正しく設定しないと通知センターっぽいUIにならないので注意が必要です。
ストーリーボード上で、UITableView及びUITableViewCellの背景色を透明にするか、以下のコードで明示的に透明にする必要があります。
必然的に黒背景になるので、その他のUIパーツ、UIButtonやUILabelは白っぽい文字でトンマナを合わせる必要があります。
3. Info.plistを編集
通知センターのタイトルを変更します。Bundle display nameにヘッダー部分に表示したい名称を入れます。 NSExtensionPointIdentifierをcom.company.appName.extensionのように変更。 その他は、通常のInfo.plistと同様に変更を加えていきます。
4. アプリ上でのコードの再利用
4-1. Framework化
App Extensionはアプリとは別のサンドボックスになっており、直接お互いのコードやDBにアクセスすることはできません。また結果的に別のアプリとして動くので、本体アプリは起動していないが、Extensionは動いている状態もありえます。そこで、コードを効率的にシェアする仕組みが必要です。そう、Frameworkです。Xcode6からはiOSアプリでもEmbeded frameworkを作ることができます。
File > New > Target > Framework & Library > Cocoa Touch FrameworkからFramework targetを生成します。Extensionにて使用したいファイルをBuild PhasesのCompile Sourcesに追加していきます。
この時、プロジェクトがMVCにしたがって設計されていると、依存関係が邪魔することなく必要なファイルだけをインポートできます。設計大事ですね。
4-2. CocoaPodsの利用(2014/08/02追記
Today ExtensionでCocoaPodsを利用する場合は、対象となるターゲットをPodfileに追記する必要があります。
Podfile Syntax Reference
target :test do pod 'OCMock', '~> 2.0.1' end
5. DBの共有:App Groupの作成
モデル部分の共有は、アプリ本体とExtensionどちらともアクセスできる領域にデータを保存する必要があります。今回はNSUserDefault
を使いました。
NSUserDefault
を使う時、通常はstandardUserDefaults
を使いますが、App Groupを定義し、共通領域に保存します。
- プロジェクトナビゲーター > 本体アプリ > Capabilities > App Groupsから設定をオンにします。
- 新しいコンテナとして、"group.com.companyName.myApp"のような命名をします。他の方法として、Apple Developer Center上でも同様に設定変更ができます。この場合、設定したprovisioning fileを再度ダウンロードし、Xcode上でプロジェクトに適応する必要があります。
- Today Extensionのターゲットでも同様のプロセスを実施します。
NSUserDefault
を読み込むときに、[[NSUserDefaults alloc] initWithSuiteName:@"group.com.companyName.myApp"]
のようにインスタンスを生成します。- 後は通常通り読み書きをするだけ。
この読み書きを実施するモデルを本体アプリで定義し、Embedded Frameworkに含めると共通化できて良さそうです。
6. データのアップデート処理
Today Extensionは、以下のメッソドを呼び、定期的にアップデートされます。その時、データ取得の成否をNCUpdateResult
として渡す必要があります。
7. Today Extensionからアプリを開く
URL schemeを使います。下の記事が参考になりました。
Custom URL Schemeの処理をシンプルに書く
8. テストの実施
Today Extensionは本体とは別のサンドボックスのため、実機やシミュレータを用いたテストでも何点かハマりました。
- ビルド時のターゲットをToday Extensionのものにしないと、ログが表示されない。
- 本体アプリターゲットでビルドしないと、必要なデータを保存できない。
- beta版だからか、シミュレータが不安定。よく落ちる。
- Today Extensionのストーリーボードに変更を加えた後、クリーン後ビルドしないと、UIが変更されない。
ユニットテストは通常と同様の書き方で問題なかったです。
まとめ
iOS8では簡単にウィジットが作れる。
2014/08/02追記
Facebook GroupでCocoaPods導入についてコメントを頂きましたので、追記しました。 https://www.facebook.com/groups/ios.dev.jp/permalink/768435093177874/