import SwiftUI import SFSafeSymbols import UniformTypeIdentifiers struct ContentView: View { @Environment(\.colorScheme) var defaultColorScheme: ColorScheme @State var content: [CVInfo] let style: CVStyle init(content: [CVInfo], style: CVStyle) { self._content = .init(initialValue: content) self.style = style } @State var darkModeEnabled = true @State var didReadDarkMode = false @State var selectedLanguageIndex = 0 var colorStyle: ColorScheme { darkModeEnabled ? .dark : .light } var info: CVInfo { content[selectedLanguageIndex] } var body: some View { VStack(alignment: .leading) { HStack { Picker("", selection: $selectedLanguageIndex) { ForEach(Array(content.enumerated()), id: \.element) { content in Text(content.element.language) .tag(content.offset) } }.frame(maxWidth: 100) Button(action: createAndSavePDF) { Label("Save PDF", systemSymbol: .squareAndArrowDown) .padding(3) } Button(action: exportData) { Label("Export", systemSymbol: .squareAndArrowUp) .padding(3) } Button(action: importData) { Label("Import", systemSymbol: .docBadgePlus) .padding(8) } Spacer() Toggle("Dark mode", isOn: $darkModeEnabled) .toggleStyle(SwitchToggleStyle()) } .padding([.top, .trailing]) .padding(.leading, 6) .padding(.bottom, 4) ScrollView(.vertical) { CV(info: info, style: style) }.frame(width: style.pageWidth) } .preferredColorScheme(colorStyle) .onAppear { guard !didReadDarkMode else { return } darkModeEnabled = defaultColorScheme == .dark didReadDarkMode = true } } 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") return } let data: Data do { data = try Data(contentsOf: url) } catch { print("Failed to open file: \(error)") return } let newData: CVInfo do { newData = try JSONDecoder().decode(CVInfo.self, from: data) } catch { print("Failed to decode data: \(error)") return } guard let index = content.firstIndex(where: { $0.language == newData.language }) else { content.append(newData) selectedLanguageIndex = content.count - 1 return } content[index] = newData selectedLanguageIndex = index } private func createAndSavePDF() { 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) } } private func showOpenFilePanel() -> URL? { let panel = NSOpenPanel() panel.allowedContentTypes = [.json] panel.canCreateDirectories = true panel.isExtensionHidden = false panel.allowsOtherFileTypes = false panel.title = "Load JSON" panel.message = "Choose a JSON file of resume data to import" panel.nameFieldLabel = "File name:" panel.nameFieldStringValue = "CV.json" let response = panel.runModal() guard response == .OK else { return nil } return panel.url } private func showDataSavePanel() -> URL? { showPanel( type: .json, title: "Save JSON", fileName: "CV.json", message: "Choose a location to save a JSON file of the resume data") } private func showPdfSavePanel() -> URL? { showPanel( 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? { let savePanel = NSSavePanel() savePanel.allowedContentTypes = [type] savePanel.canCreateDirectories = true savePanel.isExtensionHidden = false savePanel.allowsOtherFileTypes = false savePanel.title = title savePanel.message = message savePanel.nameFieldLabel = "File name:" savePanel.nameFieldStringValue = fileName let response = savePanel.runModal() guard response == .OK else { return nil } 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) } @MainActor private func renderPDF() -> URL? { let pdfURL = URL.documentsDirectory.appending(path: "cv.pdf") 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 { print("Failed to create CGContext") return } let options: [CFString: Any] = [ kCGPDFContextMediaBox: CGRect(origin: .zero, size: size) ] pdf.beginPDFPage(options as CFDictionary) context(pdf) pdf.endPDFPage() pdf.closePDF() didFinish = true } guard didFinish else { return nil } print("PDF created") return pdfURL } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView(content: [cvInfoEnglish, cvInfoGerman], style: cvStyle) .frame(width: 600, height: 600 * sqrt(2)) } }