iOS8から追加されたApp Extensionsの日本語ガイドが先日公開されました。
Extensionは全部で7種類ありますが、今回はTodayだけをまとめます。他のドキュメントを読む必要が生じたら読んで追記しようと思います。
提供する機能によってExtensionポイントを使い分ける
Extensionポイントは以下の7種類です。Finder Syncが使用できないため、iOSでは6種類ですね。
- Today
- Share
- Action
- Photo Editing
- Finder Sync (OS Xのみ)
- ドキュメントプロバイダ
- カスタムキーボード
Extensionを作る際は、最適なExtensionポイントを精査する必要があります。
App Extensionsはアプリではない
Extensionsは別バイナリでアプリ本体とは独立して動くものとのことです。ただし個別に申請を行う必要はありません。
プロジェクトに設定する際は別ターゲットを作ることになります。ちなみにExtensionターゲットはアプリ内にいくつでも追加できるので、同じTodayでも用途が別のターゲットを追加できることになります。
(Todayは実装しやすいので色々なアプリが盛り込んでいて、そのうちカオスなことになりそうですね。。。)
こういったExtensionsターゲットを保持しているアプリのことを収容アプリケーションとドキュメント内では呼んでいました。
Extensionポイントによって最適化させる
たとえばTodayでは通知センターのUIに、Photo Editingでは写真アプリと調和するデザインが求められるそうです。
また、あくまで本体のアプリではないのでExtensionは特定の機能に特化させ、素早くシンプルに処理できることが求められます。
ExtensionsのUI、機能を考える際に頭に入れておいた方が良さそうです。
ライフサイクル
App Extensionsはアプリとはライフサイクルが異なります。
- ユーザーによるExtensionの選択
- システムがApp Extensionを起動
- App Extensionのコードを実行
- システムがApp Extensionを終了
App Extensionsを選択できるようになっているアプリのことをホストアプリケーションと呼びます。
たとえば写真アプリから共有ボタンを押すと、Evernoteなどのアプリが出てきます。
このとき、写真アプリがホストアプリケーションということになります。ホストアプリケーションはExtensionにコンテキストを提供します。写真アプリで言えば画像データになりますね。
収容アプリとホストアプリは直接通信できない
データのやり取りはホストアプリが提供するExtensionのコンテキストを通して行われるため、収容アプリとホストアプリは通信を行いません。セキュリティを考慮してのことでしょう。
Extensionでは利用できないAPIがある
個人で作ったアプリにTodayを実装していて困ったのですが、sharedApplication
が使用できませんでした。
あるクラスにローカルプッシュ通知の登録を行う機能があったのですが、このクラスをExtensionでも利用しようとしたところ、sharedApplication
が内部で使われていたために使えませんでした。
アプリの開発初期段階で、あるいは将来的にExtensionsへ機能を切り出すことが想定される場合は、設計時に考慮に入れておいた方が良さそうです。
他にもカメラやマイクへのアクセスもできません。詳細はドキュメントを参照してください。
きちんとこの辺を抑えて設計を行う必要がありそうです。
64ビット対応必須
Extensionは64ビット対応している必要があります。またホストアプリケーションも64ビット対応する必要があるため、現状32ビット対応しかしていないアプリはExtensionを追加することはできません。
収容アプリケーションのターゲットが埋め込みフレームワークにリンクしている場合、アプリケー
ションにも64ビットアーキテクチャを組み込む必要があります。そうしないと、App Storeによっ
て承認されません。
NSExtensionContextを使ってコンテキストからデータ取得
App Extensionsはビューコントローラに含まれるNSExtensionContext
クラスのextensionContext
プロパティからコンテキストを取得します。
NSExtensionContext
クラスはinputItems
というプロパティを持っており、このNSExtensionItem
配列の中から処理対象のオブジェクトを取り出すことができます。
メモリについて
Extensionはホストアプリに比べてメモリがかなり制限されるようです。
ドキュメント中では特にToday=Widgetは開かれる数が多くなることが想定されるため、メモリ効率を意識する必要があると書かれています。
またExtensionはGPUなどの共有リソースを優先的には使えないため、極端な話Widget上でゲームを実装するというのは現実的ではありません。
Extensionのための機能の切り分けを欲張らずに行うことが必要ですね。
ExtensionsのUI
たぶん、以下のことだけを守っておけばいいのではないかと思います。
ExtensionのUIは、簡潔で、控えめで、単一のタスクに特
化したものでなければなりません。処理性能や使い勝手を考慮して、主たるタスクに関係ない、
余計なUI要素は使わないようにしてください。
それから、Extensionのアイコンは、本体アプリと別にしてはいけないようです。
Extensionのアイコンは、収容アプリ
ケーションのアプリケーションアイコンと同じにしてください。
ただし、Actionについてはテンプレート画像があります。これはHIGを参考にしてください。iOS8ではiOS7からテンプレート画像のデザインが変わっているの注意してください。
iOS7は塗りつぶしのない、ラインだけのアイコンですが、iOS8ではグレーで塗りつぶされたアイコンになりました。
日本語版は2014/10/28時点で更新されていないので注意してください。英語版のこちらが正しい情報です。
名前のパターン
App Extensionsは、短くて分かりやすい名前をつけてください。収容アプリケーションの名前を
含み、<収容アプリケーションの名前>—のパターンを使用します。こ
れにより、ユーザがシステム全体でExtensionを管理しやすくなります。収容アプリケーションが
完全に1つのExtensionを提供する一般的な状況では、収容アプリケーションの名前をそのまま
Extensionに使用することもできます。
これを読み解くと、複数のExtensionを提供する場合はExtensionごとに名前を分ける必要がありそうです。
埋め込みフレームワーク
本体アプリとExtensionでコードを共有するには自作のフレームワークを作成します。
前述したように、使用できないAPIがあるため、そういったAPIが含まれないようにフレームワークを作成する必要があります(自作フレームワークについては調べると色々でてきますが、試していないので割愛します)。
本体アプリとのデータ共有
収容アプリ=本体アプリとデータを共有させるためにはApp Groupの登録を行う必要があります。この設定はXcodeから行うことができます。
もし本体アプリ、Extension両方で共通して利用したいデータがある場合は両方からアクセスできる共通の保存領域を作成する必要があります。
共有領域にはUserDefaults
はもちろん、CoreData
を使用することもできます。
アップロードとダウンロード
バックグランドでの通信処理を行う場合はNSURLSession
を使用する必要があります(Extensionでは通信以外のバックグラウンド処理は行えないとのこと)。
設計時に抑えておくべきだなと感じたのはこれ。
iOSの場合、バックグラウンドタスクが終了した時点で
Extensionが動作していなければ、システムは収容アプリケーションをバックグラウンドで起動
し、アプリケーションデリゲートの
application:handleEventsForBackgroundURLSession:completionHandler:メソッド
を呼び出します。
アプリの本体の方で処理を行う必要があるので、バックグラウンド処理を行う場合は、考慮に入れておかなければならないです。
この際、共有保存領域が必要になるので、NSURLSession
を使用する場合はおのずと共有保存領域が必要になります。詳しくはドキュメントのP.24を参照してください。
取扱い可能なデータ型の宣言
ShareとActionのExtensionでは特定のデータしか扱えない場合があります。そういった場合は、ホストアプリからの起動をできないようにすることができます。
たとえばActionでは画像データを取り扱いたくない、という場合はExtensionのInfo.plistにNSExtensionActivationRule
キーを追加します。
ここに追加できるキーのリストはこちらを参照。
NSExtensionActivationRule
に対してキーを追加しなければ、扱えないデータとみなされますし、追加しないデータを明示したい場合は該当するキーに対して0
を指定すればOKです。
ExtensionがPDFのみを処理できるするようにはこの王にすればいいそうです。
画像10枚、動画1本、URL1つに制限する場合は、以下のようにNSExtensionAttributes
キーを追加し、以下のように設定を行えばいいそうです(ドキュメントより抜粋)。
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>10</integer>
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
</dict>
</dict>
Today
Widgetと呼ばれるこの機能は、ユーザーが関心のあることをすぐにみられることを期待する機能です。
Widgetに要求されることは以下の通りです。
- 常に最新の情報を表示する
- ユーザの操作に的確に応答する
- 効率よく動作する(特にiOS向けのウィジェットは、メモリを浪費するとシステムが強制停
止する可能性がある)
パッと見られないとか、表示するまでに時間がかかってしまう、というのはWidgetにふさわしくないということですね。目的の見極めが肝心ですね。
それからキーボード入力ができないので、入力を伴う機能は実装できません。スクロールビューも避けるようにと書かれています。
簡易的な動作(ボタンを押すだけなど)はできます。WWDC2014ではオークションアプリでその時点の値段の表示と、入札まで完了させるデモが行われていました。
Extensionは、Xcodeに用意されたテンプレートを使用して実装を行います。テンプレートにはStoryboardが用意されていますが、Storyboardを使用しないことも可能です。
UIについては、前述のとおり処理が重くならないようにする、キーボード入力ができないことを考慮する、スクロールを使用しない、などのことを頭に入れてデザインする必要があります。
システム側でWidgetのスナップショットを随時保存しています。
通知センターを一度開いて閉じ、再度表示させると、保存していたスナップショットを表示したうえで最新のビューに置き換えます。
スナップショット取得前にWidgetの更新を行うにはwidgetPerformUpdateWithCompletionHandler:
に実装を行います(Todayテンプレートからターゲットを作成するとViewControllerに含まれています)。
Widgetから本体のアプリを開く場合は、カスタムURLスキームを本体アプリに設定しておく必要があります。その際はNSExtensionContext
のopenURL:completionHandler:
メソッドを使用します。
まとめ
iOS8から導入されたWidgetはアップルとしても今後推進させていきたい機能であるということが10月のKeynoteからもうかがうことができます。
2~3年前は"1アプリ1タスク"という向きが強かったiOSアプリですが、アプリ開発の活性化とともに多機能なアプリが増えてきました。
ただ、やはり専門分野に特化して開発が行われるため、一部の機能を切り出して利用できるExtensionはユーザにとっては特にありがたいものと言えますね。
開発する側から見ても、たとえばEvernote連携をしたいとなった場合はEvernoteのAPIキーを取得して~といった作業・実装が必要でしたが、EvernoteのExtensionがあれば必要ないのかもしれません。
特に強みのあるアプリは、これからどんどんExtensionを導入してくるでしょうし、アプリ自体の独自性というものが一層問われることになるのだろうな~なんて感じました。