Last active
September 12, 2023 17:40
-
-
Save otmb/f48b31ceec3f648eac8c708c3c28a818 to your computer and use it in GitHub Desktop.
Create multi-page PDFs with 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 { | |
do { | |
let views: [AnyView] = [ | |
AnyView(Page1()), | |
AnyView(Page2()), | |
AnyView(Page3()), | |
] | |
let url = try 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) | |
guard let rootVC = (UIApplication.shared.connectedScenes.first as? UIWindowScene)? | |
.windows.first?.rootViewController else { | |
throw NSError(domain: "rootViewController NotFound", code: -1) | |
} | |
try pdfRenderer.writePDF(to: url , withActions: { context in | |
views.forEach{ view in | |
_createPdf(view, rect: pdfRect, context: context, rootViewController: rootVC) | |
} | |
}) | |
return url | |
} | |
func _createPdf(_ view: AnyView, rect: CGRect, | |
context: UIGraphicsPDFRendererContext, rootViewController: UIViewController){ | |
let vc = UIHostingController(rootView: view) | |
vc.view.frame = rect | |
rootViewController.addChild(vc) | |
rootViewController.view.insertSubview(vc.view, at: 0) | |
context.beginPage() | |
context.cgContext.clear(rect) | |
rootViewController.view.layer.render(in: context.cgContext) | |
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