Fast and Lean PDF Viewer for iPhone / iPad / iOS - tips and hints?
Asked Answered
G

3

379

There has been many Questions recently about drawing PDF's.

Yes, you can render PDF's very easily with a UIWebView but this cant give the performance and functionality that you would expect from a good PDF viewer.

You can draw a PDF page to a CALayer or to a UIImage. Apple even have sample code to show how draw a large PDF in a Zoomable UIScrollview

But the same issues keep cropping up.

UIImage Method:

  1. PDF's in a UIImage don't optically scale as well as a Layer approach.
  2. The CPU and memory hit on generating the UIImages from a PDFcontext limits/prevents using it to create a real-time render of new zoom-levels.

CATiledLayer Method:

  1. Theres a significant Overhead (time) drawing a full PDF page to a CALayer: individual tiles can be seen rendering (even with a tileSize tweak)
  2. CALayers cant be prepared ahead of time (rendered off-screen).

Generally PDF viewers are pretty heavy on memory too. Even monitor the memory usage of apple's zoomable PDF example.

In my current project, I'm developing a PDF viewer and am rendering a UIImage of a page in a separate thread (issues here too!) and presenting it while the scale is x1. CATiledLayer rendering kicks in once the scale is >1. iBooks takes a similar double take approach as if you scroll the pages you can see a lower res version of the page for just less than a second before a crisp version appears.

Im rendering 2 pages each side of the page in focus so that the PDF image is ready to mask the layer before it starts drawing.Pages are destroyed again when they are +2 pages away from the focused page.

Does anyone have any insights, no matter how small or obvious to improve the performance/ memory handling of Drawing PDF's? or any other issues discussed here?

EDIT: Some Tips (Credit- Luke Mcneice,VdesmedT,Matt Gallagher,Johann):

  • Save any media to disk when you can.

  • Use larger tileSizes if rendering on TiledLayers

  • init frequently used arrays with placeholder objects, alternitively another design approach is this one

  • Note that images will render faster than a CGPDFPageRef

  • Use NSOperations or GCD & Blocks to prepare pages ahead of time.

  • call CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh); CGContextSetRenderingIntent(ctx, kCGRenderingIntentDefault); before CGContextDrawPDFPage to reduce memory usage while drawing

  • init'ing your NSOperations with a docRef is a bad idea (memory), wrap the docRef into a singleton.

  • Cancel needless NSOperations When you can, especially if they will be using memory, beware of leaving contexts open though!

  • Recycle page objects and destroy unused views

  • Close any open Contexts as soon as you don't need them

  • on receiving memory warnings release and reload the DocRef and any page Caches

Other PDF Features:

Documentation

Example projects

Gaming answered 8/10, 2010 at 10:23 Comment(10)
commenting to ensure peeps get the edit notificationGaming
+1 and thanks for adding all this info, wish I had it when I was developing my reader ;) also thanks for adding my question about PDF annotations (it also contains the answers with sample code). a few days ago I opened this: https://mcmap.net/q/88246/-pdf-search-on-the-iphone do you have any tips?Interline
I haven't covered this myself yet so i couldn't say anything other than point you to the random ideas blog: random-ideas.net/posts/42 Thanks for the post though, Im trying to gather all the PDF issues in one place.Gaming
@Luke Mcneice: The problem is that the code in that post doesn't work with all PDFs (some just show weird characters, I guess it's an encoding issue but I'm not sure), and there's no explanation on how to highlight found text. Surely it's a start though, but far from real world working code...Interline
thats good to know, further down the quartz pdf rabbit hole we go- we need to start a blog about this stuff!Gaming
@Luke Mcneice: +1 and thanks for adding these stuff,can you please help me in a way like how to select the text in the pdf also how to highlight the searched text?I am surfing but not getting any solution?Can you tell me,if you have found any way? Thanks.Mol
This is what stackoverflow is all about. Comprehensive source of informationEaves
@Luke Mcneice: FastPDFkit is also good pdf reader.Lanie
At my company we used for Pdf rendering, notation etc. an 3rd party solution called PSPDFKit, it is not cheap, but worth: pspdfkit.comAmbrose
+1 I followed these useful tips for my open source pdf viewer Swifty PDF github.com/prcela/SwiftyPDFBetteanne
O
104

I have build such kind of application using approximatively the same approach except :

  • I cache the generated image on the disk and always generate two to three images in advance in a separate thread.
  • I don't overlay with a UIImage but instead draw the image in the layer when zooming is 1. Those tiles will be released automatically when memory warnings are issued.

Whenever the user start zooming, I acquire the CGPDFPage and render it using the appropriate CTM. The code in - (void)drawLayer: (CALayer*)layer inContext: (CGContextRef) context is like :

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 is the object containg the CGPDFDocumentRef. I synchronize the part where I access the pdfDoc property because I release it and recreate it when receiving memoryWarnings. It seems that the CGPDFDocumentRef object do some internal caching that I did not find how to get rid of.

Orlando answered 8/10, 2010 at 11:48 Comment(10)
whats your approach when the user starts Zooming?Gaming
@Luke : I've modified the post to answerOrlando
Thanks alot for this, and the tip about the CGPDFDocumentRef caching.Gaming
Just a quick Question: why are you getting the pdfPageRef and transform when the scale is 1.0? because I see that you draw an baseImage (the image from the bg worker?) before you create the transform.Gaming
@Luke : Post here any performance improvement you could made please. Thank you !Orlando
I havant found anything obvious except increasing the tileSize. Although, I also recycle the page objects so i don't have to keep creating them. If i find/remember anything else i will update.Gaming
@LukeMcneice I have tried release and recreate every time in draw layer, but it seems the cache won't remove totally and some leak occurred. Can you check out my question? #3890134Tableland
@Orlando how do you release and reload the cgdocumentref? it seems that mine not release all of the cache at allTableland
Hey guys, if you have a chance could you take a look at the PDF I linked in my question and see if you're solutions work with it: #9504393Dudek
Yo, I've got same question with Lunayo. Anyone can help me fix same issue here, release and recreate still crashes. How do you properly release and recreate the CGPDFRef ? I have email also [email protected] thanks a bunch.Malayalam
D
67

For a simple and effective PDF viewer, when you require only limited functionality, you can now (iOS 4.0+) use the QuickLook framework:

First, you need to link against QuickLook.framework and #import <QuickLook/QuickLook.h>;

Afterwards, in either viewDidLoad or any of the lazy initialization methods:

QLPreviewController *previewController = [[QLPreviewController alloc] init];
previewController.dataSource = self;
previewController.delegate = self;
previewController.currentPreviewItemIndex = indexPath.row;
[self presentModalViewController:previewController animated:YES];
[previewController release];
Deccan answered 8/10, 2010 at 10:23 Comment(4)
Yeah, this is conceptually the same as using an UIWebView. We wouldn't spend hours figuring out how to use CGPDF* stuff if it was that easy.Interline
Yeah, sure. I certainly don't present it as a complete solution. But some readers of this question may not be aware that, for some purposes, this is a reasonable solution. Updated the answer to make that clear.Deccan
+1 for this. My primary interest is to allow the user to view some in-app documentation that is in PDF format. I don't care about searching, highlighting or any of the other bells and whistles -- just performant PDF rendering.Wendish
My UIImage+PDF category is a quick no-nonsense PDF renderer with a built in cacheing layer. github.com/mindbrix/UIImage-PDFSystematics
B
9

Since iOS 11, you can use the native framework called PDFKit for displaying and manipulating PDFs.

After importing PDFKit, you should initialize a PDFView with a local or a remote URL and display it in your view.

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

Read more about PDFKit in the Apple Developer documentation.

Bicorn answered 8/10, 2010 at 10:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.