Search code examples
swiftpdfmacos-sierra

Multiple pages view of the same PDF


I am using PDFView from PDFKit (swift programming language, macOS) in order to display some PDF documents. I would like to display multiple pages of the same PDF. A solution would be to create multiple PDFView from the same PDF but in this way I have to allocate multiple time the same memory and this can be a problem for PDF rich in images. Is there any fast way to display different pages of the same PDF in, say, 4/8 different views?

EDIT: The precious suggestions of @danielv brought to this code

import Cocoa
import Quartz

class PDFPageView: NSView {

    var pdfPage: PDFPage?

    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)
        if(pdfPage != nil) {
            let context = NSGraphicsContext.current()
            let pdfBox = PDFDisplayBox.mediaBox        
            let pdfBounds = pdfPage?.bounds(for: pdfBox)
            let scaleX = visibleRect.width/(pdfBounds?.width)!
            let scaleY = visibleRect.height/(pdfBounds?.height)!
            let scale = min(scaleX,scaleY)
            context?.cgContext.scaleBy(x: scale, y: scale)
            pdfPage?.draw(with: pdfBox, to: (context?.cgContext)!)
        }
    }
}

Solution

  • PDFViewmost likely won't serve well for the purpose of displaying multiple pages of the same PDF document.

    But you can to do it yourself. PDFPage can draw itself to any given graphics context. You can create a subclass of NSView to draw a page or use other more fancy views like NSCollectionView and draw pages to its rows/columns/grid.

    Here is roughly how you'd do it with NSView:

    Create a subclass of NSView

    class PDFPageView: NSView {
    
        var pdfPage: PDFPage?
    
        override func draw(_ dirtyRect: NSRect) {
            super.draw(dirtyRect)
    
            let context = NSGraphicsContext.current()!
    
            pdfPage?.draw(with: .mediaBox, to: context.cgContext)
        }
    }
    

    Important: you will need to transform your view's context before drawing to fit the page and make up for any page rotations. PDFPage has some helper functions for this.

    Also note that draw(with box: PDFDisplayBox, to context: CGContext) is since MacOS 10.12, to support previous versions use draw(with box: PDFDisplayBox)

    In your controller, load the PDF document and assign each page to the view that needs to display it.

    class ViewController: NSViewController {
    
        @IBOutlet weak var pdfPageView: PDFPageView?
    
        var pdfDocumenet: PDFDocument?
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let url = URL(fileURLWithPath: "... pdf filename ...")
            pdfDocumenet = PDFDocument(url: url)
    
            pdfPageView?.pdfPage = pdfDocumenet?.page(at: 0)
    
        }
    }
    

    Edit (to include author's comments):

    When you try draw the PDF page on the NSView's context, the page drawing doesn't respect the view's bounds. Try it yourself, you will see what I mean. You need to do the scaling of the context yourself, usually by transforming the current graphics context to the desired coordinates. Search for Cocoa Drawing Guide for detailed information about contexts and transformations. PDFPage provides some helper methods to let you get the original page bounds and the transformation that the page may have used to rotate the page, if it was needed.

    To get the page's rect you use bounds(for box: PDFDisplayBox), not the context size. You need to transform the context before you draw on it. But it's all in the docs, you really need to read the Cocoa/Quartz Drawing Guides and the docs for PDF page. So be sure to take a look there.