# SwiftUI Custom Timeline View Implementation: The Palmier Pro Architecture

> Master SwiftUI custom timeline view implementation with Palmier Pro. Discover how to achieve editor-grade performance using NSViewRepresentable and reactive state synchronization.

- Repository: [Palmier/palmier-pro](https://github.com/palmier-io/palmier-pro)
- Tags: architecture
- Published: 2026-06-24

---

**Palmier Pro achieves editor-grade timeline performance by bridging an AppKit NSView into SwiftUI via NSViewRepresentable, combining GPU-accelerated drawing with reactive state synchronization through a dedicated Coordinator pattern.**

Building a high-performance timeline in SwiftUI requires breaking out of the standard view hierarchy for demanding graphics operations. Palmier Pro implements a hybrid **SwiftUI custom timeline view** architecture that wraps a custom AppKit drawing surface inside a SwiftUI container. This approach delivers the smooth scrolling, precise hit-testing, and frame-accurate updates required for professional video editing while remaining fully composable within native SwiftUI view trees.

## Architecture Overview

The Palmier Pro timeline separates concerns across five distinct layers, each optimized for specific responsibilities. This design isolates rendering performance from state management and input handling.

- **SwiftUI Wrapper** (`TimelineContainerView`): Exposes the timeline as an `NSViewRepresentable` for placement in SwiftUI hierarchies.
- **AppKit Drawing View** (`TimelineView`): Handles fast redraws of tracks, clips, and playheads using direct layer rendering in [`Sources/PalmierPro/Timeline/TimelineView.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Timeline/TimelineView.swift).
- **Geometry Model** (`TimelineGeometry`): Pure-Swift struct in [`Sources/PalmierPro/Timeline/TimelineGeometry.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Timeline/TimelineGeometry.swift) managing frame-to-coordinate conversions.
- **Input Controller** (`TimelineInputController`): Delegates mouse and drag events from the view to the editor model (referenced in architecture but implemented separately).
- **Editor Model** (`EditorViewModel`): Holds timeline data and drives rendering state through published changes in [`Sources/PalmierPro/Editor/EditorViewModel.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Editor/EditorViewModel.swift).

## Bridging SwiftUI with AppKit

The `TimelineContainerView` implements `NSViewRepresentable` to embed AppKit components within SwiftUI. It constructs three sub-views: a header for track names (`TimelineHeaderView`), an `NSScrollView` hosting the drawing canvas, and a border line.

The implementation stores references to these sub-views in a `Coordinator` class, enabling the SwiftUI side to trigger AppKit redraws and synchronize scroll position with playback state.

```swift
// TimelineContainerView.swift – makeNSView implementation
let container = NSView()
let headerView = TimelineHeaderView(editor: editor)
let scrollView = NSScrollView()
let timelineView = TimelineView(editor: editor)
scrollView.documentView = timelineView

```

When the editor model updates, the `updateNSView` method invokes `needsDisplay = true` on the timeline view and adjusts content sizes dynamically.

## The Core AppKit Drawing View

`TimelineView` inherits from `NSView` and performs all rendering directly into its layer, bypassing SwiftUI's rendering overhead for complex timelines. The view owns a `TimelineCanvasView` for final drawing commands and hosts SwiftUI overlays—such as `PlayheadOverlay`, `SnapIndicatorOverlay`, and `ClipGeneratingOverlay`—as `NSHostingView` instances.

This hybrid approach allows GPU-accelerated background drawing while supporting rich SwiftUI overlay content. Key rendering steps include:

1. **Canvas Layout**: `layoutCanvas` matches the visible rectangle to the scroll view bounds.
2. **Content Sizing**: `updateContentSize` recalculates dimensions when frame counts, zoom scales, or track lists change.
3. **Direct Drawing**: The canvas draws using pre-computed `clipDisplayRects` for optimal performance.

```swift
// In TimelineView.swift – GPU-accelerated background setup
layer?.backgroundColor = AppTheme.Background.surface.cgColor
wantsLayer = true

```

## Geometry and Hit-Testing Logic

`TimelineGeometry` provides pure-Swift, side-effect-free calculations for coordinate conversion and spatial detection. This struct knows the pixel-per-frame scale, header width, and per-track heights, enabling thread-safe use in both rendering and input handling.

The struct exposes conversion methods critical for **SwiftUI custom timeline view** implementations:

- `xForFrame(_:)` and `frameAt(x:)` translate between frame numbers and X positions.
- `trackAt(y:)` maps Y coordinates to track indices.
- `dropTargetAt(y:)` determines whether a drag operation creates a new track or drops onto existing content.
- `insertionLineY(for:)` and `ghostY(for:)` calculate visual positions for drag feedback.

## Editor Model Integration

The `EditorViewModel` owns the timeline data and publishes render-state changes. The `Coordinator` observes these changes and triggers specific updates:

- `timelineView?.updateContentSize()` when the layout requires expansion.
- `timelineView?.needsDisplay = true` when visual elements refresh.
- Scroll-to-playhead logic during active playback via `updateNSView`.

This tight coupling ensures the UI remains synchronized with the model without redundant recomputation or frame drops.

## Implementation Examples

Embedding the timeline in a SwiftUI view requires only the container wrapper and environment injection:

```swift
struct EditorScreen: View {
    @Environment(EditorViewModel.self) private var editor
    
    var body: some View {
        TimelineContainerView()
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .environment(editor)
    }
}

```

To customize the timeline background, modify the layer property in [`TimelineView.swift`](https://github.com/palmier-io/palmier-pro/blob/main/TimelineView.swift):

```swift
layer?.backgroundColor = AppTheme.Background.surface.cgColor

```

For adding SwiftUI overlays like selection marquees, wrap them in `NSHostingView`:

```swift
// Inside TimelineView.swift
private var selectionOverlay: NSHostingView<SelectionOverlay>!

func setupSelectionOverlay() {
    selectionOverlay = NSHostingView(rootView: SelectionOverlay())
    addSubview(selectionOverlay)
}

```

## Summary

- **Palmier Pro** implements a **SwiftUI custom timeline view** by wrapping an `NSView` in `NSViewRepresentable` via `TimelineContainerView`.
- The **AppKit drawing view** (`TimelineView`) handles GPU-accelerated rendering using direct layer access, while `NSHostingView` manages SwiftUI overlays.
- **Pure-Swift geometry** (`TimelineGeometry`) provides thread-safe coordinate conversion and hit-testing without side effects.
- The **Coordinator pattern** synchronizes scroll position and redraws between the SwiftUI layer and the AppKit implementation.
- Source files reside in `Sources/PalmierPro/Timeline/` and `Sources/PalmierPro/Editor/`, with styling constants defined in [`Sources/PalmierPro/UI/AppTheme.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/UI/AppTheme.swift).

## Frequently Asked Questions

### Why use AppKit NSView instead of pure SwiftUI for the timeline?

SwiftUI's rendering engine introduces overhead for complex, high-frequency updates common in video editing timelines. By using an `NSView` with `wantsLayer = true`, Palmier Pro achieves GPU-accelerated drawing and precise control over redraw regions, enabling smooth scrolling with hundreds of clips while still hosting SwiftUI overlays via `NSHostingView`.

### How does the timeline handle drag-and-drop operations?

Drag-and-drop logic flows through `TimelineGeometry` methods like `dropTargetAt(y:)` and `trackAt(y:)`, which calculate drop targets and track indices from mouse coordinates. These pure functions execute in the input controller to determine whether to create new tracks or place clips on existing ones, then update the `EditorViewModel` which triggers UI refreshes through the Coordinator.

### Can I customize the timeline appearance without modifying the source?

You can customize colors by modifying the `AppTheme` constants in [`Sources/PalmierPro/UI/AppTheme.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/UI/AppTheme.swift) or by exposing the `layer?.backgroundColor` property through a SwiftUI binding on `TimelineContainerView`. For structural changes to how clips render, you must modify the drawing logic in `TimelineCanvasView` within [`TimelineView.swift`](https://github.com/palmier-io/palmier-pro/blob/main/TimelineView.swift).

### What triggers a timeline redraw when the playhead moves?

The `EditorViewModel` publishes playhead position changes, which the `Coordinator` observes in `TimelineContainerView`. The Coordinator then calls `timelineView?.needsDisplay = true` to invalidate the AppKit view, and optionally adjusts `scrollView` bounds to keep the playhead visible during playback, ensuring frame-perfect synchronization without full SwiftUI hierarchy updates.