How to Take Screenshots and Generate PDFs in Swift
On iOS, generating a PDF from a URL means loading a WKWebView, waiting for render, then drawing with UIGraphicsPDFRenderer — a multi-step process that doesn't work in background tasks. Here's the simpler path: one URLSession call, binary response. Works in iOS apps, macOS utilities, and Vapor backends.
On iOS, generating a PDF from a URL typically means loading a WKWebView, waiting for it to render, then using UIGraphicsPDFRenderer to draw it — a multi-step process that requires a UI context and doesn't work in background tasks or server-side Swift.
On server-side Swift (Vapor), the options are similarly heavy: spawn a Puppeteer process or shell out to wkhtmltopdf.
Here's the simpler path: one URLSession call, binary response. Works in iOS apps, macOS utilities, and Vapor backends.
Screenshot from a URL (Swift concurrency)
import Foundation
class PageBoltClient {
private let apiKey = ProcessInfo.processInfo.environment["PAGEBOLT_API_KEY"] ?? ""
private let baseURL = URL(string: "https://pagebolt.dev/api/v1")!
func screenshot(url: String) async throws -> Data {
var request = URLRequest(url: baseURL.appendingPathComponent("screenshot"))
request.httpMethod = "POST"
request.setValue(apiKey, forHTTPHeaderField: "x-api-key")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONSerialization.data(withJSONObject: [
"url": url,
"fullPage": true,
"blockBanners": true
])
let (data, _) = try await URLSession.shared.data(for: request)
return data
}
}
PDF from a URL
func pdfFromUrl(url: String) async throws -> Data {
var request = URLRequest(url: baseURL.appendingPathComponent("pdf"))
request.httpMethod = "POST"
request.setValue(apiKey, forHTTPHeaderField: "x-api-key")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONSerialization.data(withJSONObject: [
"url": url,
"blockBanners": true
])
let (data, _) = try await URLSession.shared.data(for: request)
return data
}
PDF from HTML (replacing WKWebView + UIGraphicsPDFRenderer)
func pdfFromHtml(html: String) async throws -> Data {
var request = URLRequest(url: baseURL.appendingPathComponent("pdf"))
request.httpMethod = "POST"
request.setValue(apiKey, forHTTPHeaderField: "x-api-key")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
// Codable handles escaping
struct Body: Encodable { let html: String }
request.httpBody = try JSONEncoder().encode(Body(html: html))
let (data, _) = try await URLSession.shared.data(for: request)
return data
}
iOS app — share PDF via UIActivityViewController
import UIKit
class InvoiceViewController: UIViewController {
private let pagebolt = PageBoltClient()
@IBAction func exportPdfTapped(_ sender: UIButton) {
Task {
do {
let html = renderInvoiceHTML()
let pdfData = try await pagebolt.pdfFromHtml(html: html)
let tempURL = FileManager.default.temporaryDirectory
.appendingPathComponent("invoice.pdf")
try pdfData.write(to: tempURL)
let activityVC = UIActivityViewController(
activityItems: [tempURL],
applicationActivities: nil
)
present(activityVC, animated: true)
} catch {
print("PDF generation failed: \(error)")
}
}
}
}
This works in background tasks and doesn't require a visible WKWebView — you can trigger PDF generation without the user navigating to a web page at all.
SwiftUI — download and preview PDF
import SwiftUI
struct InvoiceView: View {
let invoiceId: Int
@State private var pdfData: Data?
@State private var isLoading = false
private let pagebolt = PageBoltClient()
var body: some View {
VStack {
if isLoading {
ProgressView("Generating PDF...")
} else {
Button("Download PDF") {
Task { await generatePDF() }
}
}
}
}
private func generatePDF() async {
isLoading = true
defer { isLoading = false }
do {
let html = renderInvoiceHTML(id: invoiceId)
pdfData = try await pagebolt.pdfFromHtml(html: html)
// present share sheet or PDFKit viewer
} catch {
print("Error: \(error)")
}
}
}
Vapor (server-side Swift)
import Vapor
func routes(_ app: Application) throws {
let pagebolt = PageBoltClient()
app.get("invoices", ":id", "pdf") { req async throws -> Response in
let id = try req.parameters.require("id", as: Int.self)
// Render your Leaf/HTML template
let html = try await req.view.render("invoice", ["id": id]).get()
let htmlString = String(data: html.data, encoding: .utf8)!
let pdfData = try await pagebolt.pdfFromHtml(html: htmlString)
var headers = HTTPHeaders()
headers.add(name: .contentType, value: "application/pdf")
headers.add(
name: .contentDisposition,
value: "attachment; filename=\"invoice-\(id).pdf\""
)
return Response(status: .ok, headers: headers, body: .init(data: pdfData))
}
}
No WKWebView, no UIGraphicsPDFRenderer, no Chromium. URLSession.shared ships with every Apple platform — no additional dependencies.
Get Started Free
100 requests/month, no credit card
Screenshots, PDFs, and video recordings from your Swift app — no WKWebView setup, no UIGraphicsPDFRenderer, works in iOS apps and Vapor backends.
Get Your Free API Key →