Convert A Web Page Into A PDF and Save/Share/Copy — iOS

Diayan Siat
5 min readJan 25, 2023

--

Photo by Maksym Kaharlytskyi on Unsplash

If you have been building iOS apps for a while, chances are, you have had to implement saving, sharing or copying files from your app to your phone or to other apps.

Recently I worked on a feature in which users needed to be able to save/share/copy a pdf file to their phones. That was not my first time interacting with the iOS file system. What was new this time however was that, the said pdf file did not exist. All I had was a url link that the user could click to open in a webview. At a glance, I had no idea what to do, but as a developer, I knew there was a way. In this article I will teach you how to convert a webview into a PDF and then save it to the files app.

To convert a web view into a PDF and save it to the files app, you will need to use WKWebview class and the UIGraphicsPDFRenderer class. Below is a step by step guide on how to achieve this.

  1. In your Swift file, import the following UIKit and Webkit
import UIKit
import WebKit

2. Next, create a WebView object and load the website that you want to convert to a PDF. You can do this by using the load(_:) method of the WebView class.

let webView = WKWebView()
// Load a website into the web view
let url = URL(string: "https://www.apple.com")! //replace with your own url
webView.load(URLRequest(url: url))

3. Now that you have a webview displays the web page you want to convert to a PDF, it’s time to actually create the PDF. To do this, you’ll use the drawViewHierarchy(_:afterScreenUpdates:) method of the UIWebView.

let pdfData = NSMutableData()
UIGraphicsBeginPDFContextToData(pdfData, webView.bounds, nil)
webView.drawViewHierarchy(webView.bounds, afterScreenUpdates: true)
UIGraphicsEndPDFContext()

This creates a PDF of the web view’s content and stores it in the pdfData object. You can go ahead and save this to your phone.

However if you want the PDF rendered with some customizations such as the width and height for printing, you can take advantage of the iOS printing system. Below is how you would do this:

//create print formatter object 
let printFormatter = webView.viewPrintFormatter()
// create renderer which renders the print formatter's content on pages
let renderer = UIPrintPageRenderer()
renderer.addPrintFormatter(printFormatter, startingAtPageAt: 0)

// Specify page sizes
let pageSize = CGSize(width: 595.2, height: 841.8) //set desired sizes

// Page margines
let margin = CGFloat(20.0)

// Set page sizes and margins to the renderer
renderer.setValue(NSValue(cgRect: CGRect(x: margin, y: margin, width: pageSize.width, height: pageSize.height - margin * 2.0)), forKey: "paperRect")
renderer.setValue(NSValue(cgRect: CGRect(x: 0, y: 0, width: pageSize.width, height: pageSize.height)), forKey: "printableRect")

// Create data object to store pdf data
let pdfData = NSMutableData()
// Start a pdf graphics context. This makes it the current drawing context and every drawing command after is captured and turned to pdf data
UIGraphicsBeginPDFContextToData(pdfData, CGRect.zero, nil)

// Loop through number of pages the renderer says it has and on each iteration it starts a new pdf page
for i in 0..<renderer.numberOfPages {
UIGraphicsBeginPDFPage()
// draw content of the page
renderer.drawPage(at: i, in: UIGraphicsGetPDFContextBounds())
}
// Close pdf graphics context
UIGraphicsEndPDFContext()

The above code snippet does the same thing as the previous one, however, it allows you to customize the pdf as much as you want.

4. Finally, you have your PDF of the web page, to save it to the Files app, you will use the UIActivityViewController class. You’ll create an instance of UIActivityViewController and set its activityItems property to an array of file URLs for the PDF data like below and present it:

let filePath = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("webview.pdf")
try? pdfData.write(to: filePath, options: .atomic)
let activityViewController = UIActivityViewController(activityItems: [filePath], applicationActivities: [])
present(activityViewController, animated: true, completion: nil)

Here’s how the entire code will look like:

import UIKit
import WebKit

class ViewController: UIViewController {
let webView = WKWebView()

override func viewDidLoad() {
super.viewDidLoad()
layoutViews()
configureWebView()
configureSaveButton()
}

func configureWebView() {
// Load a website into the web view
let url = URL(string: "https://www.apple.com")!

let urlRequest = URLRequest(url: url)
self.webView.load(urlRequest)
}

func layoutViews() {
view.addSubview(webView)
webView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16),
webView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])
}

func configureSaveButton() {
let screenSize: CGRect = UIScreen.main.bounds
let navBar = UINavigationBar(frame: CGRect(x: 0, y: 40, width: screenSize.width, height: 44))
let navItem = UINavigationItem(title: "")
let saveButton = UIBarButtonItem(title: "Save", style: .plain, target: self, action: #selector(convertandSavePdfToDevice))
navItem.rightBarButtonItem = saveButton
navBar.setItems([navItem], animated: false)
self.view.addSubview(navBar)

}

@objc func convertandSavePdfToDevice() {
//create print formatter object
let printFormatter = webView.viewPrintFormatter()
// create renderer which renders the print formatter's content on pages
let renderer = UIPrintPageRenderer()
renderer.addPrintFormatter(printFormatter, startingAtPageAt: 0)

// Specify page sizes
let pageSize = CGSize(width: 595.2, height: 841.8) //set desired sizes

// Page margines
let margin = CGFloat(20.0)

// Set page sizes and margins to the renderer
renderer.setValue(NSValue(cgRect: CGRect(x: margin, y: margin, width: pageSize.width, height: pageSize.height - margin * 2.0)), forKey: "paperRect")
renderer.setValue(NSValue(cgRect: CGRect(x: 0, y: 0, width: pageSize.width, height: pageSize.height)), forKey: "printableRect")

// Create data object to store pdf data
let pdfData = NSMutableData()
// Start a pdf graphics context. This makes it the current drawing context and every drawing command after is captured and turned to pdf data
UIGraphicsBeginPDFContextToData(pdfData, CGRect.zero, nil)

// Loop through number of pages the renderer says it has and on each iteration it starts a new pdf page
for i in 0..<renderer.numberOfPages {
UIGraphicsBeginPDFPage()
// draw content of the page
renderer.drawPage(at: i, in: UIGraphicsGetPDFContextBounds())
}
// Close pdf graphics context
UIGraphicsEndPDFContext()


let filePath = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("webview.pdf")
try? pdfData.write(to: filePath, options: .atomic)
let activityViewController = UIActivityViewController(activityItems: [filePath], applicationActivities: [])
present(activityViewController, animated: true, completion: nil)
}
}

And that’s it, you’re done! You a user can now click the save button, and choose to save/share/copy the pdf.

Thanks for coming this far, if you found this article helpful, please give it a clap and follow for more.

In the next several articles, I will be doing a series on conccurency on iOS focusing on the Grand Central Dispatch. Follow me if you are interested this topic and would like to learn more.

--

--

Diayan Siat

Android | iOS Engineer. I write about Mobile Engineering, Life Experience and Personal Development.