Sneak Peeks

Show a focused preview below a compact DynamicLake live activity.

Overview

A sneak peek is optional expanded content for a compact live activity. Use it when the compact activity needs a small amount of supporting information, such as a date, progress detail, or simple controls.

The sneak peek should feel connected to the compact activity. DynamicLake owns the panel shape, background, separator, clipping, and hover behavior. Your extension or plugin provides the content.

Extensions render sneak peeks with SwiftUI. Plugins describe sneak peeks with JSON left, center, and right slots. Use JSONPluginAPI when you are building a plugin.

!Live activity and sneak peek regions shown in DynamicLake.

Best Practices

JSON plugins use the same left, center, and right sneak peek slots with predefined components. Plugins can also open the sneak peek for a few seconds without hovering and animate changing numbers. See JSONPluginAPI for the message schema, including JSONPluginAPI — Show The Sneak Peek Automatically and JSONPluginAPI — Animated Numbers.

Enable Sneak Peek

Set supportsSneakPeek to true in the activity configuration.

let configuration = DynamicLakeLiveActivityConfiguration(
    id: "clock",
    priority: .normal,
    size: .large,
    supportsSneakPeek: true
)

Pass the same value when the companion app opens the activity.

DynamicLakeActivityCenter.setActivityActive(
    true,
    extensionBundleIdentifier: "com.example.ClockDynamicLakeExtension",
    title: "Clock",
    priority: .normal,
    supportsSneakPeek: true
)

Add the Scene

Add DynamicLakeSneakPeek beside the compact scene.

import DynamicLakeKit
import SwiftUI

@main
struct ClockDynamicLakeExtension: DynamicLakeExtension {
    var body: some DynamicLakeExtensionScene {
        DynamicLakeLiveActivity {
            ClockLiveActivityView()
        }

        DynamicLakeSneakPeek {
            ClockSneakPeekView()
        }
    }
}

Keep this scene focused on expanded content only.

Build the Layout

Use SneakPeekSlotLayout to align preview content with the compact activity.

import DynamicLakeKit
import SwiftUI

struct ClockSneakPeekView: View {
    let geometry: SneakPeekGeometry?

    init(geometry: SneakPeekGeometry? = nil) {
        self.geometry = geometry
    }

    var body: some View {
        TimelineView(.periodic(from: .now, by: 1)) { timeline in
            GeometryReader { proxy in
                let geometry = resolvedGeometry(for: proxy.size)

                SneakPeekSlotLayout(
                    geometry: geometry,
                    leftSlotSizing: .empty,
                    rightSlotSizing: .empty,
                    centerAlignment: .center
                ) {
                    SneakPeekSlot.empty
                } center: {
                    Text(timeline.date.formatted(.dateTime.weekday(.wide).month(.wide).day().year()))
                        .font(.system(size: 11, weight: .semibold, design: .rounded))
                        .lineLimit(1)
                        .minimumScaleFactor(0.7)
                        .foregroundStyle(.white.opacity(0.82))
                } right: {
                    SneakPeekSlot.empty
                }
                .frame(width: proxy.size.width, height: proxy.size.height)
            }
        }
    }

    private func resolvedGeometry(for size: CGSize) -> SneakPeekGeometry {
        if let geometry {
            return geometry.fitting(width: size.width, height: size.height)
        }

        let contentHeight = max(size.height, 1)
        return SneakPeekGeometry.current(
            activity: DynamicLakeLiveActivityConfiguration(
                id: "clock",
                priority: .normal,
                size: .large,
                supportsSneakPeek: true
            ),
            panelSize: CGSize(
                width: max(size.width, 1),
                height: LiveActivitySize.large.defaultTotalSize.height + contentHeight
            ),
            contentHeight: contentHeight
        )
    }
}

The optional geometry lets the same view work in a direct extension scene and in a simulator provider.

Slot Layout

SneakPeekSlotLayout divides the preview into three regions.

| Slot | Use for | | --- | --- | | Left | Content aligned under the compact left symbol. | | Center | Main preview information or controls. | | Right | Content aligned under the compact right accessory. |

Use .empty for side slots that should not reserve width.

Use .reservingLeading(_:), .reservingTrailing(_:), or .reserving(leading:trailing:) when a side slot needs a little extra layout space beside the rendered content. This is useful for compact controls that should stay aligned with the live activity while still giving the center text enough room.

SneakPeekSlotLayout(
    geometry: geometry,
    leftSlotSizing: .fixed(24),
    rightSlotSizing: .fixed(48).reservingTrailing(12),
    sideSpacing: 8
) {
    Image(systemName: "phone.fill")
} center: {
    Text("Home")
} right: {
    CallControls()
}

Use rightAnchorExtraPadding and leftAnchorExtraPadding only to match a compact activity whose visual anchor is already customized. For ordinary spacing around sneak peek content, prefer slot reserves.

!Left and right slots aligned around the compact activity.

SneakPeekSlotLayout(
    geometry: geometry,
    leftSlotSizing: .fixed(28),
    rightSlotSizing: .empty
) {
    Image(systemName: "calendar")
} center: {
    Text("Sunday, August 2, 2026")
} right: {
    SneakPeekSlot.empty
}

Show the Sneak Peek Automatically

By default a sneak peek opens only while the user hovers over the live activity. To get the user's attention, for example when a download finishes, the companion app can open it for a few seconds without hovering and then close it:

DynamicLakeActivityCenter.showSneakPeek(
    extensionBundleIdentifier: "com.example.DownloadsDynamicLakeExtension",
    activityIdentifier: "download"
)

DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    DynamicLakeActivityCenter.closeSneakPeek(
        extensionBundleIdentifier: "com.example.DownloadsDynamicLakeExtension",
        activityIdentifier: "download"
    )
}

DynamicLake opens the sneak peek only when the activity is the one currently shown and was activated with supportsSneakPeek: true. DynamicLakeActivityCenter/closeSneakPeek(extensionBundleIdentifier:activityIdentifier:) closes it only if your activity's sneak peek is still open. Keep automatic sneak peeks short (one to three seconds) and use them only for moments the user should notice.

Preview the Sneak Peek

Add the sneak peek view to the same simulator provider as the compact activity.

func sneakPeekView(context: DynamicLakeSneakPeekContext<DynamicLakeEmptyLiveActivityState>) -> some View {
    ClockSneakPeekView(geometry: context.geometry)
}

Then preview both surfaces together.

DynamicLakeLiveActivitySimulator(
    provider: ClockLiveActivityProvider(),
    mode: .both,
    panelShape: .notch
)

Use .sneakPeek when you only need to inspect the expanded presentation.

Use State

Read state from the provider context when using a simulator or demo host.

struct ClockActivityState: DynamicLakeLiveActivityState {
    var dateText: String
}

func sneakPeekView(context: DynamicLakeSneakPeekContext<ClockActivityState>) -> some View {
    Text(context.state.dateText)
}

In a direct DynamicLakeSneakPeek scene, read updates with DynamicLakeActivityStateReader.

Topics

Layout

Presentation