# Optimizing SwiftUI View Updates for High-Frequency Playback State in Palmier Pro

> Optimize SwiftUI view updates for high-frequency playback state in Palmier Pro. Learn techniques to prevent UI stalls and maintain smooth performance during rapid changes.

- Repository: [Palmier/palmier-pro](https://github.com/palmier-io/palmier-pro)
- Tags: performance
- Published: 2026-07-27

---

**Palmier Pro prevents UI stalls during rapid playback by isolating high-frequency state changes in `VideoEngine`, using selective refresh logic via `visualRefreshAction`, and throttling interactions while keeping SwiftUI bindings minimal through `EditorViewModel`.**

High-frequency playback updates can overwhelm SwiftUI’s diffing engine, causing dropped frames and unresponsive interfaces. In the `palmier-io/palmier-pro` codebase, the playback architecture deliberately minimizes view invalidations by isolating mutable state and computing refresh intervals off the main actor. This approach ensures that preview playback at 4× speed remains smooth without triggering unnecessary view recompositions.

## Centralizing Playback Control in VideoEngine

The `VideoEngine` class in [`Sources/PalmierPro/Preview/VideoEngine.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Preview/VideoEngine.swift) serves as the central playback controller and is marked with `@MainActor` because it interacts with UI-bound objects like `AVPlayer` and the preview view. However, the engine immediately offloads heavy work—such as track loading and Lottie rendering—to background tasks, ensuring the main thread remains responsive.

Rather than exposing every internal detail to SwiftUI, the engine maintains a minimal observable surface. The `EditorViewModel` in [`Sources/PalmierPro/Editor/ViewModel/EditorViewModel.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Editor/ViewModel/EditorViewModel.swift) owns the primary playback state:
- **`isPlaying`**: A `private(set) var` exposed through `@Published`-compatible properties
- **`playbackRate`**: Updated via `setPlaybackRate(_:)` and bound to the view as `editor.playbackRate`
- **Playhead position**: Driven by a time observer installed in `VideoEngine` that posts updates only when the UI actually requires them

## Selective UI Refresh with visualRefreshAction

At line 623 of [`VideoEngine.swift`](https://github.com/palmier-io/palmier-pro/blob/main/VideoEngine.swift), the `visualRefreshAction(isPlaying:playbackRate:)` method decides whether the UI actually needs a refresh. When the playback rate is high—such as `.quadruple`—the method checks `allowsAudioMetering` and returns **`.none`**, preventing the view from recomputing audio-meter UI on every tick. This selective logic stops a flood of redraws during fast playback while maintaining meter updates during normal-speed preview.

The method integrates with `ScrubAudioEngine` to stop playback metering when high-speed flags are detected, further reducing processing overhead during rapid state changes.

## Calculating Observer Intervals Off the Main Actor

To reduce main actor contention, `VideoEngine` declares the `playheadObserverInterval(for:)` function as `nonisolated` at line 692. Because this helper performs no UI work, Swift can execute it without hopping back onto the main actor.

The interval calculation automatically scales with the playback rate:

```swift
nonisolated static func playheadObserverInterval(for playbackRate: PreviewPlaybackRate) -> CMTime {
    let updatesPerSecond: Double = 30   // Desired UI refresh cap
    return CMTime(seconds: Double(playbackRate.rawValue) / updatesPerSecond,
                 preferredTimescale: 600)
}

```

This caps UI updates to 30 per second regardless of playback speed, ensuring that `VideoEngine` never overwhelms SwiftUI’s state processing.

## Throttling Interactive Scrubbing

Interactive seeks are throttled through the `interactiveThrottleTask` and `lastInteractiveDispatchTime` properties. When users drag the playhead rapidly in [`PreviewContainerView.swift`](https://github.com/palmier-io/palmier-pro/blob/main/PreviewContainerView.swift), the engine batches these scrub events instead of processing every single position change. This throttling mechanism limits the number of seek events delivered to the UI, preventing view churn during aggressive timeline interactions.

## Isolating Mutable State in EditorViewModel

All mutable state that could trigger view updates lives inside `EditorViewModel`, which exposes only a few `@Published`-compatible properties to SwiftUI. Changes to unrelated data—such as `trackMappings` or `clipTransforms`—do not invalidate the view hierarchy because they remain isolated from the observable publisher chain.

UI components in [`Sources/PalmierPro/Preview/PreviewContainerView.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Sources/PalmierPro/Preview/PreviewContainerView.swift) bind to these specific properties rather than the engine’s raw output, creating a clean separation between high-frequency backend updates and SwiftUI’s reconciliation process.

## Implementation Code Examples

The following patterns demonstrate how the UI initiates rate changes and how the engine processes them efficiently:

**1. SwiftUI menu action triggering a rate change:**

```swift
Button(action: { editor.setPlaybackRate(.quadruple) }) {
    Text("4×")
}

```

**2. VideoEngine handling the update without blocking the main thread:**

```swift
func setPlaybackRate(_ rate: PreviewPlaybackRate) {
    player.defaultRate = rate.rawValue
    installTimeObserver(for: rate)          // Re‑install with proper interval
    if !rate.allowsAudioMetering {
        scrubAudioEngine.stopPlaybackMetering()
    }
    if editor?.isPlaying == true, player.timeControlStatus != .paused {
        player.rate = rate.rawValue
    }
}

```

**3. Interval calculation avoiding main actor hops:**

```swift
nonisolated static func playheadObserverInterval(for playbackRate: PreviewPlaybackRate) -> CMTime {
    let updatesPerSecond: Double = 30
    return CMTime(seconds: Double(playbackRate.rawValue) / updatesPerSecond,
                 preferredTimescale: 600)
}

```

The [`MediaVisualCache.swift`](https://github.com/palmier-io/palmier-pro/blob/main/MediaVisualCache.swift) file complements this system by capping concurrent waveform extraction, ensuring that background media processing never interferes with the playback thread’s stability.

## Summary

- **`VideoEngine`** operates on the main actor but offloads heavy work to background tasks, isolating UI-bound operations from processing overhead.
- **`visualRefreshAction`** at line 623 selectively suppresses UI updates during high-speed playback, returning `.none` when audio metering is disabled.
- **`playheadObserverInterval`** uses a `nonisolated` static method to calculate time intervals without main actor contention, scaling automatically with playback rate.
- **Throttled scrubbing** batches rapid seek events via `interactiveThrottleTask`, preventing UI churn during timeline drags.
- **`EditorViewModel`** exposes only essential `@Published` properties (`isPlaying`, `playbackRate`), ensuring that structural data changes do not trigger unnecessary view recompositions.

## Frequently Asked Questions

### How does Palmier Pro prevent UI lag during fast playback?

The engine calls `visualRefreshAction` to determine if the UI actually needs updating. At rates like `.quadruple`, it returns `.none` and disables audio metering, preventing SwiftUI from recomputing expensive meter views on every frame tick. This selective refresh logic is verified in [`Tests/PalmierProTests/Editor/PreviewPlaybackRateTests.swift`](https://github.com/palmier-io/palmier-pro/blob/main/Tests/PalmierProTests/Editor/PreviewPlaybackRateTests.swift).

### Why is the playheadObserverInterval method marked nonisolated?

Marking `playheadObserverInterval` as `nonisolated` (line 692) allows Swift to execute the interval calculation without switching to the main actor. Since the function performs pure computation with no UI side effects, this eliminates unnecessary context switches and reduces contention during high-frequency playback updates.

### What role does EditorViewModel play in optimizing SwiftUI updates?

`EditorViewModel` acts as a gatekeeper by exposing only `isPlaying` and `playbackRate` as `@Published` properties. All other mutable state—such as clip transforms and track mappings—remains private, ensuring that background calculations never invalidate the SwiftUI view hierarchy unless explicitly intended.

### How does throttling improve scrubbing performance?

The `interactiveThrottleTask` and `lastInteractiveDispatchTime` properties in `VideoEngine` batch rapid seek events when users drag the playhead. Instead of processing every position change, the engine delivers limited, timed updates to the UI, preventing the view system from choking on high-frequency interaction events.