EasyApp

Dynamic Island

Learn how EasyAppSwiftUI builds Dynamic Island experiences

Overview

EasyAppWidgetLiveActivity leverages ActivityKit to deliver Lock Screen banners and Dynamic Island experiences. This guide covers how EasyAppWidgetAttributes and reusable components power the Dynamic Island layout and how to keep the main app and extension in sync.

Architecture

  • Widget configuration EasyAppWidgetLiveActivity declares an ActivityConfiguration, supplying views for both Lock Screen and dynamicIsland contexts.
  • View components Live Island regions live in EasyAppSwiftUI/EasyAppWidget/WidgetsComponents/LiveActivityComponents.swift.
  • Data model EasyAppWidgetAttributes and EasyAppWidgetAttributes.ContentState must remain identical between the main app and widget extension; a mismatch leads to decoding failures.

Key Structure

struct EasyAppWidgetLiveActivity: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: EasyAppWidgetAttributes.self) { context in
            LiveActivityLockScreen(
                attributes: context.attributes,
                state: context.state
            )
            .activityBackgroundTint(.clear)
            .activitySystemActionForegroundColor(.primary)

        } dynamicIsland: { context in
            DynamicIsland {
                DynamicIslandExpandedRegion(.leading) {
                    LiveActivityExpandedLeading(
                        attributes: context.attributes,
                        state: context.state
                    )
                }
                DynamicIslandExpandedRegion(.trailing) {
                    LiveActivityExpandedTrailing(
                        attributes: context.attributes,
                        state: context.state
                    )
                }
                DynamicIslandExpandedRegion(.bottom) {
                    LiveActivityExpandedBottom(
                        attributes: context.attributes,
                        state: context.state
                    )
                }
            } compactLeading: {
                LiveActivityCompactLeading(attributes: context.attributes, state: context.state)
            } compactTrailing: {
                LiveActivityCompactTrailing(attributes: context.attributes, state: context.state)
            } minimal: {
                LiveActivityMinimal(attributes: context.attributes, state: context.state)
            }
            .widgetURL(URL(string: "easyapp://live-activity?path=live-activity"))
            .keylineTint(.blue)
        }
    }
}
  • .widgetURL provides a tap target that can be replaced with your own deep link.
  • .keylineTint controls the keyline color; align this with brand accents.

Region Components

  • Expanded regions (LiveActivityExpandedLeading/Trailing/Bottom) present primary content, metrics, and actions; customize them using data from attributes and state.
  • Compact regions (LiveActivityCompactLeading/Trailing) condense information into icons and short labels for the collapsed island.
  • Minimal region (LiveActivityMinimal) uses an animated gradient background with the emoji for subtle status feedback.
  • Lock Screen banner (LiveActivityLockScreen) shares the same attributes and offers space for additional action cards.

Data & Updates

  • EasyAppWidgetAttributes describes static details such as the user name or task label.
  • EasyAppWidgetAttributes.ContentState carries live fields (emoji, progress). Update the state via Activity.update(using:) to refresh the island.
  • Keep the following files synchronized across targets:
    • EasyAppSwiftUI/Common/EasyAppWidgetAttributes.swift
    • EasyAppWidget/Common/EasyAppWidgetAttributes.swift Consistency is critical; the Live Activity will fail if either side changes fields without matching the other (see previous fix experience).

Launch & Debug

  1. Request a Live Activity from the main app:
    let activity = try Activity<EasyAppWidgetAttributes>.request(
        attributes: EasyAppWidgetAttributes(name: "World"),
        contentState: EasyAppWidgetAttributes.ContentState(emoji: "😀"),
        pushType: nil
    )
  2. Run the EasyAppWidgetExtension scheme on hardware or a simulator that supports Dynamic Island (iPhone 14 Pro or later).
  3. Use #Preview blocks with .dynamicIsland(.compact/.expanded/.minimal) in Xcode to verify layouts.
  4. Call activity.update(using:) to push state changes and observe Dynamic Island animations.

Design Tips

  • Follow Human Interface Guidelines: keep compact labels to ~6–8 characters to prevent clipping.
  • Use helpers like AnimatedProgressBar and PulsingStatusIndicator to show real-time metrics.
  • Apply .buttonStyle(PlainButtonStyle()) to action buttons so Dynamic Island commands blend with the glassmorphic look.
  • Prepare localized strings in EasyAppWidgetAttributes or via LocalizedStringResource when supporting multiple locales.

Troubleshooting

  • Live Activity fails to start: ensure NSSupportsLiveActivities = true is set in the main app Info.plist and test on real devices when possible.
  • Decoding errors: confirm EasyAppWidgetAttributes.ContentState matches between app and extension (past issues arose from mismatched fields).
  • Missing actions: verify DynamicIslandExpandedRegion closures actually return the desired buttons and that they trigger logic.
  • Preview build failures: supply mock data or guard runtime-only code with #if DEBUG.

Refer to the multiple #Preview blocks at the bottom of LiveActivityComponents.swift for compact, expanded, and Lock Screen samples.

Last updated on