You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PencilKit自定义套索工具移动多PKStroke卡顿,求优化方案

解决PencilKit多笔画移动卡顿问题的优化方案

问题根源

你当前的实现每次移动单个笔画时都会修改canvas.drawing,每修改一次就会触发一次canvasViewDrawingDidChange回调,多个笔画时会重复触发视图刷新与重绘,这是卡顿的核心原因。此外直接设置transform的方式还存在位移无法累加的逻辑问题(拖动多次会出现跳变)。

优化方案

1. 批量更新绘图,减少刷新次数

PKDrawing是值类型,每次修改单个笔画都会生成新的PKDrawing实例。改为先创建绘图副本,批量修改所有选中笔画后一次性赋值给画布,这样只会触发一次视图刷新。

2. 保存初始变换,正确累加位移

维护选中笔画的初始transform,确保每次拖动的位移是基于原始位置的累加,避免transform被反复重置导致的跳变问题。

3. 优化选中逻辑(提升选中效率)

先通过笔画的boundingBox快速过滤与选中矩形无交集的笔画,减少不必要的点遍历操作。

完整优化代码

新增状态变量

// 存储选中笔画的索引
private var selected: [Int] = []
// 记录选中笔画的初始transform,用于计算累加位移
private var originalStrokeTransforms: [Int: CGAffineTransform] = [:]
// 记录当前拖动的总位移
private var totalDragOffset: CGSize = .zero

优化选中函数

func select(rect: CGRect) {
    selected.removeAll()
    originalStrokeTransforms.removeAll()
    let selectionPath = UIBezierPath(roundedRect: rect, cornerRadius: 5)
    
    for (index, stroke) in canvas.drawing.strokes.enumerated() {
        // 先通过boundingBox快速过滤,跳过完全不在选中区域的笔画
        let transformedStrokeBounds = stroke.boundingBox.applying(stroke.transform)
        guard rect.intersects(transformedStrokeBounds) else {
            continue
        }
        
        // 再检查笔画路径点是否在选中区域内
        for point in stroke.path {
            if selectionPath.contains(point.location) {
                selected.append(index)
                originalStrokeTransforms[index] = stroke.transform
                break
            }
        }
    }
    // 重置拖动位移
    totalDragOffset = .zero
}

优化移动函数

func moveSelected(with delta: CGSize) {
    // 累加本次拖动的位移
    totalDragOffset.width += delta.width
    totalDragOffset.height += delta.height
    
    // 创建绘图副本,批量修改选中笔画
    var updatedStrokes = canvas.drawing.strokes
    for index in selected {
        guard let originalTransform = originalStrokeTransforms[index] else {
            continue
        }
        // 基于初始transform叠加总位移
        let newTransform = originalTransform.concatenating(
            CGAffineTransform(translationX: totalDragOffset.width, y: totalDragOffset.height)
        )
        updatedStrokes[index].transform = newTransform
    }
    
    // 一次性更新画布,仅触发一次视图刷新
    canvas.drawing = PKDrawing(strokes: updatedStrokes)
}

额外优化建议

  • 在canvasViewDrawingDidChange回调中,避免执行不必要的耗时操作,比如在拖动过程中可以跳过数据持久化等逻辑,仅在拖动结束后执行。
  • 如果选中区域较大,可考虑异步执行选中逻辑,避免阻塞主线程。

内容的提问来源于stack exchange,提问作者jakob witsch

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 19:02:29