2012年3月14日水曜日

UIWebViewのズーム


UIWebViewはスクロールビューの描画完了後はユーザーのピンチ操作によってズームが可能ですが、プログラムで行うことも可能です。
UIWebViewは様々なファイルフォーマットを表示できますが、フォーマットによって動作に相違があります。

UIWebView

scalesPageToFit (property)
YESにセットすると初期表示時に横幅がUIWebViewのframeに収まるようにリサイズし、その倍率が1にセットされ、ユーザーがピンチ操作でリサイズできるようになる。
デフォルトはNOなので、ピンチ操作によるズームを可能にするためには、YESをセットします。

UIScrollViewのメソッドによるズーム
-setZoomScale:animated:
-zoomToRect:animated:

UIWebViewのscrollViewに上記メッセージを送ることでズームが行えます。

UIWebView *webView;
float scaleValue;
[webView.scrollView setZoomScale:scaleValue animated:YES];
または
CGRect rect = CGRectMake((float)x, (float)y, (float)width, (float)height);
[webView.scrollView zoomToRect:rect animated:YES];

PDFの場合はanimated=NOでもシャープな画像に再描画されますが、Excelの場合はanimated=NOだと拡大後画像がボケたままとなります。

基本的には上記の方法でズームできるのですが、UIWebViewのデータロードが非同期で行われるため、ロード直後(UIWebViewのデリゲートメソッドのwebViewDidFinishLoad:が呼ばれる時点)ではスクロールビューのcontentSizeがまだ初期値のままのため、ズームが有効になりません。

私はwebViewDidFinishLoad:でNSTimerで別メソッドを呼び、スクロールビューのcontentSizeが大きくなるのを待つようにしました。1ページだけのときはcontentSizeに変化がない(かもしれない)ことに注意してください。 

5 件のコメント:

Unknown さんのコメント...

現在、storyboardを使用し、オリジナルブラウザを作成しています。webViewDidFinishLoad:でNSTimerで別メソッドを呼び、何かの処理をさせているのでしょうか?1ページだけのときはcontentSizeに変化がないので、困っています。
1ページだけだとデバイス回転時に変化がありません。何かいい方法があれば教えて頂きたいのですが。。。

Kenji Nakamura さんのコメント...

こんな方法で対処してました。待ち時間の数値を変えて試してみてください。
1ページだけの場合も少し待っています。
デバイス回転時についてはよくわかりませんが、やはり気にならない程度に待てばよさそうですが...
@implementation MyWebView
{
int cnt;
int numberOfPages;
NSTimer *timerWaiting;
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
.....
numberOfPages= CGPDFDocumentGetNumberOfPages(pdf);
float interval = 0.01; //<--OSのスケジューリングに依存するので、実際はもっと長くなる

//この時点ではまだスクロールビューの描画が完了していないため、timerでさらに描画完了を待つ。
if (timerWaiting == nil) {
cnt = 0;
timerWaiting = [NSTimer scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(didFinishExpandingScrollView)
userInfo:nil
repeats:YES];
}
}

//スクロールビューのframeが拡大したら描画完了とみなす。
//frame拡大後も描画は完了していないので、ページ数に応じてもう少しまつ。
//1ページだけの場合も数回ループする。
- (void)didFinishExpandingScrollView
{
cnt++;
int n1 = 5 + numberOfPages / 5;
int n2 = n1 + 5; //frame拡大後も数回ループする
if (cnt < n1) {
if (numberOfPages == 1
|| self.scrollView.contentSize.height >= pageHeight * (numberOfPages - 1) * initScale) {
cnt = n1;
}
} else if (cnt > n2) {
[timerWaiting invalidate];
timerWaiting = nil;
[self setZoomAnimated:NO];
}
}

Kenji Nakamura さんのコメント...

@interface を書きませんでしたが、このクラスはUIWebViewのサブクラスになってます。
@interface MyWebView : UIWebView
多分selfがUIWebView以外のクラスのため、self.scrollViewでエラーになるのでしょう。
selfをUIWebViewオブジェクトに置き換えてみてください。
setZoomAnimated:メソッドは自分で作ったものです。適当なメソッドを作り、そこでズームを実行してください。
このメソッドではスームインする領域の位置、サイズを計算して、zoomToRect:animated:を実行しています。
別メソッドにしてあるのはロードとローテイトの両方から呼び出すためです。

Kenji Nakamura さんのコメント...

numberOfPages=1固定だと待ち時間も固定になってしまいます。
ループ内で次のようなことをやってみたらいいかがでしょう。
scroolViewのサイズが変更されるのを待つ。
サイズが変更しないこともあるのでタイムアウト(50~100msec程度?)を設ける。
サイズが変更されてもループを継続し、同じサイズが数回連続するのを待つ。
サイズが安定したら描画完了とみなす。
サイズが安定してもScrollViewのZoomが有効になるまでもう少し時間がかかるかもしれないので、同じサイズが連続する回数で調整する。もしかしたら、サイズによって待ち時間を長くする必要があるかもしれません。

Kenji Nakamura さんのコメント...

PDFの場合はwebViewDidFinishLoad:が呼ばれてもまだ描画が完了していなかったのですが、htmlの場合は完了しているようです。htmlしか対象にしないのであれば、Timerによる処理はなくてもズームが行えました。(どんなに大きなサイズでも大丈夫かどうかはわかりませんが...)