まず、画面をはみ出したサムネイルを見えるようにするには、その(228)で紹介したUIScrollViewを使います。その(118)からその(178)にかけていろいろ実験したアレざます。ちなみにその(178)でやったnextResponder を使う手は、superを使いなさいという事になっています。
 ま、それはおいといて、こいつはUIViewの派生クラスなんですが、仮想のエリア
contentsSizeプロパティ

 ってのを設定できるようになってまして、こいつで指定される範囲の一部をboundsで切り取って表示するって芸当ができます。
 切り取る矩形は、左上を
contentsOffsetプロパティ

 で指定し、boundsプロパティのsizeの範囲ちゅーことになっとります。ま、ここらへんの理屈はその(228)で実践したとおりですわ。

$テン*シー*シー-3

 なので、前回のbaseViewをUIViewからUIScrollViewにしてサムネイル画面を貼付けていき、contentsSizeの横幅を画面の横幅、高さを、全サムネイルが表示できる高さに設定してやれば、その範囲を自在にスクロールしてくれます。
@implementation ThumbnailViewController {
UIActivityIndicatorView* _activityIndicatorView;
UIScrollView* _baseview; ← UIViewから変更
}
・・・
- (void)viewDidLoad
{
[super viewDidLoad];
// 先に画像用のベース作成、貼付け
_baseview = [[[UIScrollView alloc] initWithFrame:self.view.bounds]autorelease];
_baseview.contentSize = CGSizeMake(self.view.bounds.size.width, 2000);
[self.view addSubview:_baseview];
・・・

 とまあ、上のように数カ所変更すれば全サムネイル画像がスクロールして見えるようになるわけっす。_baseview.contentSizeの高さに2000と指定しているのは適当。

$テン*シー*シー-4

 で、残るは、動かせるようになるまでに時間がかかるという問題なわけですが…

 正統派ストロングスタイルなら、読み込み処理自体の高速化となるわけで、これは2コアCPUのiPad 2とかなら実際1.5~2倍弱程度まで高速化が可能と思います。Grand Central Dispatchというやつで、2コアを効率的に使うわけですな。これはまたいずれ。
 それでも、せいぜい2倍なわけで、200枚が400枚になれば同じだけ待たされるわけですよ。
 
 となると残された手は、ぶっちゃけ、いろいろごまかしを使って体感速度をあげるしかないわけですわ。
 使い勝手のいいアプリってのは、このごまかし方がうまいアプリというわけです。
 例えば前回みたいにUIActivityIndicatorViewを貼付けてぐるぐる回すだけでも、人によっては「あら、ちゃんと仕事してるじゃな~い。」ってなってくれるわけです。
 
 といってもグルグル回ってる間、ホームボタン以外なにも反応しないわけで、これが何秒も続くのはやっぱ心証が悪いわけですよ。
 なんとか、もうちょっと反応をよくできないか?

 そこで考えられるのが、読み込む画像ファイルを、画面に表示されている部分のファイルだけで終わらせるって方法です。
 必要とされるのは表示されている部分だけなんだから、なにも最初に全画像ファイルを読み込まなくてもいいじゃんというわけですな。

$テン*シー*シー-1

 こうすると、どんなに画像ファイルが増えても、画面に見えている分の読み込みの時間で済むわけですよ。でもってサムネイルの大きさを100 x 100くらいにすれば、12個くらいの読み込みで済んじゃうわけですよ。
 残りは、スクロールされた時に、新しく表示される部分の画像ファイルを逐次読み込んでいく。

$テン*シー*シー-2

 このやり方を実現するには、
 1)現在見えている領域を知る方法
 2)見えている領域が変化したタイミングを知る方法

 の知識が必要になるわけです。
 1)はすでに既知の知識であるUIScrollViewのcontentsOffsetとboundsプロパティを使えばいいわけですな。

 残るは2)ということですが、これにはUIViewの
- layoutSubviewsメソッド

 をオーバーライドするのが一番手っ取り早いです。
 このメソッドは表示領域が変化したとき呼び出されるメソッドで、必要なら、変更された表示領域に合わせて自分の入れ子にしたUIView達の位置を調整したりするのに使います。
 縦置き、横置きでUIViewの矩形が変化する時なんかによく使います。
 ドリルのQ27でも紹介したautoresizeMaskプロパティの設定では対応しきれない場合に使うわけですわ。

 で、こいつはUIScrollViewはUIViewから派生してるので、スクロールして表示領域が変化したときにも呼び出されるんですな。
 その時に、新しく見えた領域で、まだ画像が読み込まれていないサムネイルがあれば、ここで読み込むわけです。

 そのため、UIScrollViewクラスを派生させて新しいクラスを用意することにします。
@interface ThumbTileView : UIScrollView

 そして、あらかじめサムネイルの元画像ファイルの数だけUIImageViewをタイル状に貼っておくようにします。このために用意するのが、渡されたサムネイル元画像ファイルパスの配列分のUIImageViewを敷き詰めるメソッド。ひとつのUIImageViewの大きさも指定できるようにします。
- (void)setImages:(NSArray*)images thumbSize:(CGSize)thumbSize

 でもって各UIImageViewの
tagプロパティ

 には通し番号を振るようにします。

$テン*シー*シー-5

 tagプロパティはNSInteger型でプログラマが自由に使っていいプロパティざます。
 具体的な実装は以下の感じ。
@interface ThumbTileView : UIScrollView {
NSArray* _images; // サムネイル元画像ファイルパスの配列
}
@end

@implementation ThumbTileView

// 渡されたimagesとthumbSizeでUIImageViewをタイル状に敷き詰める。
- (void)setImages:(NSArray*)images thumbSize:(CGSize)thumbSize
{
// 以前貼られていたUIImageViewをすべて取りはぶく
NSArray* views = [self.subviews copy];
for (UIView* view in views) {
[view removeFromSuperview];
}
[views release];

// 新しいUIImageViewの敷き詰め準備
_images = images; // ThumbnailViewController側でretainしてるので、
//こちらはretainしなくても特に問題はない。
CGRect r = CGRectMake(0, 0, thumbSize.width, thumbSize.height);
int max = [_images count];

// 必要なcontentSizeの計算
int rowCount = (int)self.bounds.size.width / (int)thumbSize.width;
int vCount = (max + rowCount - 1) / rowCount;
self.contentSize = CGSizeMake(self.bounds.size.width, vCount * thumbSize.height);

// 必要なUIImageViewを敷き詰める。
for (int tag = 1; tag <= max; tag++) {
UIImageView* imageview = [[[UIImageView alloc]
initWithFrame:CGRectInset(r, 4, 4)]autorelease];
imageview.tag = tag;
[self addSubview:imageview];
r = CGRectOffset(r, r.size.width, 0);
if ((r.origin.x + r.size.width) > self.bounds.size.width) {
r.origin.x = 0;
r = CGRectOffset(r, 0, r.size.height);
}
}
}
@end


 この通し番号をtagに設定することで、サムネイル元画像ファイルパス配列のインディックスとUIImageViewを結びつけるわけです。
 ここで活躍するのがUIViewに送れるメッセージ
-viewWithTag:

 こいつはメッセージを送ったUIViewの入れ子(addSubviewした)UIViewの中で、引数に指定した番号が設定されたtagプロパティを持つUIViewを返してくれるんですよ。
 なので、例えばファイル配列の10番目に対応するUIImageViewを取り出す場合、tagには1から通し番号を振っているので10+1を指定してやればいいって事になります。tagの初期値には0が設定されているので1から初めています。
UIImageView* imageview = (UIImageView*)[self viewWithTag:10+1];

 で、+layoutSubviewsで現在表示されている領域と重なってないかを全UIImageViewについてチョックして、重なってるやつでimageプロパティがnilなやつは、まだ画像が設定されていないので読み込むようにするわけです。
- (void)layoutSubviews
{
[super layoutSubviews];
if (_images == nil) {
return;
}
CGRect visibleFrame = CGRectMake(self.contentOffset.x, self.contentOffset.y,
self.bounds.size.width, self.bounds.size.height);
int tag = 1;
for (NSString* filepath in _images) {
UIImageView* imageview = (UIImageView*)[self viewWithTag:tag++];
if (CGRectIntersectsRect(visibleFrame, imageview.frame) == NO) {
continue;
}
if (imageview.image) {
continue;
}
UIImage* image = [UIImage imageWithContentsOfFile:filepath];
imageview.image = [self resizeImage:image size:imageview.bounds.size];
}
}

 ここで使ってる-resizeImage:size:メソッドは、もともとThumbnailViewControllerにあったやつを移動させただけでOK。
 これで、スクロールされた時に、新しく表示される部分の画像ファイルを逐次読み込んでいくことができるわけですな。

$テン*シー*シー-6

 Runしてみると結構すばやく画面にサムネイルが表示されます。で、スクロールするとぎこちなくスクロールしていく。ただし、一度画面に現れた部分のスクロールはスムースになる。
 メモリが心配な人は、スクロールで画面外になったUIImageViewのimageプロパティをnilにする事で、いつでもスクロールはぎこちないけど、メモリはあんまり使わないってバージョンを作るといいでしょう。そこらへんは各自で。

 まあ、前回に比べたら雲泥の操作性なわけなんですが、やっぱ、スクロールがぎこちないのが嫌だなあという完璧主義者な人は、非同期での画像読み込みを使う手があるじゃな~い、なわけですが、こいつが結構めんどくさいんですよ。
 これは次回。

------------
サンプルプロジェクト:ThumbnailView-3.zip