Path Trim Draw On
A custom shape, such as a logo, that draws itself along its path by animating trim.

- Drawing
- iOS 18.0+
- beginner
- Origin
- Original work
- Found by
- path · trim · draw · logo · stroke · shape · line animation · trazado · dibujar · logo · trazo · forma · animación de línea
Description
Section titled “Description”Draws any shape along its path when isDrawn turns true, over a faint guide of the same shape.
With Reduce Motion on, the shape appears at once.
struct PathTrimDrawOnDemo: View { @State private var isDrawn = false @Environment(\.accessibilityReduceMotion) private var reduceMotion
var body: some View { VStack(spacing: 28) { PathTrimDrawOn(shape: PathTrimDrawOnHeartbeat(), isDrawn: isDrawn, label: "Heart with a pulse line", duration: 1.5) .frame(maxWidth: 280, maxHeight: 280)
Text("Heartbeat") .font(.system(.largeTitle, design: .rounded, weight: .bold)) } .padding(32) .frame(maxWidth: .infinity, maxHeight: .infinity) .background( LinearGradient(colors: [.pink.opacity(0.14), .orange.opacity(0.1)], startPoint: .top, endPoint: .bottom) .ignoresSafeArea() ) .task(id: reduceMotion) { guard !reduceMotion else { isDrawn = true return }
while !Task.isCancelled { isDrawn = true try? await Task.sleep(for: .seconds(2.2)) guard !Task.isCancelled else { return } isDrawn = false try? await Task.sleep(for: .seconds(1.8)) } } }}API reference
Section titled “API reference”| Parameter | Type | Default |
|---|---|---|
shape | Outline | Required |
isDrawn | Bool | Required |
label | LocalizedStringResource | "Logo" |
colors | [Color] | [.pink, .orange] |
lineWidth | CGFloat | 8 |
duration | Double | 1.6 |
Copy this code into your project, or download the file.
// SPDX-License-Identifier: MIT// Copyright (c) 2026 Oscar Canton and SwiftUI Blocks contributors
import SwiftUI
/// Draws any shape along its path when `isDrawn` turns true, over a faint guide of the same shape.////// With Reduce Motion on, the shape appears at once.struct PathTrimDrawOn<Outline: Shape>: View { let shape: Outline let isDrawn: Bool var label: LocalizedStringResource = "Logo" var colors: [Color] = [.pink, .orange] var lineWidth: CGFloat = 8 var duration: Double = 1.6
@Environment(\.accessibilityReduceMotion) private var reduceMotion
private var style: StrokeStyle { StrokeStyle(lineWidth: max(lineWidth, 0), lineCap: .round, lineJoin: .round) }
var body: some View { ZStack { shape.stroke(.quaternary, style: style)
shape .trim(from: 0, to: isDrawn ? 1 : 0) .stroke(LinearGradient(colors: colors, startPoint: .topLeading, endPoint: .bottomTrailing), style: style) } .animation(reduceMotion ? nil : .easeInOut(duration: duration > 0 ? duration : 0.01), value: isDrawn) .accessibilityElement() .accessibilityLabel(label) }}
/// A heart with a pulse line through it, scaled to fit any rectangle.nonisolated struct PathTrimDrawOnHeartbeat: Shape { /// The margin the heart's curves keep from the left and the top of the unit square. private static let inset: CGFloat = 0.04
func path(in rect: CGRect) -> Path { let side = min(rect.width, rect.height) guard side > 0 else { return Path() }
let origin = CGPoint(x: rect.midX - side / 2, y: rect.midY - side / 2) func point(_ x: CGFloat, _ y: CGFloat) -> CGPoint { CGPoint(x: origin.x + x * side, y: origin.y + y * side) }
var path = Path() path.move(to: point(0.5, 0.92)) path.addCurve(to: point(Self.inset, 0.36), control1: point(0.2, 0.7), control2: point(Self.inset, 0.54)) path.addCurve(to: point(0.5, 0.24), control1: point(Self.inset, 0.08), control2: point(0.4, Self.inset)) path.addCurve(to: point(0.96, 0.36), control1: point(0.6, Self.inset), control2: point(0.96, 0.08)) path.addCurve(to: point(0.5, 0.92), control1: point(0.96, 0.54), control2: point(0.8, 0.7))
path.move(to: point(0.16, 0.5)) path.addLine(to: point(0.36, 0.5)) path.addLine(to: point(0.44, 0.32)) path.addLine(to: point(0.55, 0.68)) path.addLine(to: point(0.63, 0.5)) path.addLine(to: point(0.84, 0.5)) return path }}
// MARK: - Demo
struct PathTrimDrawOnDemo: View { @State private var isDrawn = false @Environment(\.accessibilityReduceMotion) private var reduceMotion
var body: some View { VStack(spacing: 28) { PathTrimDrawOn(shape: PathTrimDrawOnHeartbeat(), isDrawn: isDrawn, label: "Heart with a pulse line", duration: 1.5) .frame(maxWidth: 280, maxHeight: 280)
Text("Heartbeat") .font(.system(.largeTitle, design: .rounded, weight: .bold)) } .padding(32) .frame(maxWidth: .infinity, maxHeight: .infinity) .background( LinearGradient(colors: [.pink.opacity(0.14), .orange.opacity(0.1)], startPoint: .top, endPoint: .bottom) .ignoresSafeArea() ) .task(id: reduceMotion) { guard !reduceMotion else { isDrawn = true return }
while !Task.isCancelled { isDrawn = true try? await Task.sleep(for: .seconds(2.2)) guard !Task.isCancelled else { return } isDrawn = false try? await Task.sleep(for: .seconds(1.8)) } } }}
#Preview { PathTrimDrawOnDemo()}