Fast and Lean PDF Viewer para iPhone / iPad / iOs: ¿consejos y sugerencias?

369

Ha habido muchas preguntas recientemente sobre dibujar PDF's.

Sí, puedes renderizar PDF muy fácilmente con un UIWebView pero esto no puede proporcionar el rendimiento y la funcionalidad que esperaría de un buen visor de PDF.

Puede dibujar una página PDF en un CALayer o en un UIImage . Apple incluso tiene un código de muestra para mostrar cómo dibujar un PDF grande en una vista de desplazamiento UIS con zoom

Pero los mismos problemas siguen apareciendo.

Método UIImage:

  1. PDF en un UIImage escala no óptica, así como un enfoque de capa.
  2. La CPU y la memoria golpean al generar el UIImagesdesde PDFcontext límites / evitan su uso para crear una representación en tiempo real de nuevos niveles de zoom.

Método CATiledLayer:

  1. Hay una sobrecarga significativa (tiempo) dibujando una página PDF completa a CALayer: se pueden ver mosaicos individuales renderizados (incluso con un ajuste de mosaico)
  2. CALayers no se puede preparar con anticipación (renderizado fuera de pantalla).

En general, los lectores de PDF también tienen bastante memoria. Incluso supervise el uso de memoria del ejemplo de PDF con zoom de Apple.

En mi proyecto actual, estoy desarrollando un visor de PDF y renderizo una UIImagepágina en un hilo separado (¡problemas aquí también!) Y la presento mientras la escala es x1. CATiledLayerla representación comienza una vez que la escala es> 1. iBooks adopta un enfoque similar de doble toma como si desplazara las páginas, puede ver una versión de resolución más baja de la página por menos de un segundo antes de que aparezca una versión nítida.

Estoy renderizando 2 páginas a cada lado de la página en foco para que la imagen PDF esté lista para enmascarar la capa antes de que comience a dibujar. Las páginas se destruyen nuevamente cuando están a +2 páginas de la página enfocada.

¿Alguien tiene alguna idea, no importa cuán pequeña u obvia sea para mejorar el rendimiento / manejo de la memoria de Drawing PDF? o cualquier otro problema discutido aquí?

EDITAR: Algunos consejos (Crédito- Luke Mcneice, VdesmedT, Matt Gallagher, Johann):

  • Guarde cualquier medio en el disco cuando pueda.

  • Use tileSizes más grandes si renderiza en TiledLayers

  • init utiliza matrices frecuentemente con objetos de marcador de posición, alternativamente otro enfoque de diseño es este

  • Tenga en cuenta que las imágenes se procesarán más rápido que un CGPDFPageRef

  • Use NSOperationso GCD & Blocks para preparar las páginas con anticipación.

  • llame CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh); CGContextSetRenderingIntent(ctx, kCGRenderingIntentDefault);antes CGContextDrawPDFPagepara reducir el uso de memoria mientras dibuja

  • iniciar tu NSOperationscon un docRef es una mala idea (memoria), envolver el docRef en un singleton.

  • Cancelar innecesariamente NSOperationsCuando pueda, especialmente si usarán memoria, ¡tenga cuidado de dejar los contextos abiertos!

  • Reciclar objetos de página y destruir vistas no utilizadas

  • Cierre los contextos abiertos tan pronto como no los necesite.

  • al recibir advertencias de memoria, libere y vuelva a cargar el DocRef y cualquier caché de página

Otras características de PDF:

Documentación

Proyectos de ejemplo

Luke Mcneice
fuente
comentando para asegurar que los píos reciban la notificación de edición
Luke Mcneice
+1 y gracias por agregar toda esta información, ojalá la tuviera cuando estaba desarrollando mi lector;) también gracias por agregar mi pregunta sobre anotaciones en PDF (también contiene las respuestas con un código de muestra). Hace unos días abrí esto: stackoverflow.com/questions/4097044/pdf-search-on-the-iphone ¿Tienes algún consejo?
pt2ph8
No lo he cubierto todavía, así que no puedo decir nada más que señalar el blog de ideas aleatorias: random-ideas.net/posts/42 Gracias por la publicación, estoy tratando de reunir todos los problemas de PDF en uno sitio.
Luke Mcneice el
8
En mi empresa, utilizamos para la representación, notación, etc. de PDF, una solución de terceros llamada PSPDFKit, no es barata, pero vale la pena: pspdfkit.com
János
1
+1 Seguí estos consejos útiles para mi visor de código abierto en PDF Swifty PDF github.com/prcela/SwiftyPDF
Prcela

Respuestas:

103

He creado este tipo de aplicación utilizando aproximadamente el mismo enfoque, excepto:

  • Guardo en caché la imagen generada en el disco y siempre genero dos o tres imágenes por adelantado en un hilo separado.
  • No superpongo con un, UIImagesino que dibujo la imagen en la capa cuando el zoom es 1. Esos mosaicos se liberarán automáticamente cuando se emitan advertencias de memoria.

Cada vez que el usuario comienza a hacer zoom, lo adquiero CGPDFPagey lo renderizo usando la CTM apropiada. El código en - (void)drawLayer: (CALayer*)layer inContext: (CGContextRef) contextes como:

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

problema es el objeto que contacta con el CGPDFDocumentRef. Sincronizo la parte donde pdfDocaccedo a la propiedad porque la libero y la vuelvo a crear cuando recibo MemoryWarnings. Parece que el CGPDFDocumentRefobjeto realiza un almacenamiento en caché interno del que no encontré cómo deshacerme.

VdesmedT
fuente
1
¿Cuál es su enfoque cuando el usuario comienza a hacer zoom?
Luke Mcneice el
2
@Luke: he modificado la publicación para responder
VdesmedT
1
Muchas gracias por esto, y el consejo sobre el almacenamiento en caché CGPDFDocumentRef.
Luke Mcneice el
Solo una pregunta rápida: ¿por qué obtiene el pdfPageRef y la transforma cuando la escala es 1.0? porque veo que dibujas una imagen base (¿la imagen del trabajador bg?) antes de crear la transformación.
Luke Mcneice el
@Luke: Publique aquí cualquier mejora en el rendimiento que pueda hacer, por favor. Gracias !
VdesmedT
67

Para un visor de PDF simple y efectivo, cuando solo necesita una funcionalidad limitada, ahora puede (iOS 4.0+) usar el marco QuickLook:

Primero, debes vincular contra QuickLook.frameworky#import <QuickLook/QuickLook.h>;

Luego, en viewDidLoadcualquiera de los métodos de inicialización diferida:

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
fuente
Sí, esto es conceptualmente lo mismo que usar un UIWebView. No seríamos horas descubriendo cómo usar las cosas CGPDF * si fuera tan fácil.
pt2ph8
55
Si seguro. Ciertamente no lo presento como una solución completa. Pero algunos lectores de esta pregunta pueden no ser conscientes de que, para algunos propósitos, esta es una solución razonable. Se actualizó la respuesta para dejar eso claro.
Joshua J. McKinnon
66
+1 por esto. Mi interés principal es permitir que el usuario vea la documentación de la aplicación en formato PDF. No me importa buscar, resaltar o cualquiera de las otras campanas y silbatos, solo la representación de PDF con rendimiento.
memmons
2
Mi categoría UIImage + PDF es un renderizador de PDF rápido y sin sentido con una capa de caché incorporada. github.com/mindbrix/UIImage-PDF
Mindbrix
6

Desde iOS 11 , puede usar el marco nativo llamado PDFKit para mostrar y manipular archivos PDF.

Después de importar PDFKit, debe inicializar a PDFViewcon una URL local o remota y mostrarlo en su vista.

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

Lea más sobre PDFKit en la documentación para desarrolladores de Apple.

the4kman
fuente
-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
fuente