From 7b1e86fe979a6b0943adc2c86b7038e0dc7aa2f1 Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Wed, 23 Aug 2023 16:39:42 +0200 Subject: [PATCH] Add image export option --- ResumeBuilder/ContentView.swift | 143 ++++++++++++++++++++------------ 1 file changed, 91 insertions(+), 52 deletions(-) diff --git a/ResumeBuilder/ContentView.swift b/ResumeBuilder/ContentView.swift index 380064a..a4bd751 100644 --- a/ResumeBuilder/ContentView.swift +++ b/ResumeBuilder/ContentView.swift @@ -26,6 +26,9 @@ struct ContentView: View { @State var selectedLanguageIndex = 0 + @State + private var showExportFormatPicker = false + var colorStyle: ColorScheme { darkModeEnabled ? .dark : .light } @@ -43,13 +46,21 @@ struct ContentView: View { .tag(content.offset) } }.frame(maxWidth: 100) - Button(action: createAndSavePDF) { - Label("Save PDF", systemSymbol: .squareAndArrowDown) - .padding(3) - } - Button(action: exportData) { - Label("Export", systemSymbol: .squareAndArrowUp) + Button(action: { + showExportFormatPicker = true + }) { + Label("Export", systemSymbol: .squareAndArrowDown) .padding(3) + }.confirmationDialog( + "Which format would you like?", + isPresented: $showExportFormatPicker + ) { + Button("JSON data", action: exportJsonData) + Button("PDF", action: exportPdf) + Button("Image", action: exportImage) + Button("Cancel", role: .cancel, action: {}) + } message: { + Text(" Choose the export format") } Button(action: importData) { Label("Import", systemSymbol: .docBadgePlus) @@ -76,27 +87,6 @@ struct ContentView: View { } } - private func exportData() { - let encoder = JSONEncoder() - encoder.outputFormatting = .prettyPrinted - let data: Data - do { - data = try encoder.encode(info) - } catch { - print("Failed to encode data: \(error)") - return - } - guard let url = showDataSavePanel() else { - print("No url to save data") - return - } - do { - try data.write(to: url) - } catch { - print("Failed to write data: \(error)") - } - } - private func importData() { guard let url = showOpenFilePanel() else { print("No url to import") @@ -125,19 +115,34 @@ struct ContentView: View { selectedLanguageIndex = index } - private func createAndSavePDF() { + private func exportPdf() { DispatchQueue.main.async { - guard let pdfURL = renderPDF() else { - return - } guard let url = showPdfSavePanel() else { print("No url to save PDF") return } - writePDF(at: pdfURL, to: url) + savePDF(to: url) } } + private func exportImage() { + DispatchQueue.main.async { + guard let url = showJpgSavePanel() else { + print("No image url") + return + } + self.saveImage(to: url) + } + } + + private func exportJsonData() { + guard let url = showJsonSavePanel() else { + print("No url to save data") + return + } + saveJson(to: url) + } + private func showOpenFilePanel() -> URL? { let panel = NSOpenPanel() panel.allowedContentTypes = [.json] @@ -155,23 +160,31 @@ struct ContentView: View { return panel.url } - private func showDataSavePanel() -> URL? { - showPanel( + private func showJsonSavePanel() -> URL? { + showSavePanel( type: .json, title: "Save JSON", fileName: "CV.json", message: "Choose a location to save a JSON file of the resume data") } + private func showJpgSavePanel() -> URL? { + showSavePanel( + type: .jpeg, + title: "Save image", + fileName: "CV.jpg", + message: "Choose a location to save an image of the resume") + } + private func showPdfSavePanel() -> URL? { - showPanel( + showSavePanel( type: .pdf, title: "Save PDF", fileName: "CV.pdf", message: "Choose a location to save a PDF of the resume") } - private func showPanel(type: UTType, title: String, fileName: String, message: String) -> URL? { + private func showSavePanel(type: UTType, title: String, fileName: String, message: String) -> URL? { let savePanel = NSSavePanel() savePanel.allowedContentTypes = [type] savePanel.canCreateDirectories = true @@ -189,32 +202,42 @@ struct ContentView: View { return savePanel.url } - private func writePDF(at source: URL, to destination: URL) { - do { - if FileManager.default.fileExists(atPath: destination.path) { - try FileManager.default.removeItem(at: destination) - } - try FileManager.default.copyItem(at: source, to: destination) - } catch { - print("Failed to save pdf: \(error)") - } - } - private var renderContent: some View { CV(info: info, style: style) .frame(width: style.pageWidth, height: style.pageHeight) + .preferredColorScheme(.dark) } @MainActor - private func renderPDF() -> URL? { - let pdfURL = URL.documentsDirectory.appending(path: "cv.pdf") + private func saveImage(to url: URL) { + let renderer = ImageRenderer(content: renderContent) + renderer.scale = 3 + guard let image = renderer.nsImage else { + print("No image from renderer") + return + } + + let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil)! + let bitmapRep = NSBitmapImageRep(cgImage: cgImage) + let data = bitmapRep.representation(using: .jpeg, properties: [:])! + + do { + try data.write(to: url) + print("Data saved") + } catch { + print("Failed to save image: \(error)") + } + } + + @MainActor + private func savePDF(to url: URL) { let renderer = ImageRenderer(content: renderContent) var didFinish = false renderer.render { size, context in var box = CGRect(x: 0, y: 0, width: size.width, height: size.height) - guard let pdf = CGContext(pdfURL as CFURL, mediaBox: &box, nil) else { + guard let pdf = CGContext(url as CFURL, mediaBox: &box, nil) else { print("Failed to create CGContext") return } @@ -230,10 +253,26 @@ struct ContentView: View { didFinish = true } guard didFinish else { - return nil + return } print("PDF created") - return pdfURL + } + + private func saveJson(to url: URL) { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + let data: Data + do { + data = try encoder.encode(info) + } catch { + print("Failed to encode data: \(error)") + return + } + do { + try data.write(to: url) + } catch { + print("Failed to write data: \(error)") + } } }