Jeffsuke is not a pen.

🏊‍♂️🚴‍♂️🏃‍♂️💻📱

Today Extensionを実装してみた。

Today Extensionはウィジット

Today Extension from WWDC
Today ExtensionはiOS8から導入されたウィジットを通知画面に設置する機能です。アプリの機能を拡張するExtensionの一つです。あまりに情報が少なくてハマったので、ブログに書いておきます。 Appleのドキュメントが一般公開されているので、詳しい情報は以下参照して下さい。

App Extension Programing Guide

この記事も参考にしました、 【iOS8】App Extension の実装方法 その1:ActionAdd Star

*以下はXcode6 beta3での検証結果です。画像はApp Extension Programing Guideから拝借してものです。

実装手順

  1. Today Extensionターゲットを作成
  2. Today ExtensionのViewの生成
  3. Info.plistを編集
  4. アプリ上でのコードの再利用:Framework化
  5. DBの共有:App Groupの作成
  6. データのアップデート処理
  7. Today Extensionからアプリを開く
  8. テストの実施

1. Today Extensionターゲットを作成

File > New > Target > Application Extension > Today Extensionを選択します。設定したExtensionの名前のディレクトリと、そのテストが生成されるます。生成される中身は以下のとおりです。

  • info.plist
  • .storyboardファイル
  • ViewController.m, ViewController.h

プロジェクトファイルを確認すると、Extensionとそのテストのターゲットができていることが確認できます。

Set up on Xcode6

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にしたがって設計されていると、依存関係が邪魔することなく必要なファイルだけをインポートできます。設計大事ですね。

Embedded Framework

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を定義し、共通領域に保存します。

  1. プロジェクトナビゲーター > 本体アプリ > Capabilities > App Groupsから設定をオンにします。
  2. 新しいコンテナとして、"group.com.companyName.myApp"のような命名をします。他の方法として、Apple Developer Center上でも同様に設定変更ができます。この場合、設定したprovisioning fileを再度ダウンロードし、Xcode上でプロジェクトに適応する必要があります。
  3. Today Extensionのターゲットでも同様のプロセスを実施します。
  4. NSUserDefaultを読み込むときに、[[NSUserDefaults alloc] initWithSuiteName:@"group.com.companyName.myApp"]のようにインスタンスを生成します。
  5. 後は通常通り読み書きをするだけ。

この読み書きを実施するモデルを本体アプリで定義し、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/