CHResume/ResumeBuilder/ContentView.swift

151 lines
4.3 KiB
Swift
Raw Normal View History

2023-08-17 10:12:26 +02:00
import SwiftUI
2023-08-18 22:47:24 +02:00
import SFSafeSymbols
2023-08-17 10:12:26 +02:00
struct ContentView: View {
2023-08-18 22:47:24 +02:00
2023-08-18 22:56:11 +02:00
@Environment(\.colorScheme)
var defaultColorScheme: ColorScheme
2023-08-20 13:11:13 +02:00
let content: [CVInfo]
2023-08-18 22:47:24 +02:00
let style: CVStyle
2023-08-18 22:56:11 +02:00
2023-08-20 13:11:13 +02:00
init(content: [CVInfo], style: CVStyle) {
self.content = content
2023-08-18 22:56:11 +02:00
self.style = style
}
@State
var darkModeEnabled = true
@State
var didReadDarkMode = false
2023-08-20 13:11:13 +02:00
@State
var selectedLanguageIndex = 0
2023-08-18 22:56:11 +02:00
var colorStyle: ColorScheme {
darkModeEnabled ? .dark : .light
}
2023-08-20 13:11:13 +02:00
var info: CVInfo {
content[selectedLanguageIndex]
}
2023-08-18 22:47:24 +02:00
2023-08-17 10:12:26 +02:00
var body: some View {
2023-08-18 22:47:24 +02:00
VStack(alignment: .leading) {
HStack {
2023-08-20 13:11:13 +02:00
Picker("", selection: $selectedLanguageIndex) {
ForEach(Array(content.enumerated()), id: \.element) { content in
Text(content.element.language)
.tag(content.offset)
}
}.frame(width: 100)
2023-08-18 22:47:24 +02:00
Button(action: createAndSavePDF) {
2023-08-20 13:11:13 +02:00
Label("Export PDF", systemSymbol: .squareAndArrowDown)
2023-08-18 22:47:24 +02:00
}
.padding()
2023-08-20 13:11:13 +02:00
Spacer()
2023-08-18 22:56:11 +02:00
Toggle("Dark mode", isOn: $darkModeEnabled)
2023-08-20 13:11:13 +02:00
.toggleStyle(SwitchToggleStyle())
2023-08-18 22:47:24 +02:00
}
2023-08-20 13:11:13 +02:00
.padding(.horizontal)
2023-08-18 22:47:24 +02:00
ScrollView(.vertical) {
CV(info: info, style: style)
}.frame(width: style.pageWidth)
}
2023-08-18 22:56:11 +02:00
.preferredColorScheme(colorStyle)
.onAppear {
guard !didReadDarkMode else {
return
}
darkModeEnabled = defaultColorScheme == .dark
didReadDarkMode = true
}
2023-08-18 22:47:24 +02:00
}
private func createAndSavePDF() {
DispatchQueue.main.async {
guard let pdfURL = renderPDF() else {
return
}
guard let url = showSavePanel() else {
return
}
writePDF(at: pdfURL, to: url)
}
}
private func showSavePanel() -> URL? {
let savePanel = NSSavePanel()
savePanel.allowedContentTypes = [.pdf]
savePanel.canCreateDirectories = true
savePanel.isExtensionHidden = false
savePanel.allowsOtherFileTypes = false
savePanel.title = "Save PDF"
savePanel.message = "Choose a location to save a PDF of the resume"
savePanel.nameFieldLabel = "File name:"
savePanel.nameFieldStringValue = "CV.pdf"
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)")
}
}
2023-08-20 13:11:13 +02:00
private var renderContent: some View {
2023-08-18 22:47:24 +02:00
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")
2023-08-20 13:11:13 +02:00
let renderer = ImageRenderer(content: renderContent)
2023-08-18 22:47:24 +02:00
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
2023-08-17 10:12:26 +02:00
}
2023-08-18 22:47:24 +02:00
print("PDF created")
return pdfURL
2023-08-17 10:12:26 +02:00
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
2023-08-20 13:11:13 +02:00
ContentView(content: [cvInfoEnglish, cvInfoGerman], style: cvStyle)
2023-08-18 22:47:24 +02:00
.frame(width: 600, height: 600 * sqrt(2))
2023-08-17 10:12:26 +02:00
}
}