Creating a smooth and natural signature experience is a small feature that can add huge perceived value to an app—especially in business, productivity, and document-related products.
While building my invocing app Reckord, I recently added a Signature feature using Apple PencilKit. This post explains why digital signatures matter, introduces Apple PencilKit, and walks through how to build a custom signature canvas in SwiftUI step by step.
Digital signatures are widely used to confirm authenticity, approval, or agreement. Many apps benefit from a built-in signature canvas, for example:
From a user’s perspective, signing directly on the screen feels fast, modern, and professional—especially on iPad with Apple Pencil support.
Apple PencilKit is Apple’s native framework for handling handwriting, drawing, and annotations on iOS and iPadOS.
Although it’s often associated with sketching or note-taking apps, PencilKit is an excellent choice for building signature capture features.
Key benefits of using PencilKit:
At the core of is PKCanvasView, a UIKit view that provides a ready-to-use drawing surface for capturing handwritten input.
PencilKit offers a small but powerful set of APIs:
PKCanvasView– A drawing canvas for user inputPKDrawing – A data model representing the drawingPKToolPicker – Pencil, pen, eraser, and tool selectionThese components make it easy to build features like:
PencilKit is built on UIKit, while many modern apps—including mine—are written in SwiftUI.
To use PencilKit inside SwiftUI, we rely on:
UIViewRepresentable
UIViewRepresentable allows us to wrap a UIKit view and embed it seamlessly inside a SwiftUI layout.
Using this approach, we can:
PKCanvasViewThe first step is to create a custom SwiftUI view that wraps PKCanvasView using UIViewRepresentable.
This wrapper is responsible for:
Typically, this includes:
makeUIView() to initialize the canvasupdateUIView() to keep state in sync@Binding to store the PKDrawingimport SwiftUI
import PencilKit
struct SignatureCanvasView: UIViewRepresentable {
@SwiftUI.Environment(\.theme) var theme
@Environment(\.colorScheme) var colorScheme
@Binding var exportedImage: UIImage?
@Binding var exportRequested: Bool
@Binding var clearRequested: Bool
@Binding var isCanvasEmpty: Bool
func makeUIView(context: Context) -> PKCanvasView {
let canvas = PKCanvasView(frame: .zero)
canvas.overrideUserInterfaceStyle = .light
canvas.drawingPolicy = .anyInput
canvas.alwaysBounceVertical = false
canvas.backgroundColor = .white
canvas.isOpaque = true
canvas.drawingPolicy = .anyInput
canvas.tool = PKInkingTool(.pen, color: .black)
context.coordinator.canvasView = canvas
context.coordinator.canvasView?.delegate = context.coordinator
return canvas
}
func updateUIView(_ uiView: PKCanvasView, context: Context) {
uiView.overrideUserInterfaceStyle = .light
uiView.tool = PKInkingTool(.pen, color: .black)
// handle export request
if exportRequested {
// Export the whole canvas bounds (transparent background preserved)
let lightTraits = UITraitCollection(userInterfaceStyle: .light)
let scale = UIScreen.main.scale
lightTraits.performAsCurrent {
let bounds = uiView.drawing.bounds.insetBy(dx: -10, dy: -10) // small padding
let drawingImage = uiView.drawing.image(from: bounds, scale: scale)
DispatchQueue.main.async {
exportedImage = drawingImage
// reset flag
self.exportRequested = false
}
}
}
// handle clear request
if clearRequested {
uiView.drawing = PKDrawing() // reset drawing
DispatchQueue.main.async {
self.clearRequested = false
}
}
}
func makeCoordinator() -> Coordinator {
Coordinator(isCanvasEmpty: $isCanvasEmpty)
}
class Coordinator: NSObject, PKCanvasViewDelegate {
weak var canvasView: PKCanvasView?
@Binding var isCanvasEmpty: Bool
init(canvasView: PKCanvasView? = nil, isCanvasEmpty: Binding<Bool>) {
self.canvasView = canvasView
self._isCanvasEmpty = isCanvasEmpty
}
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
isCanvasEmpty = canvasView.drawing.strokes.isEmpty
}
}
}Inside the UIKit view setup, you configure how the signature behaves.
Common configuration includes:
let canvas = PKCanvasView(frame: .zero)
canvas.overrideUserInterfaceStyle = .light // light, dark
canvas.drawingPolicy = .anyInput // default, anyInput, pencilOnly
canvas.alwaysBounceVertical = false
canvas.backgroundColor = .white
canvas.isOpaque = true
canvas.tool = PKInkingTool(.pen, color: .black)These settings ensure the signature feels natural and distraction-free.
Once the wrapper is ready, you can use it like any other SwiftUI view.
At this stage, you can:
VStack or FormThis makes the signature feature feel fully native within your SwiftUI app.
VStack {
Spacer()
ZStack {
// Visual border to show area
RoundedRectangle(cornerRadius: theme.spacing.sm)
.stroke(style: StrokeStyle(lineWidth: 1, dash: [6])) // dashed border
.foregroundColor(theme.colors.tint)
// The PencilKit canvas
SignatureCanvasView(
exportedImage: $exportedImage,
exportRequested: $exportRequested,
clearRequested: $clearRequested,
isCanvasEmpty: $isCanvasEmpty
)
.clipShape(RoundedRectangle(cornerRadius: theme.spacing.sm))
.padding(theme.spacing.xs)
.onChange(of: store.exportedImage) { oldValue, newValue in
if let image = newValue, let imageData = image.pngData() {
//USE imageData TO DISPLAY IMAGE ON VIEW
}
}
}
.frame(height: 220)
Spacer()
}Output:
After the user finishes signing, the captured PKDrawing can be:
UIImageThis step turns a visual signature into something usable in real-world workflows.
.onChange(of: store.exportedImage) { oldValue, newValue in
if let image = newValue, let imageData = image.pngData() {
//USE imageData TO DISPLAY IMAGE ON VIEW
}
}By combining PencilKit with SwiftUI, you get:
This is the exact approach I used to implement the signature feature in Reckord, and it works great. Here is output:
Signature capture might seem like a small feature, but when implemented well, it adds significant trust and professionalism to an app.
With SwiftUI + PencilKit, you can deliver a high-quality signature experience using native tools with minimal complexity.
Thank you for reading! 😀 Stay safe and take care!
If you have any suggestions or a better approach, feel free to connect with me on X and send me a DM.