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)
- 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.
-
We can access the context by calling
UIGraphicsGetCurrentContext()
. This will allow us to issue drawing commands to that context. -
Before drawing anything we have to call
UIGraphicsBeginPDFPage()
orUIGraphicsBeginPDFPageWithInfo(_:_:)
. 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. -
Perform drawing operations.
-
After we have finished drawing we have to call the
UIGraphicsEndPDFContext()
function to close the graphics context. - 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)
}
}