<!--
{
  "documentType" : "article",
  "framework" : "PencilKit",
  "identifier" : "/documentation/PencilKit/importing-external-drawing-data-into-pencilkit",
  "metadataVersion" : "0.1.0",
  "role" : "article",
  "title" : "Importing Bézier path data into PencilKit"
}
-->

# Importing Bézier path data into PencilKit

Convert existing Bézier-based stroke data into PencilKit drawing strokes.

## Overview

If your app handles drawing data as Bézier paths, such as when you import an existing file format, interface with a third-party library, or store drawing data in your own data model, you can convert those Bézier paths directly into PencilKit drawing strokes and adopt PencilKit as your rendering engine without discarding your existing data.

PencilKit uses a stroke format optimized for Apple Pencil input, represented by [`PKStroke`](/documentation/PencilKit/PKStroke-swift.struct), which differs from the Bézier path format that Core Graphics and other frameworks use. Because the two formats represent curves differently, the conversion is an approximation. After you convert your stroke data, review the results to confirm the strokes look as you expect.

## Convert a Bézier path to a stroke

To convert a Bézier path, initialize a [`PKStrokePath`](/documentation/PencilKit/PKStrokePath-swift.struct) from a <doc://com.apple.documentation/documentation/CoreGraphics/CGPath> using [`init(bezierPath:creationDate:pointProvider:)`](/documentation/PencilKit/PKStrokePath-swift.struct/init(bezierPath:creationDate:pointProvider:)). Because a Bézier path describes only the shape of a curve and not the per-point properties PencilKit uses for rendering, such as pressure, opacity, and size, you supply those values through the `pointProvider` closure, which the system calls once for each point in the resulting path.

> Important: ``doc://com.apple.pencilkit/documentation/PencilKit/PKStrokePath-swift.struct/init(bezierPath:creationDate:pointProvider:)`` only converts the first subpath. Split each subpath into its own <doc://com.apple.documentation/documentation/CoreGraphics/CGPath> before converting.

To call the initializer, you supply two values alongside the Bézier path: a `creationDate` for the stroke, and a `pointProvider` closure that returns rendering properties for each point. The `creationDate` is the start time of the stroke. Pass a timestamp from your source data if one exists, or use <doc://com.apple.documentation/documentation/Foundation/Date>() for the current time.

The number of points PencilKit derives from your Bézier path may differ from the number of control points in the original. Use `index` and `pointCount` to calculate values that vary along the stroke rather than mapping points one-to-one from your source data.

The initializer calls `pointProvider` once for each derived point, passing a [`PKStrokePath.ConvertedBezierPoint`](/documentation/PencilKit/PKStrokePath-swift.struct/ConvertedBezierPoint) with the following values:

- [`location`](/documentation/PencilKit/PKStrokePath-swift.struct/ConvertedBezierPoint/location): The position of the derived B-spline control point, which you pass directly to [`PKStrokePoint`](/documentation/PencilKit/PKStrokePoint-swift.struct).
- [`index`](/documentation/PencilKit/PKStrokePath-swift.struct/ConvertedBezierPoint/index) and [`pointCount`](/documentation/PencilKit/PKStrokePath-swift.struct/ConvertedBezierPoint/pointCount): `index` is the zero-based position of the control point in the derived path, and `pointCount` is the total number of control points. Divide one by the other to calculate a progress value between 0 and 1 for properties that vary along the stroke.
- [`bezierSegmentIndex`](/documentation/PencilKit/PKStrokePath-swift.struct/ConvertedBezierPoint/bezierSegmentIndex): The index of the original Bézier segment that this derived control point maps to. Use this value if your source data stores per-segment properties like color or width.

PencilKit sets `location` from the Bézier path geometry, but you define all other properties — such as `size`, `opacity`, and `force` — in your `pointProvider` closure. For the following properties, consider whether your source data includes values to use rather than a fixed default:

- [`timeOffset`](/documentation/PencilKit/PKStrokePoint-swift.struct/timeOffset): The time in seconds from the stroke’s `creationDate` to this derived control point. The example below distributes time evenly along the stroke based on index position, giving earlier points smaller offsets and later points larger ones. If your source data includes per-point or per-segment timestamps, use those elapsed times instead.
- [`azimuth`](/documentation/PencilKit/PKStrokePoint-swift.struct/azimuth) and [`altitude`](/documentation/PencilKit/PKStrokePoint-swift.struct/altitude): These properties describe the orientation of an Apple Pencil. For imported data that doesn’t include pencil orientation, use reasonable values instead. For example, an altitude of `.pi / 4` matches how most people naturally hold a pencil. Because azimuth only affects rendering once the pencil is tilted, choose a value that fits your ink and app rather than relying on a single default.

The following example shows a complete conversion from a <doc://com.apple.documentation/documentation/CoreGraphics/CGPath> to a `PKStroke`, using `index` and `pointCount` to calculate `timeOffset` and applying uniform values for all other properties:

```swift
func makeStroke(from bezierPath: CGPath, ink: PKInk) -> PKStroke {
    let path = PKStrokePath(
        bezierPath: bezierPath,
        creationDate: Date(),
        pointProvider: { convertedPoint in
            let progress = CGFloat(convertedPoint.index) / CGFloat(convertedPoint.pointCount)
            return PKStrokePoint(
                location: convertedPoint.location,
                timeOffset: 0.5 * progress,
                size: CGSize(width: 3.0, height: 3.0),
                opacity: 1.0,
                force: 1.0,
                azimuth: .pi,
                altitude: .pi / 4,
                secondaryScale: 1.0,
                threshold: 0.0
            )
        }
    )
    return PKStroke(ink: ink, path: path)
}
```

## Save and load PencilKit strokes in a Bézier file format

If you want to keep Bézier paths as your file format after adopting PencilKit, you can export strokes back to Bézier paths for saving and reload them with full fidelity. Use [`bezierRepresentation`](/documentation/PencilKit/PKStrokePath-swift.struct/bezierRepresentation) to export a stroke path to a <doc://com.apple.documentation/documentation/CoreGraphics/CGPath> for saving, then [`init(bezierPath:creationDate:pointProvider:)`](/documentation/PencilKit/PKStrokePath-swift.struct/init(bezierPath:creationDate:pointProvider:)) to load it back.

A Bézier path stores only the shape of the curve and doesn’t include properties like size, opacity, and force. When exporting a PencilKit stroke, save those properties separately for each point so you can reconstruct the full stroke when loading back.

The following example shows how to save these additional properties alongside the Bézier path:

```swift
struct SavedPoint: Codable {
    let timeOffset: TimeInterval
    let size: CGSize
    let opacity: CGFloat
    let force: CGFloat
    let azimuth: CGFloat
    let altitude: CGFloat
    let secondaryScale: CGFloat
    let threshold: CGFloat

    init(_ point: PKStrokePoint) {
        timeOffset = point.timeOffset
        size = point.size
        opacity = point.opacity
        force = point.force
        azimuth = point.azimuth
        altitude = point.altitude
        secondaryScale = point.secondaryScale
        threshold = point.threshold
    }
}

let bezierPath = stroke.path.bezierRepresentation
let savedPoints = stroke.path.map { SavedPoint($0) }
```

When loading back a path exported with [`bezierRepresentation`](/documentation/PencilKit/PKStrokePath-swift.struct/bezierRepresentation), the number of control points is guaranteed to match the original stroke’s point count — so you can look up each point’s saved data by index:

```swift
let restoredPath = PKStrokePath(
    bezierPath: bezierPath,
    creationDate: Date(),
    pointProvider: { convertedPoint in
        let saved = savedPoints[convertedPoint.index]
        return PKStrokePoint(
            location: convertedPoint.location,
            timeOffset: saved.timeOffset,
            size: saved.size,
            opacity: saved.opacity,
            force: saved.force,
            azimuth: saved.azimuth,
            altitude: saved.altitude,
            secondaryScale: saved.secondaryScale,
            threshold: saved.threshold
        )
    }
)
```

## Add the converted strokes to a drawing

[`PKDrawing`](/documentation/PencilKit/PKDrawing-swift.struct) holds all the strokes that appear in a canvas. After converting your strokes, set the [`strokes`](/documentation/PencilKit/PKDrawing-swift.struct/strokes) property on a new drawing and assign it to your [`PKCanvasView`](/documentation/PencilKit/PKCanvasView). This replaces any existing content in the canvas with your imported strokes. The following example converts a collection of legacy paths to strokes and assigns them to a canvas:

```swift
let strokes = legacyDocument.paths.map { path in
    makeStroke(from: path.cgPath, ink: PKInk(inkType: .pen, color: path.color))
}

var drawing = PKDrawing()
drawing.strokes = strokes
canvasView.drawing = drawing
```

---

Copyright &copy; 2026 Apple Inc. All rights reserved. | [Terms of Use](https://www.apple.com/legal/internet-services/terms/site.html) | [Privacy Policy](https://www.apple.com/privacy/privacy-policy)