Schneller und schlanker PDF Viewer für iPhone / iPad / iOs - Tipps und Hinweise?

368

In letzter Zeit gab es viele Fragen zum Zeichnen von PDFs.

Ja, Sie können PDFs sehr einfach mit a rendern, UIWebViewaber dies kann nicht die Leistung und Funktionalität bieten, die Sie von einem guten PDF-Viewer erwarten würden.

Sie können eine PDF-Seite in einen CALayer oder in ein UIImage zeichnen . Apple verfügt sogar über einen Beispielcode, der zeigt, wie große PDF- Dateien in einer zoombaren UIScrollview gezeichnet werden

Aber die gleichen Probleme tauchen immer wieder auf.

UIImage-Methode:

  1. PDFs in einem UIImagenicht optisch skalierbaren sowie einem Layer-Ansatz.
  2. Die CPU und der Arbeitsspeicher treffen beim Generieren UIImagesaus einem PDFcontext Grenzwert / verhindern, dass ein Echtzeit-Rendering mit neuen Zoomstufen erstellt wird.

CATiledLayer-Methode:

  1. Es gibt einen erheblichen Overhead (Zeit) beim Zeichnen einer vollständigen PDF-Seite auf a CALayer: Einzelne Kacheln können beim Rendern angezeigt werden (selbst bei einer TileSize-Optimierung).
  2. CALayers kann nicht im Voraus vorbereitet werden (außerhalb des Bildschirms gerendert).

Im Allgemeinen sind PDF-Viewer auch ziemlich speicherintensiv. Überwachen Sie sogar die Speichernutzung des zoombaren PDF-Beispiels von Apple.

In meinem aktuellen Projekt entwickle ich einen PDF-Viewer und rendere eine UIImageSeite in einem separaten Thread (Probleme auch hier!) Und präsentiere sie, während der Maßstab x1 ist. CATiledLayerDas Rendern wird gestartet, sobald die Skala> 1 ist. iBooks verfolgt einen ähnlichen Double-Take-Ansatz, als würden Sie beim Blättern durch die Seiten eine Version der Seite mit niedrigerer Auflösung für weniger als eine Sekunde sehen, bevor eine gestochen scharfe Version angezeigt wird.

Ich rendere 2 Seiten auf jeder Seite der Seite im Fokus, so dass das PDF-Bild bereit ist, die Ebene zu maskieren, bevor es mit dem Zeichnen beginnt. Seiten werden wieder zerstört, wenn sie +2 Seiten von der fokussierten Seite entfernt sind.

Hat jemand irgendwelche Erkenntnisse, egal wie klein oder offensichtlich, um die Leistung / Speicherhandhabung von Zeichnungs-PDFs zu verbessern? oder andere hier diskutierte Themen?

EDIT: Einige Tipps (Credit-Luke Mcneice, VdesmedT, Matt Gallagher, Johann):

  • Speichern Sie alle Medien auf der Festplatte, wenn Sie können.

  • Verwenden Sie beim Rendern auf TiledLayers größere tileSizes

  • Init verwendet häufig verwendete Arrays mit Platzhalterobjekten. Alternativ ist dies auch ein anderer Entwurfsansatz

  • Beachten Sie, dass Bilder schneller als a gerendert werden CGPDFPageRef

  • Verwenden Sie NSOperationsoder GCD & Blocks , um Seiten im Voraus vorzubereiten.

  • Rufen Sie CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh); CGContextSetRenderingIntent(ctx, kCGRenderingIntentDefault);vorher CGContextDrawPDFPageauf, um die Speichernutzung beim Zeichnen zu reduzieren

  • Das Initiieren NSOperationsmit einem docRef ist eine schlechte Idee (Speicher). Wickeln Sie das docRef in einen Singleton.

  • Unnötig abbrechen NSOperationsWenn Sie können, insbesondere wenn sie Speicher verwenden, sollten Sie jedoch darauf achten, dass die Kontexte nicht offen bleiben!

  • Bereiten Sie Seitenobjekte auf und zerstören Sie nicht verwendete Ansichten

  • Schließen Sie alle offenen Kontexte, sobald Sie sie nicht benötigen

  • Wenn Sie Speicherwarnungen erhalten, geben Sie DocRef und alle Seiten-Caches frei und laden Sie sie neu

Weitere PDF-Funktionen:

Dokumentation

Beispielprojekte

Luke Mcneice
quelle
Kommentieren, um sicherzustellen, dass Peeps die Bearbeitungsbenachrichtigung erhalten
Luke Mcneice
+1 und danke, dass Sie all diese Informationen hinzugefügt haben. Ich wünschte, ich hätte sie bei der Entwicklung meines Readers.) Auch danke, dass Sie meine Frage zu PDF-Anmerkungen hinzugefügt haben (sie enthält auch die Antworten mit Beispielcode). Vor ein paar Tagen habe ich Folgendes geöffnet: stackoverflow.com/questions/4097044/pdf-search-on-the-iphone Hast du irgendwelche Tipps?
pt2ph8
Ich habe dies selbst noch nicht behandelt, daher konnte ich nichts anderes sagen, als Sie auf den Blog mit zufälligen Ideen zu verweisen : random-ideas.net/posts/42 Vielen Dank für den Beitrag, ich versuche jedoch, alle PDF-Probleme in einem zu sammeln Ort.
Luke Mcneice
8
In meiner Firma haben wir für Pdf-Rendering, Notation usw. eine Drittanbieterlösung namens verwendet PSPDFKit, die nicht billig ist, aber den Wert: pspdfkit.com
János
1
+1 Ich habe diese nützlichen Tipps für meinen Open-Source-PDF-Viewer Swifty PDF github.com/prcela/SwiftyPDF
Prcela

Antworten:

103

Ich habe eine solche Anwendung mit ungefähr demselben Ansatz erstellt, außer:

  • Ich speichere das generierte Image auf der Festplatte zwischen und generiere immer zwei bis drei Images im Voraus in einem separaten Thread.
  • Ich überlagere nicht mit einem, UIImagesondern zeichne stattdessen das Bild in der Ebene, wenn das Zoomen 1 ist. Diese Kacheln werden automatisch freigegeben, wenn Speicherwarnungen ausgegeben werden.

Immer wenn der Benutzer mit dem Zoomen beginnt, erhalte ich das CGPDFPageund rendere es mit dem entsprechenden CTM. Der Code in - (void)drawLayer: (CALayer*)layer inContext: (CGContextRef) contextist wie folgt:

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);
    }
}

Problem ist das Objekt, das das enthält CGPDFDocumentRef. Ich synchronisiere den Teil, auf den ich auf die pdfDocEigenschaft zugreife, weil ich sie freigebe und beim Empfang von memoryWarnings neu erstelle. Es scheint, dass das CGPDFDocumentRefObjekt ein internes Caching durchführt, das ich nicht loswerden konnte.

VdesmedT
quelle
1
Wie gehen Sie vor, wenn der Benutzer mit dem Zoomen beginnt?
Luke Mcneice
2
@ Luke: Ich habe den Beitrag geändert, um zu antworten
VdesmedT
1
Vielen Dank dafür und den Tipp zum CGPDFDocumentRef-Caching.
Luke Mcneice
Nur eine kurze Frage: Warum erhalten Sie das pdfPageRef und transformieren es, wenn der Maßstab 1.0 ist? weil ich sehe, dass Sie ein baseImage (das Bild vom BG-Worker?) zeichnen, bevor Sie die Transformation erstellen.
Luke Mcneice
@ Luke: Posten Sie hier bitte jede Leistungsverbesserung, die Sie vornehmen könnten. Vielen Dank !
VdesmedT
67

Für einen einfachen und effektiven PDF-Viewer können Sie jetzt (iOS 4.0+) das QuickLook-Framework verwenden, wenn Sie nur eingeschränkte Funktionen benötigen:

Zuerst müssen Sie gegen QuickLook.frameworkund verlinken#import <QuickLook/QuickLook.h>;

Anschließend in einer viewDidLoadoder einer der faulen Initialisierungsmethoden:

QLPreviewController *previewController = [[QLPreviewController alloc] init];
previewController.dataSource = self;
previewController.delegate = self;
previewController.currentPreviewItemIndex = indexPath.row;
[self presentModalViewController:previewController animated:YES];
[previewController release];
Joshua J. McKinnon
quelle
Ja, dies entspricht konzeptionell der Verwendung einer UIWebView. Wir würden nicht Stunden damit verbringen, herauszufinden, wie man CGPDF * verwendet, wenn es so einfach wäre.
Pt2ph8
5
Ja sicher. Ich präsentiere es sicherlich nicht als vollständige Lösung. Einige Leser dieser Frage sind sich jedoch möglicherweise nicht bewusst, dass dies für bestimmte Zwecke eine vernünftige Lösung ist. Die Antwort wurde aktualisiert, um dies zu verdeutlichen.
Joshua J. McKinnon
6
+1 dafür. Mein Hauptinteresse ist es, dem Benutzer das Anzeigen einer In-App-Dokumentation im PDF-Format zu ermöglichen. Ich kümmere mich nicht um das Suchen, Hervorheben oder andere Schnickschnack - nur um performantes PDF-Rendering.
Memmons
2
Meine UIImage + PDF-Kategorie ist ein schneller No-Nonsense-PDF-Renderer mit einer integrierten Cache-Ebene. github.com/mindbrix/UIImage-PDF
Mindbrix
6

Seit iOS 11 können Sie das native Framework PDFKit zum Anzeigen und Bearbeiten von PDFs verwenden.

Nach dem Import von PDFKit sollten Sie a PDFViewmit einer lokalen oder einer Remote-URL initialisieren und in Ihrer Ansicht anzeigen.

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

Weitere Informationen zu PDFKit finden Sie in der Apple Developer-Dokumentation.

the4kman
quelle
-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);
     } }
Kuldeep singh
quelle