Skip to content

Commit cd74557

Browse files
authored
Create o3_iOS_PencilKitDrawing.swift
1 parent 6a559d0 commit cd74557

File tree

1 file changed

+206
-0
lines changed

1 file changed

+206
-0
lines changed
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
//
2+
// o3PencilKitDrawing.swift
3+
// CoreSwiftUI
4+
//
5+
// Created by Amos Gyamfi on 19.4.2025.
6+
//
7+
8+
import SwiftUI
9+
import PencilKit
10+
import UIKit
11+
12+
struct o3PencilKitDrawing: View {
13+
@State private var canvasView = PKCanvasView()
14+
@State private var toolPicker = PKToolPicker()
15+
@State private var importedImage: UIImage? = nil
16+
@State private var showingImagePicker = false
17+
@State private var pdfURL: URL? = nil
18+
19+
var body: some View {
20+
NavigationStack {
21+
ZStack {
22+
if let importedImage = importedImage {
23+
Image(uiImage: importedImage)
24+
.resizable()
25+
.scaledToFit()
26+
.clipped()
27+
}
28+
29+
CanvasRepresentable(canvasView: $canvasView, toolPicker: $toolPicker)
30+
.onAppear {
31+
toolPicker.setVisible(true, forFirstResponder: canvasView)
32+
toolPicker.addObserver(canvasView)
33+
canvasView.becomeFirstResponder()
34+
}
35+
}
36+
.navigationTitle("o3Draw")
37+
.navigationBarTitleDisplayMode(.inline)
38+
.sheet(isPresented: $showingImagePicker) {
39+
ImagePicker(image: $importedImage)
40+
}
41+
.sheet(item: $pdfURL) { url in
42+
ActivityView(activityItems: [url])
43+
}
44+
.toolbar {
45+
ToolbarItem(placement: .navigationBarLeading) {
46+
Button {
47+
exportDrawingAsPDF()
48+
} label: {
49+
Image(systemName: "square.and.arrow.up")
50+
}
51+
}
52+
ToolbarItemGroup(placement: .navigationBarTrailing) {
53+
Button {
54+
showingImagePicker = true
55+
} label: {
56+
Image(systemName: "photo.badge.plus")
57+
}
58+
Menu {
59+
Button {
60+
// Save the current drawing to Photos
61+
saveToPhotos()
62+
} label: {
63+
Label("Save", systemImage: "square.and.arrow.down")
64+
}
65+
66+
Button {
67+
// Undo the last stroke
68+
canvasView.undoManager?.undo()
69+
} label: {
70+
Label("Undo", systemImage: "arrow.uturn.backward")
71+
}
72+
73+
Button {
74+
// Redo the last undone stroke
75+
canvasView.undoManager?.redo()
76+
} label: {
77+
Label("Redo", systemImage: "arrow.uturn.forward")
78+
}
79+
80+
Button {
81+
// Clear the current drawing
82+
canvasView.drawing = PKDrawing()
83+
} label: {
84+
Label("Clear", systemImage: "trash")
85+
}
86+
} label: {
87+
Image(systemName: "ellipsis.circle.fill")
88+
}
89+
}
90+
}
91+
}
92+
}
93+
94+
// Save the current drawing to the Photos album
95+
private func saveToPhotos() {
96+
let image = canvasView.drawing.image(from: canvasView.bounds, scale: UIScreen.main.scale)
97+
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
98+
}
99+
100+
// Export the current composition (background image + drawing) as a single‑page PDF
101+
private func exportDrawingAsPDF() {
102+
let bounds = canvasView.bounds
103+
let renderer = UIGraphicsPDFRenderer(bounds: bounds)
104+
105+
let data = renderer.pdfData { ctx in
106+
ctx.beginPage()
107+
108+
// Draw imported background image first, if any
109+
if let importedImage = importedImage {
110+
importedImage.draw(in: bounds)
111+
}
112+
113+
// Draw the PencilKit strokes on top
114+
let drawingImage = canvasView.drawing.image(from: bounds, scale: UIScreen.main.scale)
115+
drawingImage.draw(in: bounds)
116+
}
117+
118+
// Write to a temporary file so we can share a URL
119+
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("o3Drawing.pdf")
120+
do {
121+
try data.write(to: tempURL)
122+
DispatchQueue.main.async {
123+
pdfURL = tempURL
124+
}
125+
} catch {
126+
print("Failed to write PDF: \(error)")
127+
}
128+
}
129+
}
130+
131+
struct CanvasRepresentable: UIViewRepresentable {
132+
@Binding var canvasView: PKCanvasView
133+
@Binding var toolPicker: PKToolPicker
134+
135+
func makeUIView(context: Context) -> PKCanvasView {
136+
canvasView.drawingPolicy = .anyInput
137+
canvasView.alwaysBounceVertical = false
138+
canvasView.backgroundColor = .clear
139+
canvasView.isOpaque = false
140+
return canvasView
141+
}
142+
143+
func updateUIView(_ uiView: PKCanvasView, context: Context) {
144+
// No dynamic updates needed for the static canvas
145+
}
146+
}
147+
148+
struct ImagePicker: UIViewControllerRepresentable {
149+
@Environment(\.presentationMode) private var presentationMode
150+
@Binding var image: UIImage?
151+
152+
func makeCoordinator() -> Coordinator {
153+
Coordinator(self)
154+
}
155+
156+
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
157+
let parent: ImagePicker
158+
159+
init(_ parent: ImagePicker) {
160+
self.parent = parent
161+
}
162+
163+
func imagePickerController(_ picker: UIImagePickerController,
164+
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
165+
if let edited = info[.editedImage] as? UIImage {
166+
parent.image = edited
167+
} else if let original = info[.originalImage] as? UIImage {
168+
parent.image = original
169+
}
170+
parent.presentationMode.wrappedValue.dismiss()
171+
}
172+
173+
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
174+
parent.presentationMode.wrappedValue.dismiss()
175+
}
176+
}
177+
178+
func makeUIViewController(context: Context) -> UIImagePickerController {
179+
let picker = UIImagePickerController()
180+
picker.sourceType = .photoLibrary
181+
picker.allowsEditing = true // enables built‑in cropping UI
182+
picker.delegate = context.coordinator
183+
return picker
184+
}
185+
186+
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) { }
187+
}
188+
189+
struct ActivityView: UIViewControllerRepresentable {
190+
let activityItems: [Any]
191+
let applicationActivities: [UIActivity]? = nil
192+
193+
func makeUIViewController(context: Context) -> UIActivityViewController {
194+
UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
195+
}
196+
197+
func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) { }
198+
}
199+
200+
extension URL: Identifiable {
201+
public var id: String { absoluteString }
202+
}
203+
204+
#Preview {
205+
o3PencilKitDrawing()
206+
}

0 commit comments

Comments
 (0)