Last active
September 10, 2023 08:58
-
-
Save otmb/81cfeadfe8219d108602041a3c5bd822 to your computer and use it in GitHub Desktop.
Create and combine PDFs of 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 pdfDocumentOptions = [ | |
kCGPDFContextCreator: "@mbotsu", | |
kCGPDFContextAuthor: "@mbotsu", | |
kCGPDFContextTitle: "Create and combine PDFs with SwiftUI layouts", | |
kCGPDFContextSubject: "Give up ImageRenderer and create PDFs with UIGraphicsPDFRenderer", | |
] as [PDFDocumentWriteOption : Any] | |
struct PDFCombineView: View { | |
@State var pdfDocument: PDFDocument? | |
var body: some View { | |
VStack { | |
if let pdfDocument = pdfDocument { | |
PdfView(pdfDocument: pdfDocument) | |
} | |
} | |
.onAppear { | |
do { | |
let pdfData1 = Page1().toPdfData() | |
let pdfData2 = Page2().toPdfData() | |
let pdfData3 = Page3().toPdfData() | |
if let document = combinePdf(pdfData1, pdfData2, pdfData3){ | |
pdfDocument = document | |
let url = try write("sample.pdf", pdfDocument: document, options: pdfDocumentOptions) | |
print(url.path) | |
} else { | |
throw NSError(domain: "combine failed", code: -1) | |
} | |
} catch { | |
print(error.localizedDescription) | |
} | |
} | |
} | |
func write(_ fileName: String, pdfDocument: PDFDocument, options: [PDFDocumentWriteOption : Any]) throws -> URL { | |
let url = try createUrl(fileName: fileName) | |
pdfDocument.write(to: url, withOptions: options) | |
return url | |
} | |
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 | |
} | |
} | |
struct Page1: View { | |
var body: some View { | |
Text("Page1") | |
} | |
} | |
struct Page2: View { | |
var body: some View { | |
Text("Page2") | |
} | |
} | |
struct Page3: View { | |
var body: some View { | |
Text("Page3") | |
} | |
} | |
struct PdfView : UIViewRepresentable { | |
let pdfDocument: PDFDocument | |
func makeUIView(context: Context) -> some PDFView { | |
let pdfView = PDFView() | |
pdfView.document = pdfDocument | |
return pdfView | |
} | |
func updateUIView(_ uiView: UIViewType, context: Context) {} | |
} | |
func combinePdf(_ pdfData: Data,_ pdfDataList: Data...) -> PDFDocument? { | |
if let basePdfDocument = PDFDocument(data: pdfData){ | |
pdfDataList.forEach { data in | |
let pageNo = basePdfDocument.pageCount | |
if let pdfPage = PDFDocument(data: data)?.page(at: 0){ | |
basePdfDocument.insert(pdfPage, at: pageNo) | |
} | |
} | |
return basePdfDocument | |
} | |
return nil | |
} | |
extension View { | |
func toPdfData(width:CGFloat=595.2, height:CGFloat=841.8) -> Data { | |
let pdfVC = UIHostingController(rootView: self) | |
pdfVC.view.frame = CGRect(x: 0, y: 0, width: width, height: height) | |
let scenes = UIApplication.shared.connectedScenes | |
let windowScene = scenes.first as? UIWindowScene | |
let window = windowScene?.windows.first | |
let rootVC = window?.rootViewController | |
rootVC?.addChild(pdfVC) | |
rootVC?.view.insertSubview(pdfVC.view, at: 0) | |
let pdfRenderer = UIGraphicsPDFRenderer(bounds: CGRect(x: 0, y: 0, width: width, height: height)) | |
let pdf = pdfRenderer.pdfData(actions: { context in | |
context.beginPage() | |
rootVC?.view.layer.render(in: context.cgContext) | |
}) | |
pdfVC.removeFromParent() | |
pdfVC.view.removeFromSuperview() | |
return pdf | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment