いやいやいや、よーやくネットワークやる前のワンクッションで始めたApple'sサンプルScrollViewSuiteの解析が終わりそうだよ。
長げーよ。プンスカ
サンプルはとっくにできてたけど、ブログにまとめるのに時間食ってんだよね。
ま、でもUIScrollViewが単純にタッチイベントで対応してないとか、デフォルトだと埋め込まれたビューにすぐにタッチイベントが来ないとか、いろいろ発見があって、やっておいてよかったかなっと。
ラストはTiledScrollViewのズーム対応っす。
この前ので、2万 x 2万ピクセルの領域を256x256ピクセル四方のUIViewを9個使うだけでスクロール可能にしたわけで、やろうと思えばドラクエだって作れるぜ~、俺はまだ本気出してないだけ(すまん、読んだことない)、と豪語したわけですが...
ここで問題が一つ発生。
ズームアウトしていったら最終的には全画像分のタイルが必要なのでは?
おお、のおおお。
そのとおりざんす。
じゃ、この場合どう対応すればいいかというのが今回のお題。
どうするかというと、だいたい以下の感じ。
まず、画面上のタイルの大きさが1/2になるまではそのままにしておく。この場合、表示に必要なタイルの数は4倍になるわけです。
でもって、タイルの大きさが1/2になった時点で、タイルの内容部のスケールを1/2にして、タイルの大きさは元に戻す。
で、この処理をタイルの大きさが1/2になるたびに繰り返す。
こうすれば、タイルは多くて36個(今まで9個必要だったので、その4倍)まで必要になるけど、それ以上は必要なくなるわけっす。
これを実現するのに必要なのが
なわけで、そのためにTiledScrollViewはUIScrollViewを継承しsetZoomScaleを置き換え、UIScrollViewDelegateを自身が担当してscrollViewDidEndZoomingを実装してるわけです。
Apple'sサンプルでは、上の2つのメソッドどちらからも呼ばれるメソッド
てのがあって、これが上記調整作業を引き受けているようです。
この調整作業を順を追って説明すると
まず、一番最初は以下のような状態から始まります。
注意)resolutionの値は2の階乗の値です。なので-1で1/2、0で1、1で2を表します。
次に、UIScrollViewのzoomScaleが1.0から0.5に変化すると、画面表示上のタイルの大きさ、contentSizeとも1/2になります。
で、このタイミングでupdateResolutionメソッドが呼び出され、zoomScaleが0.5なら、タイル内容部のスケールを1/2にして、zoomScale自体は1.0に戻そうとするわけです。ただし、そのままzoomScaleを1.0に戻すとcontentSizeも戻ってしまうので、もう一度contentSizeだけ0.5の時の大きさに再設定(内容部のスケールが1/2なので、contentSizeも1/2でないといけない)します。
以上が調整処理。
Apple'sサンプルでは、タイル内容部の画像は1/4、1/2、等倍を用意してるみたいです。
なのでRootViewControllerのloadViewメソッドで
として、タイル内容部のスケール変動範囲を1/4、1/2、等倍に限定しているわけです。
画像ファイル名は
という構成になってて、それぞれTiledScrollViewDataSourceのtiledScrollViewの引数row、column、resolutionに対応します。
私の方は画像を使わないので、scViewControllerのloadViewでタイル内容部のスケール変動範囲を1/64、1/32、1/16、1/8、1/4、1/2、等倍、2、4、8、16、32、64としてみました。
注)それと、setMaximumZoomScaleを64以上にしておかないと、デフォルトは1なので、等倍以上に拡大できない。
あとは、reloadDataWithNewContentSizeでsetContentSizeの前に
として、ズーム関係の変数を初期状態にする作業を追加するのと、layoutSubviewsでtileSize.width、tileSize.heightを使っていた部分はzoomScaleが変化する(0.5~2.0の間で)可能性があるので、その時のzoomScaleをかけた値を画面上のタイルの大きさ
として、割り当てるタイルの配置を決定する。
てのをやり、最後にviewForZoomingInScrollViewではnilではなくtileContainerViewを返すようにしてます。あとtileViewもzoomScaleの変化に対応できるよう若干修正。
うりゃっと起動。
うっ、今気づいたけどシミュレータだとダブルフィンガータップ認識しないんだな...
後はサンプル動かしてデバッガで気になるところで止めながら各自勉強していってください。
日本語で注釈つけといたし~。適当だけど。
------------
サンプルプロジェクト:sc9.zip
長げーよ。プンスカ
サンプルはとっくにできてたけど、ブログにまとめるのに時間食ってんだよね。
ま、でもUIScrollViewが単純にタッチイベントで対応してないとか、デフォルトだと埋め込まれたビューにすぐにタッチイベントが来ないとか、いろいろ発見があって、やっておいてよかったかなっと。
ラストはTiledScrollViewのズーム対応っす。
この前ので、2万 x 2万ピクセルの領域を256x256ピクセル四方のUIViewを9個使うだけでスクロール可能にしたわけで、やろうと思えばドラクエだって作れるぜ~、俺はまだ本気出してないだけ(すまん、読んだことない)、と豪語したわけですが...
ここで問題が一つ発生。
ズームアウトしていったら最終的には全画像分のタイルが必要なのでは?
おお、のおおお。
そのとおりざんす。
じゃ、この場合どう対応すればいいかというのが今回のお題。
どうするかというと、だいたい以下の感じ。
まず、画面上のタイルの大きさが1/2になるまではそのままにしておく。この場合、表示に必要なタイルの数は4倍になるわけです。
でもって、タイルの大きさが1/2になった時点で、タイルの内容部のスケールを1/2にして、タイルの大きさは元に戻す。
で、この処理をタイルの大きさが1/2になるたびに繰り返す。
こうすれば、タイルは多くて36個(今まで9個必要だったので、その4倍)まで必要になるけど、それ以上は必要なくなるわけっす。
これを実現するのに必要なのが
ズームのタイミングで特定の処理が実行される機構
なわけで、そのためにTiledScrollViewはUIScrollViewを継承しsetZoomScaleを置き換え、UIScrollViewDelegateを自身が担当してscrollViewDidEndZoomingを実装してるわけです。
Apple'sサンプルでは、上の2つのメソッドどちらからも呼ばれるメソッド
- (void)updateResolution
てのがあって、これが上記調整作業を引き受けているようです。
この調整作業を順を追って説明すると
まず、一番最初は以下のような状態から始まります。
注意)resolutionの値は2の階乗の値です。なので-1で1/2、0で1、1で2を表します。
次に、UIScrollViewのzoomScaleが1.0から0.5に変化すると、画面表示上のタイルの大きさ、contentSizeとも1/2になります。
で、このタイミングでupdateResolutionメソッドが呼び出され、zoomScaleが0.5なら、タイル内容部のスケールを1/2にして、zoomScale自体は1.0に戻そうとするわけです。ただし、そのままzoomScaleを1.0に戻すとcontentSizeも戻ってしまうので、もう一度contentSizeだけ0.5の時の大きさに再設定(内容部のスケールが1/2なので、contentSizeも1/2でないといけない)します。
以上が調整処理。
Apple'sサンプルでは、タイル内容部の画像は1/4、1/2、等倍を用意してるみたいです。
なのでRootViewControllerのloadViewメソッドで
[imageScrollView setMaximumResolution:0];
[imageScrollView setMinimumResolution:-2];
として、タイル内容部のスケール変動範囲を1/4、1/2、等倍に限定しているわけです。
画像ファイル名は
画像名_スケール_横番号_縦番号.png
という構成になってて、それぞれTiledScrollViewDataSourceのtiledScrollViewの引数row、column、resolutionに対応します。
スケール = 2のresolution乗
横番号 = column
縦番号 = row
私の方は画像を使わないので、scViewControllerのloadViewでタイル内容部のスケール変動範囲を1/64、1/32、1/16、1/8、1/4、1/2、等倍、2、4、8、16、32、64としてみました。
注)それと、setMaximumZoomScaleを64以上にしておかないと、デフォルトは1なので、等倍以上に拡大できない。
[scrollView setMaximumResolution:6];
[scrollView setMinimumResolution:-6];
あとは、reloadDataWithNewContentSizeでsetContentSizeの前に
[self setZoomScale:1.0];
[self setMinimumZoomScale:1.0];
[self setMaximumZoomScale:1.0];
resolution = 0;
として、ズーム関係の変数を初期状態にする作業を追加するのと、layoutSubviewsでtileSize.width、tileSize.heightを使っていた部分はzoomScaleが変化する(0.5~2.0の間で)可能性があるので、その時のzoomScaleをかけた値を画面上のタイルの大きさ
float scaledTileWidth = tileSize.width * [self zoomScale];
float scaledTileHeight = tileSize.height * [self zoomScale];
として、割り当てるタイルの配置を決定する。
てのをやり、最後にviewForZoomingInScrollViewではnilではなくtileContainerViewを返すようにしてます。あとtileViewもzoomScaleの変化に対応できるよう若干修正。
うりゃっと起動。
うっ、今気づいたけどシミュレータだとダブルフィンガータップ認識しないんだな...
後はサンプル動かしてデバッガで気になるところで止めながら各自勉強していってください。
日本語で注釈つけといたし~。適当だけど。
------------
サンプルプロジェクト:sc9.zip