web-dev-qa-db-fra.com

Rapide et Lean PDF Visionneuse pour iPhone / iPad / iOS - Trucs et astuces?

Il y a eu récemment beaucoup de questions sur le dessin de PDF.

Oui, vous pouvez très facilement rendre les PDF avec un UIWebView, mais cela ne donne pas les performances et les fonctionnalités que vous attendez d'un bon PDF spectateur.

Vous pouvez dessiner une PDF page vers un CALayer ou vers un UIImage . Apple ont même un exemple de code montrant comment dessiner un grand PDF dans une UIScrollview zoomable

Mais les mêmes problèmes ne cessent de surgir.

Méthode UIImage:

  1. Les fichiers PDF dans une UIImage n'échelonnent pas optiquement aussi bien qu'une approche par couche.
  2. La CPU et la mémoire ont réussi à générer la UIImages à partir d'une PDFcontext limite/empêche de l'utiliser pour créer un rendu en temps réel de nouveaux niveaux de zoom.

Méthode CATiledLayer:

  1. Il y a une surcharge significative (temps) qui dessine une PDF page complète vers une CALayer: on peut voir le rendu des tuiles individuelles (même avec un tileSize Tweak)
  2. CALayers ne peut pas être préparé à l'avance (rendu hors écran).

En règle générale, PDF les téléspectateurs ont également beaucoup de mémoire. Surveillez même l'utilisation de la mémoire de l'exemple PDF zoomable d'Apple.

Dans mon projet actuel, je développe une visionneuse PDF et je suis en train de rendre une UIImage d'une page dans un fil séparé (dans ce cas aussi!) Et de la présenter pendant que l'échelle est x1. CATiledLayer le rendu commence lorsque la balance est> 1. iBooks adopte une approche similaire en matière de double prise, comme si vous faites défiler les pages, vous pouvez voir une version de résolution inférieure de la page un peu moins d'une seconde avant qu'une version nette ne s'affiche.

Le rendu est effectué sur 2 pages de chaque côté de la page afin que l'image PDF soit prête à masquer le calque avant de commencer à dessiner. Les pages sont à nouveau détruites à +2 pages de la page sélectionnée.

Quelqu'un a-t-il des idées, aussi minimes soit-elles, pour améliorer la gestion des performances/de la mémoire des PDF Drawing? ou d'autres questions discutées ici?

EDIT: Quelques astuces (Crédit: Luke Mcneice, VdesmedT, Matt Gallagher, Johann):

  • Enregistrez tout support sur le disque quand vous le pouvez.

  • Utiliser des tailles de pavé plus grandes si le rendu est effectué sur des couches de mosaïque

  • arrays fréquemment utilisés avec des objets de substitution, une autre approche de conception est: celle-ci

  • Notez que les images seront rendues plus rapidement qu'un CGPDFPageRef

  • Utilisez NSOperations ou GCD & Blocks pour préparer les pages à l’avance.

  • appelez CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh); CGContextSetRenderingIntent(ctx, kCGRenderingIntentDefault); avant CGContextDrawPDFPage pour réduire l'utilisation de la mémoire pendant le dessin

  • initier votre NSOperations avec un docRef est une mauvaise idée (mémoire), enveloppez le docRef dans un singleton.

  • Annuler inutile NSOperations Lorsque vous pouvez, surtout s’ils utiliseront la mémoire, méfiez-vous de laisser les contextes ouverts!

  • Recycler les objets de page et détruire les vues inutilisées

  • Fermez tous les contextes ouverts dès que vous n'en avez plus besoin

  • lors de la réception des avertissements de mémoire, relâchez et rechargez la DocRef et toutes les pages Caches

Autres PDF Caractéristiques:

Documentation

Exemples de projets

366
Luke Mcneice

J'ai construit ce genre d'application en utilisant approximativement la même approche sauf:

  • Je cache l'image générée sur le disque et génère toujours deux ou trois images à l'avance dans un fil séparé.
  • Je ne superpose pas de UIImage, mais dessine l'image dans le calque lorsque le zoom est égal à 1. Ces tuiles sont automatiquement libérées lorsque des avertissements de mémoire sont émis.

À chaque fois que l'utilisateur commence à zoomer, j'obtiens la CGPDFPage et la restitue à l'aide du CTM approprié. Le code dans - (void)drawLayer: (CALayer*)layer inContext: (CGContextRef) context est comme:

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

issue est l'objet contenant la CGPDFDocumentRef. Je synchronise la partie où j'accède à la propriété pdfDoc parce que je la libère et la recrée lors de la réception de memoryWarnings. Il semble que l’objet CGPDFDocumentRef effectue une mise en cache interne dont je n’ai pas trouvé le moyen de se débarrasser.

103
VdesmedT

Pour une visionneuse PDF simple et efficace, lorsque vous n’avez besoin que de fonctionnalités limitées, vous pouvez désormais (iOS 4.0+) utiliser le cadre QuickLook:

Tout d’abord, vous devez créer un lien avec QuickLook.framework et #import <QuickLook/QuickLook.h>;

Ensuite, dans viewDidLoad ou l’une des méthodes d’initialisation différée:

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

Depuis iOS 11 , vous pouvez utiliser le framework natif appelé PDFKit pour afficher et manipuler des PDF.

Après avoir importé PDFKit, vous devez initialiser un PDFView avec une URL locale ou distante et l’afficher dans votre affichage.

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

Pour en savoir plus sur PDFKit dans la Apple Documentation du développeur.

6
Tamás Sengel