2013年8月11日日曜日

PDFサムネイル表示例



PDFのサムネイルを表示する一例です。

以下の実装ではページ数の多いPDFでは全ページのサムネイルを作り終えるまで応答がなくなるので操作性に問題があり、またサムネイル作成中にメモリ不足になっても回復不能でアプリが落ちてしまいます。この改善方法についてはPDFサムネイル表示改良版を参照してください。

手順概要
  • 次のクラスを追加する。
    • WebViewWithThumnails UIWebViewのサブクラス
      サムネイル画像表示機能を追加するWebView
    • PdfThumbnailSrollVewi  UIScrollViewのサブクラス
      WebViewWithThumnailsに追加するサムネイル用スクロールビュー
    • PdfImageView UIControlのサブクラス
      サムネイルを表示し、タップに応答するビュー
  • MyViewControllerのviewをWebViewWithThumnailsに変更
    • WebViewWithThumnailsのdelegateにMyViewControllerをセット
  • MyViewControllerにgoogleなどの検索画面を表示
  • MyViewControllerのwebView:shouldStartLoadWithRequest:navigationType:メソッドでURLをチェックし、pdfファイルの場合は処理をトラップ
  • PDFファイルダウンロードが完了したら
    • WebViewWithThumnailsにPDFを表示
    • WebViewWithThumnailsにPdfThumbnailSrollVewi追加
    • PdfThumbnailSrollVewiにサムネールイメージをセット
実装例

PDFファイルをダウンロードする手順はダウンロードファイルをNSDataで取得する方法を参照。
「受信終了」で呼ぶ [self  self loadPdf]以降を例示。

@implementation MyViewController
//PDFファイルダウンロード完了
- (void)connectionDidFinishLoading:(NSConnection*)connection;
{
    _connection = nil;
    [self loadPdf];
}

//受信終了で呼び、ダウンロードした_dataをWebViewにロードする。
- (void)loadPdf
{
    //PDFUIWebViewに表示する
  [ (WebViewWithThumbnails *)self.view loadData:_data
                                                            MIMEType:@"application/pdf"
                                               textEncodingName:@"utf-8"
                                                              baseURL:nil];
}
@end

@interface WebViewWithThumbnails : UIWebView 
- (void)addThumbnailsWithData:(NSData *)pdfData;
@end

@implementation WebViewWithThumbnails
//オーバライド、superを呼んだ後にサムネールをセットする。
- (void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)encodingName baseURL:(NSURL *)baseURL
{
    [self  removeThumbnailScrollView];
    [super loadData:data MIMEType:MIMEType textEncodingName:encodingName baseURL:baseURL];
    [self addThumbnailsWithData:data];
}

//PDF以外の場合に呼ばれる。thumbnailScrollViewをremoveする。
- (void)loadRequest:(NSURLRequest *)request
{
    [self  removeThumbnailScrollView];
    [super loadRequest:request];
}

//subviewsの中にThumbnailScrollViewオブジェクトがあればremoveする。
- (void)removeThumbnailScrollView
{
    for(UIView *v in self.subviews) {
        if ([v isMemberOfClass:[ThumbnailScrollView class]]) {
            [v removeFromSuperview];
            break;
        }
    }
}

//サムネールセット
- (void)addThumbnailsWithData:(NSData *)pdfData
{
    [self  removeThumbnailScrollView];
    //画面下にUIScrollViewを追加
    ThumbnailScrollView *thumbnailScrollView =
        [[ThumbnailScrollView allocinitWithWebView:self height:80];
    //各ページサイズに合わせたThumbnailViewを追加
    [thumbnailScrollView addThumbnailViews:pdfData];
    //ThumbnailViewにイメージをセット
    [thumbnailScrollView setPdfThumbnailImage:pdfData];
}
@end

@interface ThumbnailScrollView : UIScrollView
- (ThumbnailScrollView *)initWithWebView:(UIWebView *)superview height:(float)height;
- (void)addThumbnailViews:(NSData *)pdfData;
- (void)setPdfThumbnailImage:(NSData *)pdfData;
@end

@implementation ThumbnailScrollView
{
    //イメージセットが完了するまで保持する。
    CGPDFDocumentRef _pdf;
}

//保持していた_pdfをreleaseする。
//setPdfThumbnailImageでイメージセット完了時にも実行しているが、
//その前にdeallocされる可能性があるのでここでも実行する。
- (void)dealloc
{
    if (_pdf) CGPDFDocumentRelease(_pdf);
    _pdf = nil;
}

//superviewUIScrollViewを追加。frameはsuperviewに合わせて設定する。
- (ThumbnailScrollView *)initWithWebView:(UIWebView *)webView height:(float)height
{
    //画面下10pt上の位置に設定。高さはパラメータの値、幅はsuperviewと同じ。
    CGRect r = CGRectMake(0, webView.frame.size.height - height - 10,
                          webView.frame.size.width, height);
    self = [super initWithFrame:r];
    if (self) {
        [webView addSubview:self];
    }
    return self;
}

//ScrollView内に、PDFページに対応するThumbnailImageViewを配置する。
//ここではまだイメージはセットしないが、矩形領域は表示されるようになる。
- (void)addThumbnailViews:(NSData *)pdfData
{
    CGDataProviderRef provider =
        CGDataProviderCreateWithCFData((__bridge CFDataRef)pdfData);
    _pdf = CGPDFDocumentCreateWithProvider(provider);
    //providerは同じメソッド内でreleaseしないとメモリリークの原因になる。
    CFRelease(provider);
    
    int numPages = CGPDFDocumentGetNumberOfPages(_pdf);
    float x = 10;
    float h = self.frame.size.height;
    float pagePos = 0;
    UIScrollView *webScrollView = ((UIWebView *)self.superview).scrollView;
    for (int p  = 1; p <= numPages; p++) {
        //CGPDFPageRefautoreleaseされるのでreleaseする必要なし。
        CGPDFPageRef pageRef = CGPDFDocumentGetPage(_pdf, 1);
        CGRect pageRect = CGPDFPageGetBoxRect(pageRef, kCGPDFCropBox);
        float w = pageRect.size.width * (h / pageRect.size.height);
        CGRect imageRect = CGRectMake(x, 0, w, h);
        ThumbnailImageView *v = [[ThumbnailImageView alloc] initWithFrame:imageRect pageNum:p pagePos:pagePos];
        v.alpha = 0.5;
        [self addSubview:v];
        //タップ時のアクションをセット
        [v addTarget:self action:@selector(jumpToPage:) forControlEvents:UIControlEventTouchUpInside];
        x += w + 10; //サムネイル間に10ptの間隔を置く。
        pagePos += pageRect.size.height * (webScrollView.frame.size.width-5) / pageRect.size.width;
        //contentSize調整
        self.contentSize = CGSizeMake(self.contentSize.width + w + 10, self.contentSize.height);
    }    
    [self setNeedsDisplay];
}

//配置したThumbnailImageViewにイメージをセット
- (void)setPdfThumbnailImage:(NSData *)pdfData
{
    size_t numPages = CGPDFDocumentGetNumberOfPages(_pdf);
    for(int p=1; p<=numPages; p++) {        
        CGPDFPageRef pageRef = CGPDFDocumentGetPage(_pdf, p);
        ThumbnailImageView *v = [self.subviews objectAtIndex:p-1];
        [v setImageWithPdfPage:pageRef];
    }
    //ループが無事完了したら_pdfをリリース
    CGPDFDocumentRelease(_pdf);
    _pdf = nil;
    [self setNeedsDisplay];
}

//タップされたサムネールのページ位置に移動
- (void)jumpToPage:(ThumbnailImageView *)sender
{
    UIScrollView *scrollView = ((UIWebView *)self.superview).scrollView;
    CGPoint point = CGPointMake(0, sender.pagePos * scrollView.zoomScale);
    [scrollView setContentOffset:point animated:YES];
}

@end

0 件のコメント:

コメントを投稿