こんにちは、共同開発部 開発担当の西林です。
さて、Fenrir Advent Calendar 2014 も13日目となりました。
みなさん、 iOS8 から追加された Today Widget は使っていますか?
私は発売日に入手した iPhone6 で通知センターを開きすぎて親指にグッときました。
今日はそんな Today Widget を開発する時に困ったことと、その解決策を紹介したいと思います。
既存アプリのデータを Today Widget で表示したい
既存の iOS アプリにアップデートで Today Widget を追加したい場合、 Today Widget でデータを利用する前提に設計されていないことが多いと思います。
例えば TODO リストアプリで Today Widget に未完了のタスクを表示したい時、データがアプリの領域に保存されていたら、そのデータは Today Widget から直接アクセスできません。
そういった時どうすればよいでしょうか?
1. 既存のデータを常に App Group の shared container に書き出す
Today Widget に対応しているバージョンの時のみ、データの保存時に shared container にデータをコピーします。
簡単に実装ができるアプローチです。
ただし、データが重複するのでディスク容量を余計に消費しますし、いろんな場所でデータを書き出すアプリの場合、データの整合性が問題になってくる可能性もあります。
また、大きなファイルだとデータの反映に時間がかかる可能性なども出てきます。
2. そもそも shared container にデータを移動する
Today Widget に対応しているバージョンの時のみ、データの保存先を shared container に変更します。
1回移動してしまえば、あとは shared container にデータを書き込めばよいですし、ディスクの使用量は一番少なくて済みます。
ただし、 shared container は、同じ App Group の他のアプリもアクセスできるため、すべてのデータを移動してしまうと、関係のないデータを他のアプリから参照できてしまう可能性がありますので、あまりよいアプローチとは言えません。
3. Today Widget に必要なデータだけを切り出して保存する
Today Widget に対応しているバージョンの時のみ、データの保存時に Today Widget で利用するデータだけを shared container に書き出します。
実装は一番複雑になりますが、 Today Widget 側からは必要なデータだけ読み込めばいいので、メモリーの節約にもなります。
ただし、時間と共に表示が変わるようなアプリは定期的にデータの更新をしてやる必要が出てくるかもしれません。
メモリーが足りない
デバッグ中だと動くのに、普通に表示しようとすると Today Widget が表示されない、といったことがよくあります。
この場合、メモリー不足の可能性が高いです。
Today Widget のメモリー制限は通常のアプリよりかなり厳しく、通常のアプローチでは全然メモリーが足りない可能性もあります。
iOS8.1 での体感ですが、だいたいピーク時に 15MB 辺りを超えると Today Widget が強制終了されて何も表示されなくなります。
通常のアプリ開発では、手間のかからない仕組みを使って実装するようなところも、最適化が必要になるかもしれません。
実際に Today Widget を開発した時に試した内容を紹介します。
1. drawRect や CALayer による描画を使わない
UIView の drawRect や CALayer に描画する方式は、カスタムビューを実装する時によく使いますが、結構メモリーを消費します。
該当のViewがインタラクションのないものであれば、 UIGraphics 等を駆使して作成した画像を、 UIImageView にセットすると良いかもしれません。
実際にこれでメモリー使用量を 20MB 前後から 7MB くらいまで減らすことができました。
2. オブジェクトをなるべく早めに破棄する
1つのメソッドで複数のデータファイルを読み込んで変換しているような処理があった場合、 必要なくなった時点で明示的に nil を代入するなどして、破棄するタイミングを制御します。
読み込んでいるデータのサイズにもよりますが、ピークのメモリー使用量が若干ですが、抑えられます。
3. アプリ本体と同じような表示をするカスタムビューでも、別に実装する
DRY の哲学に反しますが、1や2のようなアプローチをする場合、どうしてもアプリ本体とカスタムビューやロジックの共通化ができない場合があります。
もちろんアプリ側を Today Widget に合わせて書き換えるのがベストかもしれませんが、どうしてもそういうわけにいかない場合は、専用の View や Model を作るというアプローチが必要になるかもしれません。
4. 表示する情報を整理して、ロードするデータを減らす
Today Widget に限らず、 App Extension は制約が通常のアプリより厳しくなっています。
本当にその情報は通知センターで見る必要があるのか、といった観点で表示する情報を整理するとよいでしょう。
そうすることで、ロードが必要なデータも減ってメモリー使用量も減るかもしれません。
あるいは Today Widget 自体が必要なくなるかもしれません。
AutoLayout がうまくいかず表示が崩れる
AutoLayout を利用したレイアウトは昨今当たり前になってきましたが、 Android 等他のプラットフォームの自動レイアウトと比べてまだまだノウハウが少ないのが現状です。
Today Widget は Auto Layout を使用しないレイアウトも可能ですが、縦画面固定にできない、高さの最大値に制限がある、複数の解像度などの問題で Auto Layout を使うのがベターかと思います。
1. 子の View だけで View のサイズが決まるようにする
Today Widget で使う ViewController の View は縦横のサイズが決まっていません。
そのため、 Storyboard 上に配置する View でサイズが決まるようにすべきです。
カスタムビューを作るときは intrinsicContentSize メソッドをオーバライドして、あらかじめカスタムビューのサイズがどうなるか決めてやるとよいでしょう。
2. あまり高さのある Widget を作らない
Today Widget の View は、開発者が高さを決めることができます。
しかし、実際のところは1つのウィジェットが画面内に収まりきるように調整されるようです。
そのため、あらかじめ縦横領画面で高さが収まりきるように Today Widget を作ってやるとよいでしょう。
3. 最終手段は preferredContentSize
どうしてもうまくいかない場合は、 ViewController の preferredContentSize に値をセットしてやるとうまくいくかもしれません。
ただし、このプロパティーの CGSize の値は height しか使われませんので、やはり width はちゃんと自分で決めてやる必要があります。
まとめ
- データの保存方法を見直す
- メモリー使用量に気をつけて
- AutoLayout ちゃんとやりましょう
最後に
Today Extension のハマりどころを紹介しました。
App Extension の中でも Today Widget は実用的な機能かと思います。
用量用法を守って正しく使い、アプリの利便性を向上しましょう。
フェンリルのオフィシャル Twitter アカウントでは、フェンリルプロダクトの最新情報などをつぶやいています。よろしければフォローしてください!