iPhone / iPad / iO向けの高速で無駄のないPDFビューア-ヒントとヒント?


369

最近、PDFの描画について多くの質問があります。

はい、PDFをで簡単にレンダリングできますUIWebViewが、これは優れたPDFビューアに期待するパフォーマンスと機能を提供できません。

PDFページをCALayerまたはUIImageに描画できます。Appleには、ズーム可能なUIScrollviewで大きなPDF 描画する方法を示すサンプルコードさえあります

しかし、同じ問題が増え続けています。

UIImageメソッド:

  1. PDF UIImageは、レイヤーアプローチと同様に光学的にスケーリングされません。
  2. 制限UIImagesからの生成でCPUとメモリがヒットしPDFcontext、新しいズームレベルのリアルタイムレンダリングを作成するためにそれを使用できなくなります。

CATiledLayerメソッド:

  1. PDFページ全体をaに描画すると、かなりのオーバーヘッド(時間)CALayerが発生します。個々のタイルがレンダリングで表示されます(tileSizeを微調整しても)
  2. CALayers 事前に準備できません(画面外でレンダリングされます)。

一般に、PDFビューアもメモリをかなり消費します。Appleのズーム可能なPDFの例のメモリ使用量も監視します。

現在のプロジェクトでは、PDFビューアを開発UIImageしていて、別のスレッドでページのをレンダリングしています(ここでも問題です)、スケールがx1のときにページを表示しています。CATiledLayerスケールが1を超えると、レンダリングが開始されます。iBooksも同様のダブルテイクアプローチを採用しており、ページをスクロールすると、鮮明なバージョンが表示される前に1秒未満でページの低解像度バージョンを表示できます。

ページの各側にフォーカスがある2ページをレンダリングして、PDFイメージが描画を開始する前にレイヤーをマスクできるようにします。フォーカスされたページから+2ページ離れると、ページは再び破棄されます。

描画PDFのパフォーマンス/メモリ処理を改善するためにどれほど小さくても明白でも、誰かが洞察を持っていますか?またはここで議論された他の問題?

編集:いくつかのヒント(Credit- Luke Mcneice、VdesmedT、Matt Gallagher、Johann):

  • 可能な場合は、メディアをディスクに保存します。

  • TiledLayersでレンダリングする場合は、より大きなtileSizeを使用します

  • INIT頻繁プレースホルダオブジェクトと配列を用い、alternitively別の設計アプローチは、これ

  • 画像は、 CGPDFPageRef

  • NSOperationsまたはGCD&ブロックを使用して、事前にページを準備します。

  • CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh); CGContextSetRenderingIntent(ctx, kCGRenderingIntentDefault);CGContextDrawPDFPageに呼び出して、描画中のメモリ使用量を減らします

  • docRefで初期化するのNSOperationsは悪い考え(メモリ)であり、docRefをシングルトンにラップします。

  • 不要なNSOperations場合はキャンセルできます。特にメモリを使用する場合は、コンテキストを開いたままにしてください。

  • ページオブジェクトをリサイクルし、未使用のビューを破棄する

  • 開いているコンテキストが必要なくなったらすぐに閉じます

  • メモリ警告を受け取ったら、DocRefとページキャッシュを解放して再ロードします

その他のPDF機能:

ドキュメンテーション

プロジェクトの例


のぞき見が編集通知を確実に受け取るようにコメントする
Luke Mcneice 2010年

+1そして、このすべての情報を追加してくれてありがとう、私がリーダーを開発しているときにそれがあったらいいのに;)また、PDFアノテーションに関する質問を追加してくれてありがとう(サンプルコード付きの回答も含まれています)。数日前、私はこれを開きました:stackoverflow.com/questions/4097044/pdf-search-on-the-iphoneヒントはありますか?
pt2ph8 2010年

私はまだこれを取り上げていないので、ランダムなアイデアのブログをランダムに紹介する以外に何も言えませんでした:random-ideas.net/posts/42ありがとうございます。場所。
ルーク・マクニース、2010年

8
私の会社では、Pdfレンダリング、表記法などと呼ばれるサードパーティのソリューションを使用しました。PSPDFKit安価ではありませんが、価値があります。pspdfkit.com
János

1
+1オープンソースのPDFビューアSwifty PDFの これらの便利なヒントに従いましたgithub.com/prcela/SwiftyPDF
Prcela

回答:


103

私は、ほぼ同じアプローチを使用して、このような種類のアプリケーションを構築しました。

  • 生成されたイメージをディスクにキャッシュし、常に別のスレッドで事前に2〜3つのイメージを生成します。
  • 私はオーバーレイしないで、UIImageズームが1のときにレイヤーに画像を描画します。これらのタイルは、メモリ警告が発行されると自動的に解放されます。

ユーザーがズームを開始するたびに、私はを取得CGPDFPageし、適切なCTMを使用してレンダリングします。のコード- (void)drawLayer: (CALayer*)layer inContext: (CGContextRef) contextは次のとおりです:

CGAffineTransform currentCTM = CGContextGetCTM(context);    
if (currentCTM.a == 1.0 && baseImage) {
    //Calculate ideal scale
    CGFloat scaleForWidth = baseImage.size.width/self.bounds.size.width;
    CGFloat scaleForHeight = baseImage.size.height/self.bounds.size.height; 
    CGFloat imageScaleFactor = MAX(scaleForWidth, scaleForHeight);

    CGSize imageSize = CGSizeMake(baseImage.size.width/imageScaleFactor, baseImage.size.height/imageScaleFactor);
    CGRect imageRect = CGRectMake((self.bounds.size.width-imageSize.width)/2, (self.bounds.size.height-imageSize.height)/2, imageSize.width, imageSize.height);
    CGContextDrawImage(context, imageRect, [baseImage CGImage]);
} else {
    @synchronized(issue) { 
        CGPDFPageRef pdfPage = CGPDFDocumentGetPage(issue.pdfDoc, pageIndex+1);
        pdfToPageTransform = CGPDFPageGetDrawingTransform(pdfPage, kCGPDFMediaBox, layer.bounds, 0, true);
        CGContextConcatCTM(context, pdfToPageTransform);    
        CGContextDrawPDFPage(context, pdfPage);
    }
}

問題は、を含むオブジェクトですCGPDFDocumentRefpdfDocプロパティにアクセスする部分は、memoryWarningsを受け取ったときに解放して再作成するため、同期しています。と思われるCGPDFDocumentRef物体が、私は取り除く方法を見つけることができませんでしたことをいくつかの内部キャッシュを行います。


1
ユーザーがズームを開始したときのアプローチはどうですか?
ルークマクナイス

2
@Luke:投稿を変更して回答しました
VdesmedT

1
これ、およびCGPDFDocumentRefキャッシングに関するヒントに感謝します。
ルークマクナイス

簡単な質問:スケールが1.0のときにpdfPageRefを取得して変換するのはなぜですか?変換を作成する前にbaseImage(bg workerからの画像?)を描画していることがわかります。
ルークマクナイス

@Luke:パフォーマンスの改善をここに投稿してください。ありがとうございました !
VdesmedT

67

シンプルで効果的なPDFビューアで、限られた機能のみが必要な場合に、QuickLookフレームワークを使用できるようになりました(iOS 4.0以降)。

まず、あなたはとリンクする必要があるQuickLook.framework#import <QuickLook/QuickLook.h>;

その後、viewDidLoad遅延初期化メソッドのいずれかまたはいずれかで:

QLPreviewController *previewController = [[QLPreviewController alloc] init];
previewController.dataSource = self;
previewController.delegate = self;
previewController.currentPreviewItemIndex = indexPath.row;
[self presentModalViewController:previewController animated:YES];
[previewController release];

ええ、これはUIWebViewを使用するのと概念的に同じです。それが簡単なら、CGPDF *の使い方を理解するのに何時間も費やすことはありません。
pt2ph8

5
ええ、確かに。私はそれを完全な解決策として提示しません。しかし、この質問を読んだ人の中には、目的によってはこれが合理的な解決策であることを知らない人もいます。それを明確にするために回答を更新しました。
ジョシュアJ.マッキノン2011年

6
+1。私の主な関心は、ユーザーがPDF形式のアプリ内ドキュメントを表示できるようにすることです。私は、検索、ハイライト表示、またはその他のベルやホイッスルは気にせず、パフォーマンスの高いPDFレンダリングを行います。
memmons 2011

2
私のUIImage + PDFカテゴリは、組み込みのキャッシュレイヤーを備えた高速で実用的なPDFレンダラーです。 github.com/mindbrix/UIImage-PDF
Mindbrix

6

iOS 11以降、PDFの表示と操作にPDFKitと呼ばれるネイティブフレームワークを使用できます。

PDFKitをインポートした後PDFView、ローカルまたはリモートのURLでを初期化して、ビューに表示する必要があります。

if let url = Bundle.main.url(forResource: "example", withExtension: "pdf") {
    let pdfView = PDFView(frame: view.frame)
    pdfView.document = PDFDocument(url: url)
    view.addSubview(pdfView)
}

Apple DeveloperのドキュメントでPDFKitの詳細を読んでください。


-2
 CGAffineTransform currentCTM = CGContextGetCTM(context);     if
 (currentCTM.a == 1.0 && baseImage)  {
     //Calculate ideal scale
     CGFloat scaleForWidth = baseImage.size.width/self.bounds.size.width;
     CGFloat scaleForHeight = baseImage.size.height/self.bounds.size.height; 
     CGFloat imageScaleFactor = MAX(scaleForWidth, scaleForHeight);
     CGSize imageSize = CGSizeMake(baseImage.size.width/imageScaleFactor,
 baseImage.size.height/imageScaleFactor);
     CGRect imageRect = CGRectMake((self.bounds.size.width-imageSize.width)/2,
 (self.bounds.size.height-imageSize.height)/2, imageSize.width,
 imageSize.height);
     CGContextDrawImage(context, imageRect, [baseImage CGImage]); }  else  {
     @synchronized(issue)  { 
         CGPDFPageRef pdfPage = CGPDFDocumentGetPage(issue.pdfDoc, pageIndex+1);
         pdfToPageTransform = CGPDFPageGetDrawingTransform(pdfPage, kCGPDFMediaBox, layer.bounds, 0, true);
         CGContextConcatCTM(context, pdfToPageTransform);    
         CGContextDrawPDFPage(context, pdfPage);
     } }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.