โ๏ธ PencilKit
โญ Difficulty: โญโญ
โฑ๏ธ Est. Time: 1-2h
๐ Graphics & Media
์๊ธ์จ์ ๋๋ก์์ ์ํ ๊ฐ๋ ฅํ ์บ๋ฒ์ค
iOS 13+Apple Pencil ์ต์ ํ
โจ PencilKit is?
PencilKit is Apple's drawing framework, perfectly integrated with Apple Pencil to deliver a low-latency handwriting and sketching experience. It provides pen, marker, pencil tools, eraser, and ruler out of the box, with support for handwriting recognition and data persistence.
๐ก Key Features: Low-Latency Drawing ยท Apple Pencil Support ยท Various Tools ยท Handwriting Recognition ยท Undo/Redo ยท Data Save/Share ยท Finger/Touch Support ยท Dark Mode
๐ฏ 1. ๊ธฐ๋ณธ ์บ๋ฒ์ค ์ค์
Create a drawing canvas using PKCanvasView.
DrawingView.swift โ ๊ธฐ๋ณธ ์บ๋ฒ์ค
import SwiftUI import PencilKit struct DrawingView: View { @State private var canvasView = PKCanvasView() @State private var toolPicker = PKToolPicker() var body: some View { CanvasView(canvasView: $canvasView, toolPicker: toolPicker) .onAppear { // ํด ํผ์ปค ํ์ toolPicker.setVisible(true, forFirstResponder: canvasView) toolPicker.addObserver(canvasView) canvasView.becomeFirstResponder() } } } struct CanvasView: UIViewRepresentable { @Binding var canvasView: PKCanvasView let toolPicker: PKToolPicker func makeUIView(context: Context) -> PKCanvasView { canvasView.drawingPolicy = .anyInput // Apple Pencil + ์๊ฐ๋ฝ canvasView.delegate = context.coordinator return canvasView } func updateUIView(_ uiView: PKCanvasView, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator(canvasView: $canvasView) } class Coordinator: NSObject, PKCanvasViewDelegate { @Binding var canvasView: PKCanvasView init(canvasView: Binding<PKCanvasView>) { _canvasView = canvasView } // ๋๋ก์ ๋ณ๊ฒฝ ์ ํธ์ถ func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) { print("๋๋ก์ ๋ณ๊ฒฝ๋จ") } } }
๐๏ธ 2. ๋๊ตฌ ์ค์
Configure various drawing tools like pen, marker, pencil.
DrawingTools.swift โ ๋๊ตฌ ์ค์
import PencilKit class DrawingToolManager { // 1. ํ ๋๊ตฌ func createPenTool(color: UIColor = .black, width: CGFloat = 5) -> PKInkingTool { let ink = PKInkingTool(.pen, color: color, width: width) return ink } // 2. ๋ง์ปค ๋๊ตฌ func createMarkerTool(color: UIColor = .yellow, width: CGFloat = 20) -> PKInkingTool { let ink = PKInkingTool(.marker, color: color, width: width) return ink } // 3. ์ฐํ ๋๊ตฌ func createPencilTool(color: UIColor = .darkGray, width: CGFloat = 3) -> PKInkingTool { let ink = PKInkingTool(.pencil, color: color, width: width) return ink } // 4. ์ง์ฐ๊ฐ ๋๊ตฌ func createEraserTool(eraserType: PKEraserTool.EraserType = .vector) -> PKEraserTool { // .vector = ํ ๋จ์ ์ง์ฐ๊ธฐ // .bitmap = ํฝ์ ๋จ์ ์ง์ฐ๊ธฐ return PKEraserTool(eraserType) } // 5. ๋๊ธ์ ๋๊ตฌ func setupRuler(for canvasView: PKCanvasView) { canvasView.isRulerActive = true } // ๋๊ตฌ ์ ์ฉ func applyTool(_ tool: PKTool, to canvasView: PKCanvasView) { canvasView.tool = tool } }
๐พ 3. ๋๋ก์ ์ ์ฅ ๋ฐ ๋ถ๋ฌ์ค๊ธฐ
๋๋ก์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ ๋ถ๋ฌ์ต.
DrawingStorage.swift โ Save/Load
import PencilKit class DrawingStorageManager { // ๋๋ก์ ์ ์ฅ func saveDrawing(_ drawing: PKDrawing, to url: URL) throws { // PKDrawing์ Data๋ก ๋ณํ let data = drawing.dataRepresentation() // ํ์ผ๋ก ์ ์ฅ try data.write(to: url) } // ๋๋ก์ ๋ถ๋ฌ์ค๊ธฐ func loadDrawing(from url: URL) throws -> PKDrawing { // ํ์ผ์์ Data ์ฝ๊ธฐ let data = try Data(contentsOf: url) // Data๋ฅผ PKDrawing์ผ๋ก ๋ณํ let drawing = try PKDrawing(data: data) return drawing } // ์ด๋ฏธ์ง๋ก ๋ณํ func convertToImage(_ drawing: PKDrawing, size: CGSize) -> UIImage { let image = drawing.image(from: drawing.bounds, scale: UIScreen.main.scale) return image } // UserDefaults์ ์ ์ฅ func saveToUserDefaults(_ drawing: PKDrawing, key: String) { let data = drawing.dataRepresentation() UserDefaults.standard.set(data, forKey: key) } // UserDefaults์์ ๋ถ๋ฌ์ค๊ธฐ func loadFromUserDefaults(key: String) -> PKDrawing? { guard let data = UserDefaults.standard.data(forKey: key) else { return nil } return try? PKDrawing(data: data) } }
โ๏ธ 4. ์๊ธ์จ ์ธ์
์์ผ๋ก ์ด ํ ์คํธ๋ฅผ ์ธ์.
HandwritingRecognition.swift โ Handwriting Recognition
import PencilKit import Vision @Observable class HandwritingRecognizer { var recognizedText: String = "" // ๋๋ก์์์ ํ ์คํธ ์ธ์ func recognizeText(from drawing: PKDrawing) async { // ๋๋ก์์ ์ด๋ฏธ์ง๋ก ๋ณํ let image = drawing.image(from: drawing.bounds, scale: 2.0) guard let cgImage = image.cgImage else { return } // Vision ํ ์คํธ ์ธ์ let request = VNRecognizeTextRequest { [weak self] request, error in guard let observations = request.results as? [VNRecognizedTextObservation] else { return } let recognizedStrings = observations.compactMap { observation in observation.topCandidates(1).first?.string } Task { @MainActor in self?.recognizedText = recognizedStrings.joined(separator: "\n") } } // ์๊ธ์จ ์ธ์ ํ์ฑํ request.recognitionLevel = .accurate request.recognitionLanguages = ["en-US", "ko-KR"] let handler = VNImageRequestHandler(cgImage: cgImage, options: [:]) try? handler.perform([request]) } }
๐ 5. ์๋ช ์บก์ฒ
์ ์ ์๋ช ์ ์บก์ฒํ๋ ๊ธฐ๋ฅ implementation.
SignatureView.swift โ ์๋ช
์บก์ฒ
import SwiftUI import PencilKit struct SignatureView: View { @State private var canvasView = PKCanvasView() @State private var signatureImage: UIImage? @State private var showSignaturePad = false var body: some View { VStack { if let image = signatureImage { Image(uiImage: image) .resizable() .scaledToFit() .frame(height: 150) .border(Color.gray) .padding() } else { Text("์๋ช ์์") .foregroundStyle(.secondary) } Button("์๋ช ํ๊ธฐ") { showSignaturePad = true } .buttonStyle(.borderedProminent) } .sheet(isPresented: $showSignaturePad) { SignaturePadView( canvasView: $canvasView, onSave: { drawing in signatureImage = drawing.image( from: drawing.bounds, scale: UIScreen.main.scale ) showSignaturePad = false } ) } } } struct SignaturePadView: View { @Binding var canvasView: PKCanvasView let onSave: (PKDrawing) -> Void @Environment(\.dismiss) var dismiss var body: some View { NavigationStack { VStack { Text("์๋์ ์๋ช ํด์ฃผ์ธ์") .font(.headline) .padding() CanvasView(canvasView: $canvasView, toolPicker: PKToolPicker()) .frame(height: 300) .border(Color.gray) .padding() } .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("์ทจ์") { dismiss() } } ToolbarItem(placement: .confirmationAction) { Button("์ ์ฅ") { onSave(canvasView.drawing) } } ToolbarItem(placement: .bottomBar) { Button("์ง์ฐ๊ธฐ") { canvasView.drawing = PKDrawing() } } } } } }
๐ฑ Complete Example
DrawingAppView.swift โ App
import SwiftUI import PencilKit struct DrawingAppView: View { @State private var canvasView = PKCanvasView() @State private var toolPicker = PKToolPicker() @State private var showSaveAlert = false let storageManager = DrawingStorageManager() var body: some View { NavigationStack { CanvasView(canvasView: $canvasView, toolPicker: toolPicker) .navigationTitle("๋๋ก์") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItemGroup(placement: .topBarTrailing) { Button { // ์ด๋ฏธ์ง๋ก ์ ์ฅ let image = canvasView.drawing.image( from: canvasView.drawing.bounds, scale: UIScreen.main.scale ) UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil) showSaveAlert = true } label: { Image(systemName: "square.and.arrow.down") } Button { canvasView.drawing = PKDrawing() } label: { Image(systemName: "trash") } } ToolbarItemGroup(placement: .bottomBar) { Button { canvasView.undoManager?.undo() } label: { Image(systemName: "arrow.uturn.backward") } .disabled(canvasView.undoManager?.canUndo == false) Spacer() Button { canvasView.undoManager?.redo() } label: { Image(systemName: "arrow.uturn.forward") } .disabled(canvasView.undoManager?.canRedo == false) } } .alert("์ ์ฅ ์๋ฃ", isPresented: $showSaveAlert) { Button("ํ์ธ", role: .cancel) {} } message: { Text("๋๋ก์์ด ์ฌ์ง์ฒฉ์ ์ ์ฅ๋์์ต๋๋ค") } .onAppear { toolPicker.setVisible(true, forFirstResponder: canvasView) toolPicker.addObserver(canvasView) canvasView.becomeFirstResponder() } } } }
๐ก HIG Guidelines
- Apple Pencil: Apple Pencil ์ฌ์ฉ ์ ์ต์ ํ๋ ๊ฒฝํ ์ ๊ณต
- ๋๊ตฌ ์ ํ: ๋ช ํํ ๋๊ตฌ ์ ํ UI ์ ๊ณต
- ์คํ ์ทจ์: ์ฌ์ด ์คํ ์ทจ์/์ฌ์คํ ๋ฒํผ
- ์ ์ฅ: ์๋ ์ ์ฅ ๋๋ ๋ช ํํ ์ ์ฅ ๋ฒํผ
- ๊ถํ: In Info.plist,
NSPhotoLibraryAddUsageDescription์ถ๊ฐ (์ฌ์ง ์ ์ฅ ์)
๐ฏ Practical Usage
- ๋ ธํธ ์ฑ: ์๊ธ์จ ํ๊ธฐ ๋ฐ ์ค์ผ์น
- ํ์ดํธ๋ณด๋: ํ์ ๋๋ก์
- ๊ทธ๋ฆผ ์ฑ: ๋์งํธ ๋๋ก์
- ์๋ช : ์ ์ ์๋ช ์บก์ฒ
- ์ฃผ์: ๋ฌธ์/์ด๋ฏธ์ง ์ฃผ์
๐ Learn More
โก๏ธ Performance Tips:
drawingPolicy๋ฅผ .pencilOnly to ignore finger touch and recognize only Apple Pencil. Useful for precise drawing.