●FUNDING — Rork's $15M seed was led by Left Lane Capital with Peak XV, True Ventures, Goodwater, and a16z Speedrun●GROWTH — Rork keeps growing with 743K monthly visits and an 85% growth rate●MAX — Rork Max generates native Swift apps for iPhone, iPad, Watch, TV, Vision Pro, and iMessage●MAX — It reaches HealthKit, Core ML, and Dynamic Island — territory React Native struggles with●MARKET — Apple pushes agentic coding in Xcode 27, accelerating AI-driven native development●MARKET — Gartner projects 75% of new apps will be low-code or no-code by the end of 2026●FUNDING — Rork's $15M seed was led by Left Lane Capital with Peak XV, True Ventures, Goodwater, and a16z Speedrun●GROWTH — Rork keeps growing with 743K monthly visits and an 85% growth rate●MAX — Rork Max generates native Swift apps for iPhone, iPad, Watch, TV, Vision Pro, and iMessage●MAX — It reaches HealthKit, Core ML, and Dynamic Island — territory React Native struggles with●MARKET — Apple pushes agentic coding in Xcode 27, accelerating AI-driven native development●MARKET — Gartner projects 75% of new apps will be low-code or no-code by the end of 2026
Adding PDF Viewing and Export to a Rork Max App — When to Use PDFKit vs. ImageRenderer
Implement PDF viewing, search, and export in Rork Max's native Swift with PDFKit. Covers UIGraphicsPDFRenderer vs. ImageRenderer trade-offs, password protection, and share sheet integration.
Running an app with a notes feature, I started receiving the same request over and over: "Can I send my notes as a PDF?" As an indie developer, I initially assumed piping plain text into the share sheet was enough. But for people using the app to keep work records, a properly formatted PDF isn't a nicer version of text — it's a deliverable, and text doesn't substitute for it.
Because Rork Max generates native Swift, you get PDFKit — which ships with the OS — for free. Viewing, search, and export all work without adding a single dependency, and that's the biggest practical difference from a React Native setup. In this article I'll walk through working code for viewing, exporting, password protection, and sharing.
Why Handle PDFs in Rork Max (Native Swift)?
With React Native Rork, your options are third-party libraries or WebView rendering for display, and HTML-bridge approaches like expo-print for generation. They work, but I kept running into limits around dependency maintenance and fine-grained layout control.
Approach
Viewing
Search / Annotation
Generation
Extra Dependency
WebView (RN)
△ basic display only
✕
✕
No
react-native-pdf etc. (RN)
◯
△ depends on library
✕
Yes
expo-print (RN)
✕
✕
△ via HTML
Yes
PDFKit (Rork Max / native)
◯
◯
◯ with UIGraphicsPDFRenderer
No (bundled with OS)
For a solo developer, not adding dependencies is itself a maintenance win. Being able to lean on the OS-standard framework for everything PDF-related is one of the concrete payoffs of choosing native generation.
A practical Rork Max prompt to start from looks like this:
Add a screen where saved notes can be viewed and exported as PDF.Use PDFKit's PDFView for display and UIGraphicsPDFRenderer for export,and make the result shareable via the share sheet.
With the generated code as a base, let's go through each building block and where you'll want to intervene.
Displaying with PDFView — a Minimal SwiftUI Wrapper
The heart of PDFKit is PDFView. It's a UIKit view, so you wrap it in UIViewRepresentable:
The first thing that trips people up is autoScales. Forget it and the PDF renders at its natural size — a tiny speck in the corner of the screen. Generated code sometimes omits it, so if your PDF looks minuscule, check this first.
PDFDocument(url:) lazy-loads pages, so even a several-hundred-page document opens quickly. Reading the whole file into Data and using PDFDocument(data:), on the other hand, spikes memory on large files. For local files, I recommend the URL initializer.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦A minimal SwiftUI wrapper for PDFView, plus the autoScales setup detail that trips people up first
✦Clear criteria for choosing between UIGraphicsPDFRenderer and ImageRenderer (pagination, resolution, implementation cost)
✦Password-protected export and share sheet integration, with the production pitfalls I actually hit
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Adding Search and Thumbnails — Making It Actually Readable
WebView can approximate plain display, but PDFKit earns its keep by giving you search and thumbnails in a few lines:
// Find a keyword and jump to the first matchif let document = pdfView.document { let matches = document.findString("invoice", withOptions: .caseInsensitive) if let first = matches.first { pdfView.go(to: first) pdfView.setCurrentSelection(first, animate: true) }}
A thumbnail strip is just a PDFThumbnailView connected to your PDFView:
let thumbnail = PDFThumbnailView()thumbnail.pdfView = pdfView // selection stays in syncthumbnail.thumbnailSize = CGSize(width: 44, height: 60)thumbnail.layoutMode = .horizontal
For apps that handle long documents, these two features are the difference between "it displays" and "you can read it." On the import side, the security-scoped URL patterns from importing the user's own files from the Files app apply directly here.
For export, the workhorse is UIGraphicsPDFRenderer. You pick a page size and place text and graphics inside a drawing closure — and crucially, you control pagination yourself:
import UIKitfunc exportNotesPDF(title: String, body: String) -> URL { let pageRect = CGRect(x: 0, y: 0, width: 595.2, height: 841.8) // A4 at 72dpi let renderer = UIGraphicsPDFRenderer(bounds: pageRect) let url = FileManager.default.temporaryDirectory .appendingPathComponent("notes-\(Int(Date().timeIntervalSince1970)).pdf") let titleAttrs: [NSAttributedString.Key: Any] = [ .font: UIFont.systemFont(ofSize: 20, weight: .bold) ] let bodyAttrs: [NSAttributedString.Key: Any] = [ .font: UIFont.systemFont(ofSize: 12), .paragraphStyle: NSParagraphStyle.default ] try? renderer.writePDF(to: url) { context in context.beginPage() title.draw(in: CGRect(x: 48, y: 48, width: 499, height: 40), withAttributes: titleAttrs) let bodyText = NSAttributedString(string: body, attributes: bodyAttrs) let framesetter = CTFramesetterCreateWithAttributedString(bodyText) var currentRange = CFRange(location: 0, length: 0) var firstPage = true repeat { if !firstPage { context.beginPage() } let top: CGFloat = firstPage ? 100 : 48 let textRect = CGRect(x: 48, y: top, width: 499, height: pageRect.height - top - 48) let path = CGPath(rect: textRect, transform: nil) let frame = CTFramesetterCreateFrame(framesetter, currentRange, path, nil) // Core Text uses a bottom-left origin — flip before drawing let cg = context.cgContext cg.saveGState() cg.translateBy(x: 0, y: pageRect.height) cg.scaleBy(x: 1, y: -1) CTFrameDraw(frame, cg) cg.restoreGState() let visible = CTFrameGetVisibleStringRange(frame) currentRange.location += visible.length firstPage = false } while currentRange.location < bodyText.length } return url}
Three things matter here. First, pagination works by asking CTFrameGetVisibleStringRange how many characters actually fit, then carrying the remainder to the next page. Second, Core Text's coordinate system has its origin at the bottom-left, so you must flip the context or your text renders upside down. Third, non-Latin text (Japanese, in my case) embeds correctly with the system font — no font-bundling work required.
Generating PDFs (2): ImageRenderer — a SwiftUI View as a Single Page
On iOS 16 and later, SwiftUI's ImageRenderer can turn a view directly into a PDF. The idea is to treat a screen you've already laid out in SwiftUI as the "paper":
import SwiftUI@MainActorfunc exportViewAsPDF<V: View>(_ view: V, size: CGSize) -> URL { let url = FileManager.default.temporaryDirectory .appendingPathComponent("summary.pdf") let renderer = ImageRenderer(content: view.frame(width: size.width, height: size.height)) renderer.proposedSize = .init(size) renderer.render { rendered, draw in var box = CGRect(origin: .zero, size: rendered) guard let context = CGContext(url as CFURL, mediaBox: &box, nil) else { return } context.beginPDFPage(nil) draw(context) context.endPDFPage() context.closePDF() } return url}
But ImageRenderer has a hard limit: it has no concept of pages. Long content becomes one very tall page. My own decision criteria:
Single-screen documents like monthly summaries or receipts → ImageRenderer. It's a few lines of code, and design changes stay entirely in SwiftUI.
Content of unpredictable length, like notes or meeting minutes → UIGraphicsPDFRenderer. Owning pagination is the deciding factor.
Annotating or merging existing PDFs → PDFKit's PDFAnnotation and PDFDocument.insert(_:at:). If you want handwritten annotations, this combines naturally with the approach in building a handwriting note app with PencilKit.
Password Protection and the Share Sheet — Finishing the Export Path
When people export work records, password-protected export comes up regularly. It's a single call — pass encryption options to PDFDocument.write(to:withOptions:):
if let document = PDFDocument(url: exportedURL) { let protectedURL = FileManager.default.temporaryDirectory .appendingPathComponent("protected.pdf") document.write(to: protectedURL, withOptions: [ .userPasswordOption: userPassword, // for opening .ownerPasswordOption: ownerPassword // for permissions (use a different value) ])}
For sharing, hand the URL to SwiftUI's ShareLink and the share sheet opens. Write exports to temporaryDirectory and clean them up after sharing or on next launch so they never pile up in storage. If you'd rather show an in-app preview before sending, the pattern from calling Quick Look safely from your app slots in nicely — people appreciate seeing exactly what they're about to send.
Pitfalls I Hit in Production
In the order I stumbled into them:
ImageRenderer resolution: the default scale is 1.0, which looks blurry in print. When rendering images, set renderer.scale = displayScale. (Text drawn into the PDF context stays vector and is unaffected, but embedded images are not.)
Encryption applies to the copy: write(to:withOptions:) doesn't change the already-open PDFDocument. Always write the protected version to a separate URL, and decide up front what happens to the original.
Diffing on documentURL: replacing document on every updateUIView call resets the reading position. Compare URLs and only swap when the file actually changed.
The filename shows up in the share sheet: whatever you name the file in temporaryDirectory is what recipients see. A mechanical name like notes-1751522400.pdf makes a very different impression than one with a date or title in it.
The week I shipped the export feature, the "why can't I send this as a PDF" reviews stopped — and the app's name started traveling through share sheets to people who had never seen it. It's an unglamorous feature, but a file that leaves your app as a finished deliverable doubles as a small advertisement.
Your Next Step
Start by exporting one record from your own app onto a single A4 page with UIGraphicsPDFRenderer. Pagination and passwords can wait. Once you've seen information from inside your app become a properly formatted document out in the world, your users' reactions will tell you which feature to build next.
Share
Thank You for Reading
Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.