Skip to content
All essays
iOSMarch 27, 202511 min

Swift PencilKit: Drawing and Handwriting

Implement Apple Pencil support with PencilKit. Drawing, handwriting, and annotation.

Ü
Ümit Uz
Mobile & Full Stack Developer

PencilKit enables your app to support Apple Pencil. Learn to create drawing surfaces and handwriting recognition.

PencilKit Setup

Add PencilKit

  1. 1Add PencilKit framework
  2. 2Import PencilKit in your files
  3. 3Configure drawing canvas

Basic Drawing Canvas

swift
import PencilKit
import SwiftUI

struct DrawingView: UIViewRepresentable {
    @Binding var drawing: PKDrawing

    func makeUIView(context: Context) -> PKCanvasView {
        let canvasView = PKCanvasView()
        canvasView.drawing = drawing
        canvasView.delegate = context.coordinator
        canvasView.backgroundColor = .systemBackground

        let tool = PKInkingTool(
            ink: .black,
            width: 15
        )
        canvasView.tool = tool

        return canvasView
    }

    func updateUIView(_ uiView: PKCanvasView, context: Context) {
        uiView.drawing = drawing
    }

    func makeCoordinator() -> Coordinator {
        Coordinator()
    }

    class Coordinator: NSObject, PKCanvasViewDelegate {
        func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
            // Drawing changed
        }
    }
}

struct DrawingContentView: View {
    @State private var drawing = PKDrawing()
    @State private var showDrawing = true

    var body: some View {
        VStack {
            if showDrawing {
                DrawingView(drawing: $drawing)
                    .frame(height: 400)
                    .cornerRadius(10)
                    .shadow(radius: 5)
            }

            HStack(spacing: 20) {
                Button("Clear") {
                    drawing = PKDrawing()
                }
                .padding()
                .background(Color.red)
                .foregroundColor(.white)
                .cornerRadius(10)

                Button("Save") {
                    saveDrawing()
                }
                .padding()
                .background(Color.blue)
                .foregroundColor(.white)
                .cornerRadius(10)
            }
        }
        .padding()
    }

    func saveDrawing() {
        // Save drawing logic
        print("Drawing saved")
    }
}

Drawing Tools

Tool Selection

swift
class DrawingToolManager: ObservableObject {
    @Published var selectedTool: PKTool = PKInkingTool(ink: .black, width: 15)

    func setPencil() {
        selectedTool = PencilTool()
    }

    func setPen() {
        selectedTool = PKInkingTool(ink: .black, width: 5)
    }

    func setMarker() {
        selectedTool = PKMarkerTool()
    }

    func setEraser() {
        selectedTool = PKEraserTool()
    }

    func setPencil(to style: PKPencilStyle) {
        let tool = PencilTool(style: style)
        selectedTool = tool
    }

    func setPen(width: CGFloat, color: UIColor) {
        selectedTool = PKInkingTool(ink: .color(color), width: width)
    }

    func setMarker(color: UIColor) {
        selectedTool = PKMarkerTool(color: color)
    }

    func setEraser(width: CGFloat) {
        selectedTool = PKEraserTool(width: width)
    }
}

Color Picker

Custom Colors

swift
struct ColorPickerView: View {
    @Binding var selectedColor: UIColor
    @State private var showColorPicker = false

    let colors: [UIColor] = [
        .black, .red, .blue, .green, .yellow, .orange, .purple, .brown
    ]

    var body: some View {
        VStack {
            HStack(spacing: 10) {
                ForEach(colors.indices, id: \.self) { index in
                    let color = colors[index]
                    Circle()
                        .fill(Color(color))
                        .frame(width: 30, height: 30)
                        .overlay(
                            Circle()
                                .strokeBorder(selectedColor == color ? Color.blue : Color.clear, lineWidth: 3)
                        )
                        .onTapGesture {
                            selectedColor = color
                        }
                }
            }
        }
        .padding()
    }
}

Handwriting Recognition

Data Detector

swift
import PencilKit

class HandwritingRecognizer {
    func recognizeText(in drawing: PKDrawing) -> [String] {
        var recognizedStrings: [String] = []

        let textRecognition = drawing.dataStrokes.flatMap { stroke -> [String] in
            // Simple recognition logic
            // In production, use Core ML or Vision framework
            return ["Sample Text"]
        }

        return textRecognition
    }

    func convertToData(drawing: PKDrawing) -> Data {
        return drawing.dataRepresentation()
    }

    func saveDrawing(_ drawing: PKDrawing) -> URL? {
        let data = convertToData(drawing: drawing)

        let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
        let fileURL = URL(fileURLWithPath: documentsPath).appendingPathComponent("drawing.pkd")

        try? data.write(to: fileURL)

        return fileURL
    }

    func loadDrawing(from url: URL) -> PKDrawing? {
        guard let data = try? Data(contentsOf: url) else {
            return nil
        }

        return try? PKDrawing(data: data)
    }
}

SwiftUI Integration

Complete Drawing App

swift
struct DrawingAppView: View {
    @StateObject private var toolManager = DrawingToolManager()
    @State private var drawing = PKDrawing()
    @State private var selectedColor: UIColor = .black

    var body: some View {
        VStack {
            // Canvas
            DrawingView(drawing: $drawing)
                .frame(height: 400)
                .background(Color.white)
                .cornerRadius(10)
                .overlay(
                    RoundedRectangle(cornerRadius: 10)
                        .stroke(Color.gray.opacity(0.2), lineWidth: 1)
                )
                .gesture(
                    DragGesture(minimumDistance: 0)
                        .onChanged { value in
                            // Handle drawing
                        }
                )

            // Tool selection
            ScrollView(.horizontal, showsIndicators: false) {
                HStack(spacing: 15) {
                    ToolButton(
                        title: "Pencil",
                        isSelected: toolManager.selectedTool is PencilTool
                    ) {
                        toolManager.setPencil()
                    }

                    ToolButton(
                        title: "Pen",
                        isSelected: toolManager.selectedTool is PKInkingTool
                    ) {
                        toolManager.setPen()
                    }

                    ToolButton(
                        title: "Marker",
                        isSelected: toolManager.selectedTool is PKMarkerTool
                    ) {
                        toolManager.setMarker()
                    }

                    ToolButton(
                        title: "Eraser",
                        isSelected: toolManager.selectedTool is PKEraserTool
                    ) {
                        toolManager.setEraser()
                    }

                    ColorPickerView(selectedColor: $selectedColor)
                }
                .padding()
            }

            // Actions
            HStack(spacing: 20) {
                Button("Clear") {
                    drawing = PKDrawing()
                }
                .padding()
                .background(Color.red)
                .foregroundColor(.white)
                .cornerRadius(10)

                Button("Undo") {
                    // Undo last stroke
                }
                .padding()
                .background(Color.orange)
                .foregroundColor(.white)
                .cornerRadius(10)

                Button("Redo") {
                    // Redo last stroke
                }
                .padding()
                .background(Color.blue)
                .foregroundColor(.white)
                .cornerRadius(10)
            }
        }
        .padding()
    }
}

struct ToolButton: View {
    let title: String
    let isSelected: Bool
    let action: () -> Void

    var body: some View {
        Button(action: action) {
            Text(title)
                .padding(.horizontal, 15)
                .padding(.vertical, 8)
                .background(isSelected ? Color.blue : Color.gray.opacity(0.2))
                .foregroundColor(.white)
                .cornerRadius(8)
        }
    }
}

Pressure Sensitivity

Handle Pressure

swift
class PressureSensitiveDrawing: NSObject, PKCanvasViewDelegate {
    var canvasView: PKCanvasView?

    func setupPressureSensitivity() {
        let tool = PKInkingTool(
            ink: .black,
            width: 15
        )

        tool.observesInput = true
        tool.baseWidth = 10
        tool.width = 15
        tool.opacity = 1.0

        canvasView?.tool = tool
    }

    func canvasViewDidBeginUsingTool(_ canvasView: PKCanvasView) {
        setupPressureSensitivity()
    }

    func canvasViewDidEndUsingTool(_ canvasView: PKCanvasView) {
        // Tool ended
    }

    func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
        // Drawing changed
    }
}

Animation

Animate Drawing

swift
extension PKDrawing {
    func animatedStrokes() -> [PKStroke] {
        return dataStrokes
    }

    func clearAnimated(in view: PKCanvasView, duration: TimeInterval) {
        let strokes = animatedStrokes()
        let strokesToRemove = strokes

        for stroke in strokes {
            view.removeStroke(stroke)
        }
    }
}

Best Practices

  1. 1Performance: Optimize drawing performance
  2. 2Memory: Manage memory for large drawings
  3. 3Tools: Provide appropriate drawing tools
  4. 4Pressure: Use pressure sensitivity
  5. 5Colors: Support wide color range
  6. 6Saving: Save drawings efficiently
  7. 7Loading: Load drawings asynchronously
  8. 8Undo/Redo: Implement undo/redo properly

PencilKit enables natural drawing and handwriting experiences!

Related essays

Next essay
Swift Text Kit: Advanced Text Processing