Blog

OpenSwiftUI Project

  • OS: AppleOS

Preview Dynamic Island presentations in your app with JindoKit

JindoKit brings WidgetKit-style Dynamic Island presentations into regular SwiftUI and OpenSwiftUI views, so apps can preview and configure Live Activity content in place.

JindoKit example app showing expanded, compact, and minimal Dynamic Island presentations on an iPhone simulator
The JindoKit example renders all three presentations in the app and can start the matching WidgetKit Live Activity for comparison.

WidgetKit previews are useful while building a Live Activity, but they stay in Xcode. An app that lets people choose a theme, inspect live data, or configure a presentation needs the same content to render inside its own interface.

JindoKit brings WidgetKit-style Dynamic Island presentations into a regular View. The API closely follows WidgetKit's Dynamic Island vocabulary, so leading, trailing, center, bottom, compact, and minimal content can be composed in the same familiar shape.

Add JindoKit

Add the package:

.package(
    url: "https://github.com/OpenSwiftUIProject/JindoKit.git",
    from: "0.2.0"
)

Then add the JindoKit product to your application target:

.product(name: "JindoKit", package: "JindoKit")

JindoKit currently requires a Swift 6.3-capable toolchain.

Build an in-app preview

Create the presentation just as you would describe the regions of a Dynamic Island:

import JindoKit
import SwiftUI

struct RunIslandPreview: View {
    let mode: DynamicIslandPreviewMode = .expanded

    var body: some View {
        DynamicIsland {
            DynamicIslandExpandedRegion(.leading) {
                Text("12 min")
            }
            DynamicIslandExpandedRegion(.trailing) {
                Image(systemName: "figure.run")
            }
            DynamicIslandExpandedRegion(.bottom) {
                Text("Running to the finish")
            }
        } compactLeading: {
            Text("12")
        } compactTrailing: {
            Image(systemName: "figure.run")
        } minimal: {
            Image(systemName: "figure.run")
        }
        .contentMargins(.all, 20, for: .expanded)
        .previewMode(mode)
    }
}

The expanded presentation is the default. Pass .compact or .minimal to previewMode(_:) to render another presentation, then position the resulting view with ordinary layout. Because the result is a normal view, application state can drive the preview immediately while the user changes its content or configuration.

Choose the rendering backend

The default SwiftUI_NO_SPI backend uses SwiftUI with JindoKit's open-source Jindo layout, implemented through SwiftUI's public Layout API. It neither imports SwiftUI_SPI nor enables JindoKit's conditional SPI build flags.

The four backend traits are mutually exclusive. Omit traits to use SwiftUI_NO_SPI, or select exactly one of the other three.

To compare the result with the system implementation during development, enable the SwiftUI_SPI package trait:

.package(
    url: "https://github.com/OpenSwiftUIProject/JindoKit.git",
    from: "0.2.0",
    traits: ["SwiftUI_SPI"]
)

That configuration compiles against JindoKit's bundled SwiftUI_SPI interface and resolves the private Jindo symbols from the system SwiftUI framework. It is intended only for local compatibility testing, not App Store distribution.

OpenSwiftUI applications can instead enable the OpenSwiftUI_NO_SPI package trait:

.package(
    url: "https://github.com/OpenSwiftUIProject/JindoKit.git",
    from: "0.2.0",
    traits: ["OpenSwiftUI_NO_SPI"]
)

Then import OpenSwiftUI instead of SwiftUI. The JindoKit call site remains the same. OpenSwiftUI_NO_SPI produces OpenSwiftUI views with JindoKit's open-source layout; select OpenSwiftUI_SPI only to compare with the layout exposed by that SPI module.

Both OpenSwiftUI backends require iOS 18 or later. Even OpenSwiftUI_NO_SPI inherits OpenSwiftUI's runtime dependencies, so neither OpenSwiftUI backend should be treated as App Store-safe.

Preview and Live Activity side by side

JindoKit renders an in-app preview; it does not replace ActivityKit or start a system Live Activity. A production app still uses ActivityKit to request the activity and a WidgetKit extension to provide its system presentation. Shared child views and presentation values can feed both paths, giving the configuration screen and the system presentation a common source of truth.

The repository's example app demonstrates that arrangement. It displays expanded, compact, and minimal JindoKit previews in the app, embeds a WidgetKit extension, and starts the real Live Activity for comparison.