Generating a PDF file from UIScrollView content


After taking a short break, I decided to get back to writing by covering an interesting problem that I had to work on in the recent past. I had to generate a one page PDF file that contained content presented in a UIScrollView.


PDF file generation process

The process of generating a PDF file using UIKit’s built-in functions is quite straightforward. The code for generating PDF file would look like this:

// 1
UIGraphicsBeginPDFContextToData(outputData, pageDimensions, nil)
// 2
if let context = UIGraphicsGetCurrentContext() {
    // 3
    UIGraphicsBeginPDFPage()
    // 4
}
// 5
UIGraphicsEndPDFContext()
// 6
outputData.write(to: pdfFileUrl, atomically: true)
  1. We first start by creating a PDF graphics context using UIGraphicsBeginPDFContextToData(_:_:_:). This function requires three parameters:
    • NSMutableData object that will contain the PDF output data.
    • CGRect object that defines the size and location of the PDF pages.
    • [AnyHashable: Any] dictionary that can contain additional metadata about the document.
  2. We can access the context by calling UIGraphicsGetCurrentContext(). This will allow us to issue drawing commands to that context.

  3. Before drawing anything we have to call UIGraphicsBeginPDFPage() or UIGraphicsBeginPDFPageWithInfo(_:_:). These functions are used for creating a new page inside the PDF file. All the drawing that happens after calling that function will be transferred onto the newly created page.

  4. Perform drawing operations.

  5. After we have finished drawing we have to call the UIGraphicsEndPDFContext() function to close the graphics context.

  6. As a result, we will have an NSMutableData object that stores the PDF data. The data object can then be used for writing into a file on disk or sharing the data directly.

Note that there is also a second function that can be used to create a PDF context - UIGraphicsBeginPDFContextToFile(_:_:_:). It is just slightly different from the one we used in terms that it writes the result of the drawing directly into a file instead of an NSMutableData object.


Drawing the UIScrollView content to a PDF file

The main problem when generating a PDF from a UIScrollView is capturing all the content inside the UIScrollView. There are a couple of ways of doing that. I’ve found that the most straightforward way was to have a content view as the first subview of the scrollview and using that view to take a snapshot of the scrollview. Based on the PDF file generation process described in the previous paragraph, we could create a function that generates a one page PDF file with the content presented in a UIView like this:

func generatePDFdata(withView view: UIView) -> NSData {

    let pageDimensions = view.bounds
    let outputData = NSMutableData()

    UIGraphicsBeginPDFContextToData(outputData, pageDimensions, nil)
    if let context = UIGraphicsGetCurrentContext() {
        UIGraphicsBeginPDFPage()
        view.layer.render(in: context)
    }
    UIGraphicsEndPDFContext()

    return outputData
}

Now, we only have to pass the scrollview’s content view as a parameter to get the PDF data, and, if we want, write that data into a file.


Handling transparent colors

One interesting thing that I noticed when the PDF file was generated is that the transparency in colors was gone. All views with background colors that had the alpha component less then 1 were opaque, so I had to find a workaround for that issue.
In my case, the superview of the transparent views was white. That meant that I could create the colors that were made as a result of mixing of the transparent colors with the superview’s white color. That way I could simulate color transparency.
I’ve made an extension on UIColor that I used for this purpose:

extension UIColor {

    func withSimulatedTransparency(backgroundColor: UIColor, alpha: CGFloat) -> UIColor {

        var bgRed: CGFloat = 0, bgGreen: CGFloat = 0, bgBlue: CGFloat = 0, bgAlpha: CGFloat = 0
        guard backgroundColor.getRed(&bgRed, green: &bgGreen, blue: &bgBlue, alpha: &bgAlpha) else {
            return self
        }

        var fgRed: CGFloat = 0, fgGreen: CGFloat = 0, fgBlue: CGFloat = 0, fgAlpha: CGFloat = 0
        guard getRed(&fgRed, green: &fgGreen, blue: &fgBlue, alpha: &fgAlpha) else {
            return self
        }

        let resultingRed = fgRed * alpha + bgRed * bgAlpha * (1 - alpha)
        let resultingGreen = fgGreen * alpha + bgGreen * bgAlpha * (1 - alpha)
        let resultingBlue = fgBlue * alpha + bgBlue * bgAlpha * (1 - alpha)

        return UIColor(red: resultingRed, green: resultingGreen, blue: resultingBlue, alpha: 1)
    }

}