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

求助:如何创建含线条/曲线的自定义PKDrawing文件

创建自定义PKDrawing文件的实现思路与技巧

核心前提:.drawing是PencilKit专属序列化格式

你用随机JSON转.drawing失败的原因很明确:.drawing不是普通JSON,它是PencilKit定义的结构化格式,必须包含version、strokes(每个stroke含points、drawingTool等必填字段),随便生成的JSON不符合规范,自然无法解析。


实现思路

1. 代码直接生成PKDrawing(灵活可控)

PKDrawing本质是[PKStroke]数组,每个PKStroke由一系列PKStrokePoint组成(包含位置、时间、压力、笔触参数)。你可以手动构建这些数据,生成任意线条/曲线:

  • 基础流程:定义笔触样式(PKInk)→ 创建点序列 → 生成Stroke → 组合成Drawing
  • 适合批量生成标准化图形(比如你代码里的字母库)

示例代码:

// 生成带手绘质感的曲线
func createCurveDrawing() -> PKDrawing {
    // 用贝塞尔曲线生成平滑路径
    let bezierPath = UIBezierPath()
    bezierPath.move(to: CGPoint(x: 30, y: 100))
    bezierPath.addCurve(to: CGPoint(x: 270, y: 100),
                        controlPoint1: CGPoint(x: 100, y: 20),
                        controlPoint2: CGPoint(x: 200, y: 180))
    
    // 将路径转为PKStrokePoint数组,添加随机压力模拟手绘
    let points = stride(from: 0.0, to: 1.0, by: 0.02).map { t in
        let point = bezierPath.point(at: t)
        let randomForce = Double.random(in: 0.4...0.8)
        return PKStrokePoint(
            location: point,
            timeOffset: t * 0.5,
            size: CGSize(width: 2, height: 2),
            opacity: 1,
            force: randomForce,
            azimuth: 0,
            altitude: 0
        )
    }
    
    let stroke = PKStroke(
        ink: PKInk(.pen, color: .systemBlue),
        points: points,
        transform: .identity
    )
    
    return PKDrawing(strokes: [stroke])
}

2. 导出手绘.drawing文件作为模板

如果需要更自然的手绘风格,直接用PencilKit画布手绘后导出合法文件:

  • 写一个极简的PencilKit测试App,或用系统Notes app手绘后分享导出.drawing文件
  • 将导出的文件加入项目资源,用PKDrawing(data:)加载即可
  • 这种方式生成的文件完全符合格式规范,不会出现解析失败问题

3. 正确序列化/反序列化JSON

如果需要手动处理JSON,必须通过PencilKit API生成合法结构:

  • 先通过代码或手绘得到PKDrawing,再用drawing.dataRepresentation()转成Data(这就是.drawing文件的本质)
  • 若要查看JSON结构,可将Data转成JSON字符串分析,但不要手动修改核心字段
  • 示例:保存/加载.drawing文件
// 保存PKDrawing到本地
func saveDrawing(_ drawing: PKDrawing, filename: String) {
    let data = drawing.dataRepresentation()
    let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        .appendingPathComponent("\(filename).drawing")
    try? data.write(to: url)
}

// 从本地加载PKDrawing
func loadDrawing(filename: String) -> PKDrawing? {
    let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        .appendingPathComponent("\(filename).drawing")
    guard let data = try? Data(contentsOf: url) else { return nil }
    return try? PKDrawing(data: data)
}

实用技巧

  • 优化线条流畅度:代码生成点时,用贝塞尔曲线插值生成密集点,避免线条生硬;
  • 模拟真实手绘:给PKStrokePoint的force、azimuth参数添加随机值,让生成的线条更接近真实笔触;
  • 批量资源管理:像你的TextGenerator那样,提前预加载所有.drawing资源,初始化时验证有效性,避免运行时崩溃;
  • 复用Stroke模板:将常用的线条/曲线保存成模板Drawing,后续通过复制、变换(平移/缩放)快速生成变体。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 07:10:22