Previewing Live Activity Views in SwiftUI Previews

I am curious if there is suggested guidance on how to create "mock" Live Activities/ActivityKit for the sake of developing Lock Screen/Dynamic Island views in SwiftUI and taking advantage of SwiftUI Previews.

For example, in the Display live data with Live Activities documentation, in the Create Lock Screen view section, demonstrates encapsulating the LockScreenActivityView in its own SwiftUI view. However, a subview of an ActivityConfiguration vends a generic context of type ActivityViewContext<MyActivityAttributes>, which does not seem to be able to be initialized directly.

This makes it difficult to use SwiftUI Previews for building the Live Activity views. If I try to add a SwiftUI Preview;

struct MyLockScreenLiveActivityView_Previews: PreviewProvider {

    static var previews: some View {
        MyLockScreenLiveActivityView(context: ...)
    }
}

I am unsure how I would define a context that I could pass into the preview, as trying to manually define something like let context = ActivityViewContext<MyActivityAttributes> does not yield any accessible initializers to construct a mock object that conforms to ActivityViewContext.

I might be missing something super simple, but would love any guidance, otherwise, I'm unable to use SwiftUI Previews for building the view.

Post not yet marked as solved Up vote post of brandonK212 Down vote post of brandonK212
3.2k views

Replies

@brandonK212,

The ActivityViewContext is the context object passed inside the ActivityConfiguration for building your view bodies. The preview for a live activity is very different from previewing a widget. You have to create your attributes set and then request a preview on that instead. Here is a minimally required code snip to get you started.

  1. Create custom Attributes
  2. Create a widget that uses an ActivityConfiguration of your custom attributes
  3. In your preview, instantiate an instance of your attributes and call .preview on it

As a note, you need your widget defined as main, or use a widget bundle. I originally missed this and nothing happened. Presumably, under the hood it will lookup which ActivityConfiguration to invoke, and thus which 'View' to finally instantiate for the preview.

https://developer.apple.com/documentation/widgetkit/activitypreviewviewkind

https://developer.apple.com/documentation/activitykit/activityattributes/previewcontext(_:isstale:viewkind:)

P.S. This is brand new with Xcode 14.2 and requires iOS 16.2 :)

@available(iOSApplicationExtension 16.1, *)
@main
struct LocationWidget: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: LocationAttributes.self) { context in
            Text("You're at \(context.attributes.name)")
        } dynamicIsland: { context in
            DynamicIsland {
                DynamicIslandExpandedRegion(.leading) {
                    Image(systemName: "mappin.circle")
                        .resizable()
                        .scaledToFit()
                        .frame(width: 32, height: 32)
                }
                
                DynamicIslandExpandedRegion(.center) {
                    VStack(alignment: .center) {
                        Text("You're visiting \(context.attributes.name)")
                        HStack {
                            Text("\(context.state.location.latitude), \(context.state.location.longitude)")
                        }
                    }
                }
                
                DynamicIslandExpandedRegion(.trailing) {
                    HStack {
                        Image(systemName: "hand.thumbsup.fill")
                            .resizable()
                            .scaledToFit()
                            .frame(width: 32, height: 32)
                        
                        Image(systemName: "hand.thumbsdown")
                            .resizable()
                            .scaledToFit()
                            .frame(width: 32, height: 32)
                    }
                }
                
                DynamicIslandExpandedRegion(.bottom) {
                    Text("Enjoy your visit!")
                }
            } compactLeading: {
                Text("Compact Leading")
            } compactTrailing: {
                Text("Compact Trailing")
            } minimal: {
                Text("Minimal")
            }
        }
    }
}

struct LocationActivityView: View {
    var body: some View {
        Text("Hello Location")
    }
}

struct LocationAttributes: ActivityAttributes {
    struct ContentState: Codable, Hashable {
        var location: Location
    }
    
    let name: String
}

struct Location: Codable, Hashable {
    let latitude: Double
    let longitude: Double
}

#if DEBUG

@available(iOSApplicationExtension 16.2, *)
struct LocationActivityView_Previews: PreviewProvider {
    
    static var previews: some View {
        Group {
            LocationAttributes(name: "London Eye")
                .previewContext(
                    LocationAttributes.ContentState(location: Location(latitude: 51.503351, longitude: -0.119623)),
                    viewKind: .content
                )
            
            LocationAttributes(name: "London Eye")
                .previewContext(
                    LocationAttributes.ContentState(location: Location(latitude: 51.503351, longitude: -0.119623)),
                    viewKind: .dynamicIsland(.expanded)
                )
        }
    }
}

#endif

I'm getting an error trying to attach the resulting preview screenshot but give it a run and check it out for yourself!