pencilkit-drawing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePencilKit Drawing
PencilKit 绘图
Capture Apple Pencil and finger input using , manage drawing
tools with , serialize drawings with , and wrap
PencilKit in SwiftUI. Targets Swift 6.2 / iOS 26+.
PKCanvasViewPKToolPickerPKDrawing使用捕捉Apple Pencil和手指输入,借助管理绘图工具,通过实现绘图内容序列化,并在SwiftUI中封装PencilKit。适配Swift 6.2 / iOS 26+。
PKCanvasViewPKToolPickerPKDrawingContents
目录
Setup
设置
PencilKit requires no entitlements or Info.plist entries. Import
and create a .
PencilKitPKCanvasViewswift
import PencilKitPlatform availability: iOS 13+, iPadOS 13+, Mac Catalyst 13.1+, visionOS 1.0+.
PencilKit不需要权限或Info.plist配置项。导入并创建实例。
PencilKitPKCanvasViewswift
import PencilKit平台兼容性: iOS 13+, iPadOS 13+, Mac Catalyst 13.1+, visionOS 1.0+。
PKCanvasView Basics
PKCanvasView 基础
PKCanvasViewUIScrollViewswift
import PencilKit
import UIKit
class DrawingViewController: UIViewController, PKCanvasViewDelegate {
let canvasView = PKCanvasView()
override func viewDidLoad() {
super.viewDidLoad()
canvasView.delegate = self
canvasView.drawingPolicy = .anyInput
canvasView.tool = PKInkingTool(.pen, color: .black, width: 5)
canvasView.frame = view.bounds
canvasView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(canvasView)
}
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
// Drawing changed -- save or process
}
}PKCanvasViewUIScrollViewswift
import PencilKit
import UIKit
class DrawingViewController: UIViewController, PKCanvasViewDelegate {
let canvasView = PKCanvasView()
override func viewDidLoad() {
super.viewDidLoad()
canvasView.delegate = self
canvasView.drawingPolicy = .anyInput
canvasView.tool = PKInkingTool(.pen, color: .black, width: 5)
canvasView.frame = view.bounds
canvasView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(canvasView)
}
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
// 绘图内容已更改 -- 保存或处理
}
}Drawing Policies
绘图策略
| Policy | Behavior |
|---|---|
| Apple Pencil draws; finger scrolls |
| Both pencil and finger draw |
| Only Apple Pencil draws; finger always scrolls |
swift
canvasView.drawingPolicy = .pencilOnly| 策略 | 行为 |
|---|---|
| Apple Pencil绘图;手指滚动 |
| Pencil和手指均可绘图 |
| 仅Apple Pencil可绘图;手指始终用于滚动 |
swift
canvasView.drawingPolicy = .pencilOnlyConfiguring the Canvas
配置画布
swift
// Set a large drawing area (scrollable)
canvasView.contentSize = CGSize(width: 2000, height: 3000)
// Enable/disable the ruler
canvasView.isRulerActive = true
// Set the current tool programmatically
canvasView.tool = PKInkingTool(.pencil, color: .blue, width: 3)
canvasView.tool = PKEraserTool(.vector)swift
// 设置大尺寸绘图区域(支持滚动)
canvasView.contentSize = CGSize(width: 2000, height: 3000)
// 启用/禁用标尺
canvasView.isRulerActive = true
// 以编程方式设置当前工具
canvasView.tool = PKInkingTool(.pencil, color: .blue, width: 3)
canvasView.tool = PKEraserTool(.vector)PKToolPicker
PKToolPicker 工具选择器
PKToolPickerswift
class DrawingViewController: UIViewController {
let canvasView = PKCanvasView()
let toolPicker = PKToolPicker()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
toolPicker.setVisible(true, forFirstResponder: canvasView)
toolPicker.addObserver(canvasView)
canvasView.becomeFirstResponder()
}
}PKToolPickerswift
class DrawingViewController: UIViewController {
let canvasView = PKCanvasView()
let toolPicker = PKToolPicker()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
toolPicker.setVisible(true, forFirstResponder: canvasView)
toolPicker.addObserver(canvasView)
canvasView.becomeFirstResponder()
}
}Custom Tool Picker Items
自定义工具选择器项
Create a tool picker with specific tools.
swift
let toolPicker = PKToolPicker(toolItems: [
PKToolPickerInkingItem(type: .pen, color: .black),
PKToolPickerInkingItem(type: .pencil, color: .gray),
PKToolPickerInkingItem(type: .marker, color: .yellow),
PKToolPickerEraserItem(type: .vector),
PKToolPickerLassoItem(),
PKToolPickerRulerItem()
])创建包含特定工具的工具选择器。
swift
let toolPicker = PKToolPicker(toolItems: [
PKToolPickerInkingItem(type: .pen, color: .black),
PKToolPickerInkingItem(type: .pencil, color: .gray),
PKToolPickerInkingItem(type: .marker, color: .yellow),
PKToolPickerEraserItem(type: .vector),
PKToolPickerLassoItem(),
PKToolPickerRulerItem()
])Ink Types
墨迹类型
| Type | Description |
|---|---|
| Smooth, pressure-sensitive pen |
| Textured pencil with tilt shading |
| Semi-transparent highlighter |
| Uniform-width pen |
| Variable-width calligraphy pen |
| Blendable watercolor brush |
| Textured crayon |
| 类型 | 描述 |
|---|---|
| 平滑的压感钢笔 |
| 带倾斜阴影的纹理铅笔 |
| 半透明荧光笔 |
| 等宽钢笔 |
| 可变宽度的书法钢笔 |
| 可混合的水彩画笔 |
| 纹理蜡笔 |
PKDrawing Serialization
PKDrawing 序列化
PKDrawingDataswift
// Save
func saveDrawing(_ drawing: PKDrawing) throws {
let data = drawing.dataRepresentation()
try data.write(to: fileURL)
}
// Load
func loadDrawing() throws -> PKDrawing {
let data = try Data(contentsOf: fileURL)
return try PKDrawing(data: data)
}PKDrawingDataswift
// 保存
func saveDrawing(_ drawing: PKDrawing) throws {
let data = drawing.dataRepresentation()
try data.write(to: fileURL)
}
// 加载
func loadDrawing() throws -> PKDrawing {
let data = try Data(contentsOf: fileURL)
return try PKDrawing(data: data)
}Combining Drawings
合并绘图内容
swift
var drawing1 = PKDrawing()
let drawing2 = PKDrawing()
drawing1.append(drawing2)
// Non-mutating
let combined = drawing1.appending(drawing2)swift
var drawing1 = PKDrawing()
let drawing2 = PKDrawing()
drawing1.append(drawing2)
// 非可变方式
let combined = drawing1.appending(drawing2)Transforming Drawings
变换绘图内容
swift
let scaled = drawing.transformed(using: CGAffineTransform(scaleX: 2, y: 2))
let translated = drawing.transformed(using: CGAffineTransform(translationX: 100, y: 0))swift
let scaled = drawing.transformed(using: CGAffineTransform(scaleX: 2, y: 2))
let translated = drawing.transformed(using: CGAffineTransform(translationX: 100, y: 0))Exporting to Image
导出为图片
Generate a from a drawing.
UIImageswift
func exportImage(from drawing: PKDrawing, scale: CGFloat = 2.0) -> UIImage {
drawing.image(from: drawing.bounds, scale: scale)
}
// Export a specific region
let region = CGRect(x: 0, y: 0, width: 500, height: 500)
let scale = UITraitCollection.current.displayScale
let croppedImage = drawing.image(from: region, scale: scale)将绘图内容生成为。
UIImageswift
func exportImage(from drawing: PKDrawing, scale: CGFloat = 2.0) -> UIImage {
drawing.image(from: drawing.bounds, scale: scale)
}
// 导出特定区域
let region = CGRect(x: 0, y: 0, width: 500, height: 500)
let scale = UITraitCollection.current.displayScale
let croppedImage = drawing.image(from: region, scale: scale)Stroke Inspection
笔触检查
Access individual strokes, their ink, and control points.
swift
for stroke in drawing.strokes {
let ink = stroke.ink
print("Ink type: \(ink.inkType), color: \(ink.color)")
print("Bounds: \(stroke.renderBounds)")
// Access path points
let path = stroke.path
print("Points: \(path.count), created: \(path.creationDate)")
// Interpolate along the path
for point in path.interpolatedPoints(by: .distance(10)) {
print("Location: \(point.location), force: \(point.force)")
}
}访问单个笔触、其墨迹属性和控制点。
swift
for stroke in drawing.strokes {
let ink = stroke.ink
print("墨迹类型: \(ink.inkType), 颜色: \(ink.color)")
print("边界: \(stroke.renderBounds)")
// 访问路径点
let path = stroke.path
print("点数: \(path.count), 创建时间: \(path.creationDate)")
// 沿路径插值
for point in path.interpolatedPoints(by: .distance(10)) {
print("位置: \(point.location), 压力: \(point.force)")
}
}Constructing Strokes Programmatically
以编程方式构造笔触
swift
let points = [
PKStrokePoint(location: CGPoint(x: 0, y: 0), timeOffset: 0,
size: CGSize(width: 5, height: 5), opacity: 1,
force: 0.5, azimuth: 0, altitude: .pi / 2),
PKStrokePoint(location: CGPoint(x: 100, y: 100), timeOffset: 0.1,
size: CGSize(width: 5, height: 5), opacity: 1,
force: 0.5, azimuth: 0, altitude: .pi / 2)
]
let path = PKStrokePath(controlPoints: points, creationDate: Date())
let stroke = PKStroke(ink: PKInk(.pen, color: .black), path: path,
transform: .identity, mask: nil)
let drawing = PKDrawing(strokes: [stroke])swift
let points = [
PKStrokePoint(location: CGPoint(x: 0, y: 0), timeOffset: 0,
size: CGSize(width: 5, height: 5), opacity: 1,
force: 0.5, azimuth: 0, altitude: .pi / 2),
PKStrokePoint(location: CGPoint(x: 100, y: 100), timeOffset: 0.1,
size: CGSize(width: 5, height: 5), opacity: 1,
force: 0.5, azimuth: 0, altitude: .pi / 2)
]
let path = PKStrokePath(controlPoints: points, creationDate: Date())
let stroke = PKStroke(ink: PKInk(.pen, color: .black), path: path,
transform: .identity, mask: nil)
let drawing = PKDrawing(strokes: [stroke])SwiftUI Integration
SwiftUI 集成
Wrap in a for SwiftUI.
PKCanvasViewUIViewRepresentableswift
import SwiftUI
import PencilKit
struct CanvasView: UIViewRepresentable {
@Binding var drawing: PKDrawing
@Binding var toolPickerVisible: Bool
func makeUIView(context: Context) -> PKCanvasView {
let canvas = PKCanvasView()
canvas.delegate = context.coordinator
canvas.drawingPolicy = .anyInput
canvas.drawing = drawing
return canvas
}
func updateUIView(_ canvas: PKCanvasView, context: Context) {
if canvas.drawing != drawing {
canvas.drawing = drawing
}
let toolPicker = context.coordinator.toolPicker
toolPicker.setVisible(toolPickerVisible, forFirstResponder: canvas)
if toolPickerVisible { canvas.becomeFirstResponder() }
}
func makeCoordinator() -> Coordinator { Coordinator(self) }
class Coordinator: NSObject, PKCanvasViewDelegate {
let parent: CanvasView
let toolPicker = PKToolPicker()
init(_ parent: CanvasView) {
self.parent = parent
super.init()
}
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
parent.drawing = canvasView.drawing
}
}
}将封装为以在SwiftUI中使用。
PKCanvasViewUIViewRepresentableswift
import SwiftUI
import PencilKit
struct CanvasView: UIViewRepresentable {
@Binding var drawing: PKDrawing
@Binding var toolPickerVisible: Bool
func makeUIView(context: Context) -> PKCanvasView {
let canvas = PKCanvasView()
canvas.delegate = context.coordinator
canvas.drawingPolicy = .anyInput
canvas.drawing = drawing
return canvas
}
func updateUIView(_ canvas: PKCanvasView, context: Context) {
if canvas.drawing != drawing {
canvas.drawing = drawing
}
let toolPicker = context.coordinator.toolPicker
toolPicker.setVisible(toolPickerVisible, forFirstResponder: canvas)
if toolPickerVisible { canvas.becomeFirstResponder() }
}
func makeCoordinator() -> Coordinator { Coordinator(self) }
class Coordinator: NSObject, PKCanvasViewDelegate {
let parent: CanvasView
let toolPicker = PKToolPicker()
init(_ parent: CanvasView) {
self.parent = parent
super.init()
}
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
parent.drawing = canvasView.drawing
}
}
}Usage in SwiftUI
在SwiftUI中使用
swift
struct DrawingScreen: View {
@State private var drawing = PKDrawing()
@State private var showToolPicker = true
var body: some View {
CanvasView(drawing: $drawing, toolPickerVisible: $showToolPicker)
.ignoresSafeArea()
}
}swift
struct DrawingScreen: View {
@State private var drawing = PKDrawing()
@State private var showToolPicker = true
var body: some View {
CanvasView(drawing: $drawing, toolPickerVisible: $showToolPicker)
.ignoresSafeArea()
}
}PaperKit Relationship
与PaperKit的关系
PaperKit (iOS 26+) extends PencilKit with a complete markup experience
including shapes, text boxes, images, stickers, and loupes. Use PaperKit
when you need more than freeform drawing.
| Capability | PencilKit | PaperKit |
|---|---|---|
| Freeform drawing | Yes | Yes |
| Shapes & lines | No | Yes |
| Text boxes | No | Yes |
| Images & stickers | No | Yes |
| Markup toolbar | No | Yes |
| Data model | | |
PaperKit uses PencilKit under the hood -- accepts
for its property and can append a
. See for PaperKit patterns.
PaperMarkupViewControllerPKTooldrawingToolPaperMarkupPKDrawingreferences/paperkit-integration.mdPaperKit(iOS 26+)是PencilKit的扩展,提供完整的标注体验,包括形状、文本框、图片、贴纸和放大镜。如果需要的功能超出自由绘图,可使用PaperKit。
| 功能 | PencilKit | PaperKit |
|---|---|---|
| 自由绘图 | 是 | 是 |
| 形状与线条 | 否 | 是 |
| 文本框 | 否 | 是 |
| 图片与贴纸 | 否 | 是 |
| 标注工具栏 | 否 | 是 |
| 数据模型 | | |
PaperKit底层基于PencilKit实现 -- 的属性支持,也可追加内容。如需了解PaperKit的使用模式,请查看。
PaperMarkupViewControllerdrawingToolPKToolPaperMarkupPKDrawingreferences/paperkit-integration.mdCommon Mistakes
常见错误
DON'T: Forget to call becomeFirstResponder for the tool picker
错误做法:忘记为工具选择器调用becomeFirstResponder
The tool picker only appears when its associated responder is first responder.
swift
// WRONG: Tool picker never shows
toolPicker.setVisible(true, forFirstResponder: canvasView)
// CORRECT: Also become first responder
toolPicker.setVisible(true, forFirstResponder: canvasView)
canvasView.becomeFirstResponder()只有当关联的响应者成为第一响应者时,工具选择器才会显示。
swift
// 错误:工具选择器永远不会显示
toolPicker.setVisible(true, forFirstResponder: canvasView)
// 正确:同时设置为第一响应者
toolPicker.setVisible(true, forFirstResponder: canvasView)
canvasView.becomeFirstResponder()DON'T: Create multiple tool pickers for the same canvas
错误做法:为同一个画布创建多个工具选择器
One per canvas. Creating extras causes visual conflicts.
PKToolPickerswift
// WRONG
func viewDidAppear(_ animated: Bool) {
let picker = PKToolPicker() // New picker every appearance
picker.setVisible(true, forFirstResponder: canvasView)
}
// CORRECT: Store picker as a property
let toolPicker = PKToolPicker()一个画布对应一个。创建多个会导致视觉冲突。
PKToolPickerswift
// 错误
func viewDidAppear(_ animated: Bool) {
let picker = PKToolPicker() // 每次显示都创建新的选择器
picker.setVisible(true, forFirstResponder: canvasView)
}
// 正确:将选择器作为属性存储
let toolPicker = PKToolPicker()DON'T: Ignore content version for backward compatibility
错误做法:忽略内容版本以实现向后兼容
Newer ink types crash on older OS versions. Set
if you need backward-compatible drawings.
maximumSupportedContentVersionswift
// WRONG: Saves a drawing with .watercolor, crashes on iOS 16
canvasView.tool = PKInkingTool(.watercolor, color: .blue)
// CORRECT: Limit content version for compatibility
canvasView.maximumSupportedContentVersion = .version2较新的墨迹类型在旧版系统上会崩溃。如果需要向后兼容的绘图内容,请设置。
maximumSupportedContentVersionswift
// 错误:保存包含.watercolor的绘图内容,在iOS 16上会崩溃
canvasView.tool = PKInkingTool(.watercolor, color: .blue)
// 正确:限制内容版本以确保兼容性
canvasView.maximumSupportedContentVersion = .version2DON'T: Compare drawings by data representation
错误做法:通过数据表示比较绘图内容
PKDrawingswift
// WRONG
if drawing1.dataRepresentation() == drawing2.dataRepresentation() { }
// CORRECT
if drawing1 == drawing2 { }PKDrawingswift
// 错误
if drawing1.dataRepresentation() == drawing2.dataRepresentation() { }
// 正确
if drawing1 == drawing2 { }Review Checklist
检查清单
- set appropriately (
PKCanvasView.drawingPolicyfor Pencil-primary apps).default - stored as a property, not recreated each appearance
PKToolPicker - called to show the tool picker
canvasView.becomeFirstResponder() - Drawing serialized via and loaded via
dataRepresentation()PKDrawing(data:) - delegate method used to track changes
canvasViewDrawingDidChange - set if backward compatibility needed
maximumSupportedContentVersion - Exported images use appropriate scale factor for the device
- SwiftUI wrapper avoids infinite update loops by checking
drawing != binding - Tool picker observer added before becoming first responder
- Drawing bounds checked before image export (empty drawings have bounds)
.zero
- 已正确设置(以Pencil为主的应用推荐使用
PKCanvasView.drawingPolicy).default - 已作为属性存储,而非每次显示时重新创建
PKToolPicker - 已调用以显示工具选择器
canvasView.becomeFirstResponder() - 已通过序列化绘图内容,并通过
dataRepresentation()加载PKDrawing(data:) - 已使用代理方法跟踪内容变更
canvasViewDrawingDidChange - 若需要向后兼容,已设置
maximumSupportedContentVersion - 导出图片时使用了适配设备的缩放因子
- SwiftUI封装通过检查避免无限更新循环
drawing != binding - 在设置为第一响应者前已添加工具选择器观察者
- 导出图片前已检查绘图边界(空绘图的边界为)
.zero
References
参考资料
- Extended PencilKit patterns (advanced strokes, Scribble, delegate):
references/pencilkit-patterns.md - PaperKit integration patterns:
references/paperkit-integration.md - PencilKit framework
- PKCanvasView
- PKDrawing
- PKToolPicker
- PKInkingTool
- PKStroke
- Drawing with PencilKit
- Configuring the PencilKit tool picker
- 进阶PencilKit使用模式(高级笔触、Scribble、代理):
references/pencilkit-patterns.md - PaperKit集成模式:
references/paperkit-integration.md - PencilKit 框架
- PKCanvasView
- PKDrawing
- PKToolPicker
- PKInkingTool
- PKStroke
- 使用PencilKit绘图
- 配置PencilKit工具选择器