PencilKit enables your app to support Apple Pencil. Learn to create drawing surfaces and handwriting recognition.
PencilKit Setup
Add PencilKit
- 1Add PencilKit framework
- 2Import PencilKit in your files
- 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
- 1Performance: Optimize drawing performance
- 2Memory: Manage memory for large drawings
- 3Tools: Provide appropriate drawing tools
- 4Pressure: Use pressure sensitivity
- 5Colors: Support wide color range
- 6Saving: Save drawings efficiently
- 7Loading: Load drawings asynchronously
- 8Undo/Redo: Implement undo/redo properly
PencilKit enables natural drawing and handwriting experiences!