2010年7月アーカイブ

 iOSでは写真アルバムからの画像を選び出す手段としてイメージピッカー UIImagePickerControllerクラスを提供しています。

 iPad上では,これをPopoverというUIで使用しなさいという条件が付いています。ボタンを押すと現れる吹き出しのようなインターフェイスがPopoverです。

 Popoverの横幅は320ピクセルとされていて,これはiPhoneの横幅と同じなので,テーブルビューなどをそのまま持ってきてPopover内に表示することができます。Popover内がiPhoneアプリのように見えるのも当然です。

--

 ところで,イメージピッカーで写真を選択する場合,範囲選択の編集をさせることができます。allowsEditingプロパティをYESにすれば,選択後に拡大縮小と範囲を移動させる画面が用意されます。

 そして,ピックアップした画像をおさめたNSDictionary型からUIImagePickerControllerEditedImageをキーとして編集結果を取り出すという手順になります。ちなみに,UIImagePickerControllerOriginalImageをキーにすると編集されてないオリジナル画像が取り出せます。

 ところが,この方法を素朴に採用すると横幅最大320ピクセルの画像しか取り出せず,それを拡大するとぼやけてしまいます。

 というのもイメージピッカーがPopoverの中でしか使えないためです。Popoverの大きさを拡大すれば,原理的にはイメージピッカーの切り抜きサイズも大きくなるはずですが,残念ながらPopoverは,縦方向はともかく,横方向は320ピクセルに制限されているため,数値を大きく指定して引き伸ばしてもすぐに幅320ピクセルへ縮んでしまいます。

--

 iPadでイメージピッカーを使用して,大きなサイズの画像を切り抜けないのかというと,そうでもありません。

 幸い,イメージピッカーはオリジナル画像とともに切り抜こうとした矩形範囲の座標をUIImagePickerControllerCropRectキーで教えてくれるのです。

 であれば,こちらでオリジナル画像を切り抜けば良いだけのこと。画像の切り抜き方法は,ググるとあれこれ出てきますので,それを参考にしましょう。

 ところが,うまく切り抜いてアプリも動いているにもかかわらず,処理を繰り返していると頻繁に「落ちる」。メディアを自在に閲覧できるiPadがどうして?と訝しく思えるのですが,どうも画像を保持するUIImageViewでは1024ピクセル以上の画像を扱わないで欲しいらしい。かなりメモリにシビアなのが現状です。

 というわけで,切り抜いた画像を1024ピクセル以内にリサイズすることにして,やっと問題が解消しそうです。

 新しいアプリの開発を始めて,iPadとiPhoneのユニバーサルアプリを勉強しています。Appleの「iPadプログラミングガイド」や『iPadプログラミングの作法』などが参考になります。

 ユニバーサルアプリのメリットはそれほど大きくありません。iPadとiPhoneの個別修正があった場合,変更無い方も一緒に更新作業が必要になる煩雑さがユーザーにはあるかもしれませんし,有料アプリの場合には価格設定を分けられないというのも困るかもしれません。

 開発の手間も減るわけではありませんが,配信手続きが一回で済む点は便利かなと思います。なにしろ,あのAppStoreですから,願わくはシンプルに済ませたいものです。

--

 現時点で,ユニバーサルアプリの開発は,iPhone OS3.2(iPad)とiOS4(iPhone)というメジャーバージョンの異なるOSを想定して作業することになります。

 iOS4からマルチタスクやアプリ切り替え機能が追加されたので,アプリのコア設計も変わってしまっています。その上,iPadとiPhoneは画面解像度が違いますから,UIデザインは二重作業です。

 ところでiPhone3G(S)からiPhone4では解像度が縦横2倍に変わりましたが,この場合のグラフィック処理も2倍で考えるのかと思っていましたが,実はiPhone4においてもプログラミング段階では320x480という座標値でプログラムを作ることになります。

 iPhone4ではそれが高解像度に自動変換されるということが売りのようです。ただし,アプリ内で使用する画像ファイルなどは,2倍の大きさで作り込んでおかないと,低解像度がそのまま引き延ばされた格好になるので見栄えしないというわけです。

 というわけで,新しいiOSでは,プログラミングの手間が増えるというよりも,グラフィック素材の準備に手間がかかるようになっています。どちらかといえば絵心が無くてプログラミングをしているような人間には,悩ましさが増している今日この頃です。

 CoreDataにストアしてあるデータをTableViewでセクションとインデックス付きで表示する際,CoreDataからセクション名を取得する方法があります。

 私が作っているアプリは主に電車の時刻表を扱うので,始発から終電までの発車時刻を並べて表示しています。その際,1時間毎にセクションにまとめて表示します。

 セクション名は,始発が朝5時で終電が夜中1時まであれば,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,0,1という順番になることを意図しています(最後の方が24時,25時でなく0時,1時ってところがミソです)。

 最後の0,1は最初の5,6...よりも小さい数字ですが,データ取得する際のソート指定において内部的に24,26になるようにしてあるわけです。

--

 さて,

 セクション名を取得する方法としてNSFetchedResultsControllerに対してsectionsメソッドでセクションの集合を取り出し,ひとつひとつセクションを取り出して名前を取得していました。

 iPhone OS3.1.3までは,この方法で意図した順番にセクション名を取得できました。ところがiOS4上では,全く同じプログラムなのに取得されたセクション名の並び順が0,1,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23となってしまう挙動に変わってしまっています。

 最初は開発環境の変化で,諸々の設定が適切でなくなったせいなのかと,あれこれ設定を変えてみたのですが,現象に変化はなし。シミュレーターでも実機でもiPhone OS3.1.3では従来通り意図通り動作しているのですが,iOS4では並びが変わってしまいます。

--

■実験

 とにかく実験してみましょう...

○取得方法その1:セクション自体の集合を取得してひとつずつ名前を取り出す

NSArray* fetchedTitles1 = [fetchedResultsController sections];
for (id section in fetchedTitles1) {
NSLog(@"Sec %@",[section name]);
}

○取得方法その2:セクション名の配列を取得して名前を取り出す

NSArray* fetchedTitles2 = [fetchedResultsController sectionIndexTitles];
for (NSString *secname in fetchedTitles2) {
NSLog(@"SecNm: %@",secname);
}


 取得方法その2は,1文字しか取得してくれないので,10番台は「1」20番台は「2」にまとめられて表示されます。
 なお,今回の実験のデータは終電が0時台の時刻表を使用したので,0時だけが注目点になります。

■結果

【iPhone3G + iPhone OS3.1.3】意図した並び...

○取得方法その1
2010-07-17 03:52:31.996 RideOnTime[78:207] Sec 5
2010-07-17 03:52:32.002 RideOnTime[78:207] Sec 6
2010-07-17 03:52:32.007 RideOnTime[78:207] Sec 7
2010-07-17 03:52:32.013 RideOnTime[78:207] Sec 8
2010-07-17 03:52:32.019 RideOnTime[78:207] Sec 9
2010-07-17 03:52:32.024 RideOnTime[78:207] Sec 10
2010-07-17 03:52:32.030 RideOnTime[78:207] Sec 11
2010-07-17 03:52:32.044 RideOnTime[78:207] Sec 12
2010-07-17 03:52:32.050 RideOnTime[78:207] Sec 13
2010-07-17 03:52:32.055 RideOnTime[78:207] Sec 14
2010-07-17 03:52:32.060 RideOnTime[78:207] Sec 15
2010-07-17 03:52:32.065 RideOnTime[78:207] Sec 16
2010-07-17 03:52:32.071 RideOnTime[78:207] Sec 17
2010-07-17 03:52:32.077 RideOnTime[78:207] Sec 18
2010-07-17 03:52:32.083 RideOnTime[78:207] Sec 19
2010-07-17 03:52:32.089 RideOnTime[78:207] Sec 20
2010-07-17 03:52:32.094 RideOnTime[78:207] Sec 21
2010-07-17 03:52:32.099 RideOnTime[78:207] Sec 22
2010-07-17 03:52:32.156 RideOnTime[78:207] Sec 23
2010-07-17 03:52:32.167 RideOnTime[78:207] Sec 0

○取得方法その2
2010-07-17 03:52:38.298 RideOnTime[78:207] SecNm: 5
2010-07-17 03:52:38.305 RideOnTime[78:207] SecNm: 6
2010-07-17 03:52:38.310 RideOnTime[78:207] SecNm: 7
2010-07-17 03:52:38.316 RideOnTime[78:207] SecNm: 8
2010-07-17 03:52:38.321 RideOnTime[78:207] SecNm: 9
2010-07-17 03:52:38.326 RideOnTime[78:207] SecNm: 1
2010-07-17 03:52:38.331 RideOnTime[78:207] SecNm: 2
2010-07-17 03:52:38.338 RideOnTime[78:207] SecNm: 0


【iPhone3GS + iOS4】意図せざる並び...

○取得方法その1
2010-07-17 03:48:12.698 RideOnTime[497:307] Sec 0
2010-07-17 03:48:12.702 RideOnTime[497:307] Sec 5
2010-07-17 03:48:12.708 RideOnTime[497:307] Sec 6
2010-07-17 03:48:12.713 RideOnTime[497:307] Sec 7
2010-07-17 03:48:12.718 RideOnTime[497:307] Sec 8
2010-07-17 03:48:12.723 RideOnTime[497:307] Sec 9
2010-07-17 03:48:12.729 RideOnTime[497:307] Sec 10
2010-07-17 03:48:12.734 RideOnTime[497:307] Sec 11
2010-07-17 03:48:12.739 RideOnTime[497:307] Sec 12
2010-07-17 03:48:12.744 RideOnTime[497:307] Sec 13
2010-07-17 03:48:12.749 RideOnTime[497:307] Sec 14
2010-07-17 03:48:12.754 RideOnTime[497:307] Sec 15
2010-07-17 03:48:12.760 RideOnTime[497:307] Sec 16
2010-07-17 03:48:12.765 RideOnTime[497:307] Sec 17
2010-07-17 03:48:12.770 RideOnTime[497:307] Sec 18
2010-07-17 03:48:12.775 RideOnTime[497:307] Sec 19
2010-07-17 03:48:12.781 RideOnTime[497:307] Sec 20
2010-07-17 03:48:12.785 RideOnTime[497:307] Sec 21
2010-07-17 03:48:12.790 RideOnTime[497:307] Sec 22
2010-07-17 03:48:12.796 RideOnTime[497:307] Sec 23

○取得方法その2
2010-07-17 03:48:15.293 RideOnTime[497:307] SecNm: 0
2010-07-17 03:48:15.295 RideOnTime[497:307] SecNm: 5
2010-07-17 03:48:15.306 RideOnTime[497:307] SecNm: 6
2010-07-17 03:48:15.309 RideOnTime[497:307] SecNm: 7
2010-07-17 03:48:15.312 RideOnTime[497:307] SecNm: 8
2010-07-17 03:48:15.314 RideOnTime[497:307] SecNm: 9
2010-07-17 03:48:15.316 RideOnTime[497:307] SecNm: 1
2010-07-17 03:48:15.319 RideOnTime[497:307] SecNm: 2

--

 ご覧のようにiOS4だと0が始めに来てしまいます。おかげでセクションと時刻データとの組み合わせにもズレが生じて,TableViewが混乱してしまっています。

 デベロッパドキュメントにはinitWithFetchRequest:managedObjectContext:sectionNameKeyPath:cacheName:メソッドについて次のように書いてあります。

sectionNameKeyPath
A key path on result objects that returns the section name. Pass nil to indicate that the controller should generate a single section.

The section name is used to pre-compute the section information.

If this key path is not the same as that specified by the first sort descriptor in fetchRequest, they must generate the same relative orderings. For example, the first sort descriptor in fetchRequest might specify the key for a persistent property; sectionNameKeyPath might specify a key for a transient property derived from the persistent property.

 う〜む,iOS4ではこの記述通りになっていないような気がするのですが...。

iOS4対応作業

user-pic
   

 開発環境を凍結退避していたため,時刻表管理アプリのRide on Timeのアップデート作業はもとよりiOS4対応も後手に回っていた。

 Xcode3.2.3最終版もダウンロードし,ハードディスクの空きも確保したので,開発環境を再構築。アプリのiOS4対応作業を始めた次第である。

 基本的には新しいSDKでコンパイルすることでiOS4向けアプリになる。

 ただし,環境引越しのための作業が伴う。

 ・ライブラリの検索パスを再設定
 ・ベースSDKなどの再設定

 新しいXcodeでは,開発用のプロビジョニングコードを自動取得する機能が追加されているので,従来のようにWebから自分で取得する手間が省けるようだ。
 もっとも証明関係は従来通りと思うけれど,これは以前のものを継続使用しているので実際がどうかは試しておらず分からない。

 さて,問題はアプリのソースコード側の対応である。

--

■AdMob

 Ride on TimeをiOS4対応するにあたって,まず最初に直面した問題はアプリ内広告のAdMob部分であった。

 現行配信版をリリースしてから数ヶ月が経過しているが,その間にもAdMob部分自体がアップデートを繰り返しており,メソッドの仕様が変わっていた。

 iOS4向けに新しいAdMobライブラリに差替えた上で,変わっている仕様に合わせてソースコードに修正を加えた。具体的な作業はメソッド名の変更や廃止になったメソッドの削除などであり,それほど難しいものではなかった。

 ただ,iAdの登場によってAdMobを使い続けることが逆風になるのは心配だなと思う。そもそもiAdはiOS4以降のみ対応なので,iPhone OS3.1系のサポートをどう考えるかも関係する。

--

■UIScrollView Delegate

 手作業で時刻表を入力するため,60個の分ボタンが表示される画面を24時間分用意して,スワイプ移動できるインターフェイスを採用している。

 これを実現するために参考にしたのは『iPhone SDKアプリケーション開発ガイド』(オライリー)の第13章「ページフリック」だった。

 これをアレンジして使用していたが,SDK4でコンパイル後,iOS4上で動かすとデリゲートメソッドのscrollViewDidEndScrollingAnimationが意図しないタイミングに繰り返し呼び出されるようになっていた。必要の無いタイミングに呼ばれれば,動作も意図した通りにはならない。

 最初はありがちなdelegate設定の不備かと思ってあれこれ調べたが,改善される気配は無し。デバッガで追いかけてみるとUIScrollViewInternalから飛んできていることは分かるが,勝手に呼ばれても困る...

 似たような問題に困っている人もいるようだけれど,どうも根本的解決策は示されていないようだ。(How to determine if UIScrollView crash is in my code or Apple's?
 少なくとも3.1.3上では問題なく動作しているのであるから,OS側の挙動が変わったことだけは確かというわけである。

 実のところ,UIScrollView絡みは他にも不明な点がいろいろある。もしかしたらiPhone4解像度の関係で,表示座標系の何か処理が足りないせいかも知れないが,情報が不足している。

--

■CoreDataとTableView

 Ride on Timeは時刻表データをCoreDataにストアして管理している。

 iPhoneはここから読み出したデータをTableViewに簡単に表示できるようになっていて,その辺は楽ちんしている。

 ただ,どうもデータをFetch(取得)しようとするときの挙動が変わってしまっている。

 普通は取得する際のソート順を指定できるので,別途ソート・ディスクリプタというもので順番を指定することでTableViewにもその順番が反映できる。セクションも基本的にはその指定に準じている。

 ところが,iOS4上だと,セクション名が(時刻の)数字であったためか,セクション部分を勝手にソートして並べ替えてしまうみたいなのだ。

 そのため,ソート・ディスクリプタの順とセクションの順に食い違いが起こって,ズレた表示になってしまう。なんだこれは...。

 楽ちんしたのが裏目に出た格好。インデックスに使うデータの中身を確かめて,正しく並べ替えるか,それとも全角文字でセクション名を付ければソートされずに済むかも知れないので,後日実験してみることにする。

--

■iOS4.01と4.1

 ところで,ここ数日でiOS4がアップデートしたりしている。

 何か変化はあるかなと期待して動かしてみたが,上記の問題については変化無し。まだまだ今後のバージョンアップで改善改良を積み重ねなければならないようだ。

 iOS4への変化は,2→3と違って,かなり大掛かりな変化だと思うので,細部の挙動についてはこれから調整に入るのではないかと思う。OS開発チームは大変だなぁと,ちょっと心配な気持ちも膨らむ。

--

 こうした問題に対処する作業は少しずつしたいとは思うものの,時間がかかりそうなので,とりあえずiOS4のアプリ切り替えに対応するレベルでAppStoreに申請することにした。

 細かい問題は残っているとはいえ,アプリ切り替えに対応するだけでも,時刻表アプリはかなり使いやすくなる。まずは大きなメリットの方を享受しよう...。