Skip to content

Commit 51cc7bc

Browse files
authored
Create o3_visionOS_PencilKitDrawing.swift
1 parent cd74557 commit 51cc7bc

File tree

1 file changed

+221
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)