DEV Community

Custodia-Admin
Custodia-Admin

Posted on • Originally published at pagebolt.dev

How to take screenshots and generate PDFs in Swift

How to Take Screenshots and Generate PDFs in Swift

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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)")
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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)")
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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))
    }
}
Enter fullscreen mode Exit fullscreen mode

No WKWebView, no UIGraphicsPDFRenderer, no Chromium. URLSession.shared ships with every Apple platform — no additional dependencies.


Try it free — 100 requests/month, no credit card. → Get started in 2 minutes

Top comments (0)