Visionneuse PDF rapide et allégée pour iPhone / iPad / iOs - conseils et astuces?

369

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

Oui, vous pouvez rendre le PDF très facilement avec un UIWebViewmais cela ne peut pas donner les performances et les fonctionnalités que vous attendez d'un bon visualiseur PDF.

Vous pouvez dessiner une page PDF sur un CALayer ou sur un UIImage . Apple a même un exemple de code pour montrer comment dessiner un grand PDF dans un UIScrollview zoomable

Mais les mêmes problèmes continuent d'apparaître.

Méthode UIImage:

  1. Les PDF UIImagene sont pas à l'échelle optique ainsi qu'une approche par calque.
  2. Le processeur et la mémoire ont frappé sur la génération de la à UIImagespartir d' une PDFcontext limite / empêche de l' utiliser pour créer en temps réel rendent de nouveaux niveaux de zoom.

Méthode CATiledLayer:

  1. Il y a un temps système important (temps) pour dessiner une page PDF complète sur un CALayer: les tuiles individuelles peuvent être vues en rendu (même avec un ajustement de la taille des tuiles)
  2. CALayers ne peut pas être préparé à l'avance (rendu hors écran).

Généralement, les visionneuses PDF sont également assez gourmandes en 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 fais le rendu UIImaged'une page dans un thread séparé (problèmes ici aussi!) Et je la présente alors que l'échelle est x1. CATiledLayerle rendu entre en jeu une fois que l'échelle est> 1. iBooks adopte une approche à double prise similaire, comme si vous faites défiler les pages, vous pouvez voir une version de résolution inférieure de la page pendant un peu moins d'une seconde avant qu'une version nette n'apparaisse.

Im rendu 2 pages de chaque côté de la page au point afin que l'image PDF soit prête à masquer le calque avant qu'il ne commence à dessiner. Les pages sont détruites à nouveau lorsqu'elles sont à +2 pages de la page focalisée.

Quelqu'un a-t-il des idées, aussi petites ou évidentes pour améliorer les performances / la gestion de la mémoire des dessins PDF? ou d'autres questions discutées ici?

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

  • Enregistrez tous les médias sur le disque lorsque vous le pouvez.

  • Utiliser des tailles de tuile plus grandes si le rendu est sur des tuiles

  • initialiser des tableaux fréquemment utilisés avec des objets d'espace réservé, alternativement une autre approche de conception est celle-ci

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

  • Utilisez NSOperationsou GCD & Blocks pour préparer les pages à l'avance.

  • appeler CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh); CGContextSetRenderingIntent(ctx, kCGRenderingIntentDefault);avant CGContextDrawPDFPagepour réduire l'utilisation de la mémoire lors du dessin

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

  • Annuler inutilement NSOperationsLorsque vous le pouvez, surtout s'ils utilisent de la mémoire, faites attention à ne pas laisser les contextes ouverts!

  • Recyclez les objets de page et détruisez les vues inutilisées

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

  • à la réception des avertissements de mémoire, relâchez et rechargez le DocRef et les caches de page

Autres fonctionnalités PDF:

Documentation

Exemples de projets

Luke Mcneice
la source
commentant pour que les voyants reçoivent la notification de modification
Luke Mcneice
+1 et merci d'avoir ajouté toutes ces informations, j'aurais aimé l'avoir quand je développais mon lecteur;) merci également d'avoir ajouté ma question sur les annotations PDF (elle contient également les réponses avec un exemple de code). il y a quelques jours, j'ai ouvert ceci: stackoverflow.com/questions/4097044/pdf-search-on-the-iphone avez-vous des conseils?
pt2ph8
Je ne l'ai pas encore couvert moi-même, donc je ne pouvais rien dire d'autre que de vous indiquer le blog d'idées aléatoires: random-ideas.net/posts/42 Merci pour le message, j'essaie de rassembler tous les problèmes PDF en un endroit.
Luke Mcneice
8
Dans ma société, nous avons utilisé pour le rendu Pdf, la notation, etc. une solution tierce appelée PSPDFKit, elle n'est pas bon marché, mais vaut: pspdfkit.com
János
1
+1 J'ai suivi ces conseils utiles pour ma visionneuse de pdf open source Swifty PDF github.com/prcela/SwiftyPDF
Prcela

Réponses:

103

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

  • Je mets en cache l'image générée sur le disque et génère toujours deux à trois images à l'avance dans un thread séparé.
  • Je ne superpose pas un UIImagemais dessine plutôt l'image dans le calque lorsque le zoom est 1. Ces tuiles seront libérées automatiquement lorsque des avertissements de mémoire seront émis.

Chaque fois que l'utilisateur commence à zoomer, j'acquiert le CGPDFPageet le rend à l'aide du CTM approprié. Le code en - (void)drawLayer: (CALayer*)layer inContext: (CGContextRef) contextest 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);
    }
}

est l’objet contenant le CGPDFDocumentRef. Je synchronise la partie où pdfDocj'accède à la propriété car je la libère et la recrée lors de la réception de memoryWarnings. Il semble que l' CGPDFDocumentRefobjet fasse une mise en cache interne dont je n'ai pas trouvé comment me débarrasser.

VdesmedT
la source
1
quelle est votre approche lorsque l'utilisateur commence à zoomer?
Luke Mcneice
2
@Luke: J'ai modifié le message pour répondre
VdesmedT
1
Merci beaucoup pour cela, et l'astuce sur la mise en cache CGPDFDocumentRef.
Luke Mcneice
Juste une question rapide: pourquoi obtenez-vous le pdfPageRef et transformez-vous quand l'échelle est 1.0? parce que je vois que vous dessinez une baseImage (l'image du travailleur bg?) avant de créer la transformation.
Luke Mcneice
@Luke: postez ici toute amélioration de performance que vous pourriez apporter. Merci !
VdesmedT
67

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 framework QuickLook:

Tout d'abord, vous devez établir un lien avec QuickLook.frameworket#import <QuickLook/QuickLook.h>;

Ensuite, dans l'une viewDidLoadou l'autre des méthodes d'initialisation paresseuse:

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
la source
Oui, c'est conceptuellement la même chose que d'utiliser un UIWebView. Nous ne passerions pas des heures à découvrir comment utiliser les trucs CGPDF * si c'était aussi simple.
pt2ph8
5
Oui bien sûr. Je ne le présente certainement pas comme une solution complète. Mais certains lecteurs de cette question peuvent ne pas savoir que, à certaines fins, il s'agit d'une solution raisonnable. Mise à jour de la réponse pour que cela soit clair.
Joshua J. McKinnon
6
+1 pour cela. Mon intérêt principal est de permettre à l'utilisateur de visualiser une documentation intégrée au format PDF. Je ne me soucie pas de la recherche, de la mise en évidence ou des autres cloches et sifflets - juste un rendu PDF performant.
memmons
2
Ma catégorie UIImage + PDF est un moteur de rendu PDF rapide et simple avec une couche de cache intégrée. github.com/mindbrix/UIImage-PDF
Mindbrix
6

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 PDFViewavec une URL locale ou distante et l'afficher dans votre vue.

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

En savoir plus sur PDFKit dans la documentation Apple Developer.

the4kman
la source
-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
la source