Last active
September 21, 2023 14:54
-
-
Save otmb/deedb649829b2aa188a1032a9bc69522 to your computer and use it in GitHub Desktop.
Create multi-page PDFs async using SwiftUI layouts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
import PDFKit | |
let pdfRendererFormat = [ | |
kCGPDFContextCreator: "@mbotsu", | |
kCGPDFContextAuthor: "@mbotsu", | |
kCGPDFContextTitle: "Create multi-page PDFs with SwiftUI layouts", | |
kCGPDFContextSubject: "Give up ImageRenderer and create PDFs with UIGraphicsPDFRenderer", | |
] as [String : Any] | |
struct CreatePDFView: View { | |
@State var pdfUrl: URL? | |
var body: some View { | |
VStack { | |
if let pdfUrl = pdfUrl { | |
PdfFileView(url: pdfUrl) | |
} | |
} | |
.onAppear { | |
let views: [AnyView] = [ | |
AnyView(Page1()), | |
AnyView(Page2()), | |
AnyView(Page3()), | |
] | |
Task { | |
do { | |
let url = try await createPdf("sample.pdf", | |
options: pdfRendererFormat, | |
views: views) | |
pdfUrl = url | |
print(url.path) | |
} catch { | |
print(error.localizedDescription) | |
} | |
} | |
} | |
} | |
struct Page1: View { | |
var body: some View { | |
Text("Page1").font(.largeTitle) | |
} | |
} | |
struct Page2: View { | |
var body: some View { | |
Text("Page2").font(.largeTitle) | |
} | |
} | |
struct Page3: View { | |
var body: some View { | |
Text("Page3").font(.largeTitle) | |
} | |
} | |
} | |
func createUrl(fileName: String) throws -> URL { | |
let fileManager = FileManager.default | |
let url = fileManager.temporaryDirectory.appendingPathComponent(fileName, conformingTo: .pdf) | |
if fileManager.fileExists(atPath: url.path) { | |
try fileManager.removeItem(at: url) | |
} | |
return url | |
} | |
func createPdf(_ fileName: String, options: [String: Any], | |
width:CGFloat=595.2, height:CGFloat=841.8, | |
views: [AnyView]) throws -> URL { | |
let format = UIGraphicsPDFRendererFormat() | |
format.documentInfo = options | |
let url = try createUrl(fileName: fileName) | |
let pdfRect = CGRect(x: 0, y: 0, width: width, height: height) | |
let pdfRenderer = UIGraphicsPDFRenderer(bounds: pdfRect, format: format) | |
try pdfRenderer.writePDF(to: url , withActions: { context in | |
views.forEach{ view in | |
_createPdf(view, rect: pdfRect, context: context) | |
} | |
}) | |
return url | |
} | |
func _createPdf(_ view: AnyView, rect: CGRect, | |
context: UIGraphicsPDFRendererContext){ | |
let vc = UIHostingController(rootView: view) | |
vc.view.frame = rect | |
vc.loadView() | |
context.beginPage() | |
context.cgContext.clear(rect) | |
vc.view.drawHierarchy(in: rect, afterScreenUpdates: true) | |
vc.removeFromParent() | |
vc.view.removeFromSuperview() | |
} | |
struct PdfFileView : UIViewRepresentable { | |
let url: URL | |
func makeUIView(context: Context) -> some PDFView { | |
let pdfView = PDFView() | |
pdfView.document = PDFDocument(url: url) | |
pdfView.autoScales = true | |
pdfView.displayMode = .twoUpContinuous | |
return pdfView | |
} | |
func updateUIView(_ uiView: UIViewType, context: Context) {} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment