swiftuiuikitswift5widgetliveactivitydynamic-island

For a Live Activity, how can I put my widget bundle WidgetBundle "in" a Target


I have a UIKit app (it perfectly handles and does APNS), I add an ActivityAttributes and thus launch an activity.

So that's TestAttributes;

notice that is used in the Widget TestWidg;

and is used as the content in the Activity#request.

Gotta get that "in" the target somehow so I put it in a TestWidgBundle, all code below.

This all runs perfectly and when you lactivities.test() . You get a token et al,

** check true
** activity ID woot 0384DF90-795E-487A-ADAC-185203E25F22
** traverse ok
** token woot gPk5RJkfAi/dCrZG4GVc7/4E48uQ...Po3vz+9jKcD4C4qjg+M=

Naturally I

example image shows info.plist settings

etc etc.

BUT the live activity never appears.

Surely, I'm doing something conceptually wrong in that I have not actually put the Widget / WidgetBundle in the project - ???

ie the project in question has only one Target, the main iOS build.

I have run other projects (eg test projects from the web) on my phones, sims, which clearly show Live Activities / Happy Island appearing correctly.

Does struct TestWidgBundle: WidgetBundle need something more than it's declaration to add the new target, or ?

import UIKit
import ActivityKit
import WidgetKit
import SwiftUI

/// singleton for Live Activity Feat. "dynamic island" etc
let lactivities = Lactivities.shared

/// access using `lactivities.`
final class Lactivities: NSObject {
    static let shared = Lactivities()
    private override init() { super.init() }
    
    ///Give it a go ...
    func test() {
        print("** check", ActivityAuthorizationInfo().areActivitiesEnabled )
        let att = TestAttributes(name: "test")
        let cs = TestAttributes.ContentState(news: "test")
        let cont = ActivityContent.init(state: cs, staleDate: nil, relevanceScore: 1.0)
        do {
            let activity = try Activity.request(attributes: att, content: cont, pushType: .token)
            print("** activity ID woot", activity.id)
            Task {
                for await pushToken in activity.pushTokenUpdates {
                    print("** token woot", pushToken.base64EncodedString()
                    )
                }
            }
        }
        catch let error { print("** lactivity ERR!!!!", error) }
        print("** traverse ok")
    }
}

.. and the structs ..

struct TestAttributes: ActivityAttributes {
    public struct ContentState: Codable, Hashable {
        var news: String
    }
    var name: String
}

struct TestWidg: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: TestAttributes.self) { context in
            SimpleView(state: context.state, stuff: context.attributes)
        } dynamicIsland: { context in
            DynamicIsland { DynamicIslandExpandedRegion(.bottom) {
                    VStack { Text(context.attributes.name) } }
            } compactLeading: { Text(context.attributes.name)
            } compactTrailing: { Image(systemName: "testtube.2")
            } minimal: { Image(systemName: "testtube.2")
            }
        }
    }
}

struct SimpleView: View {
    let state: TestAttributes.ContentState
    let stuff: TestAttributes
    var body: some View {
        VStack {
            Text(state.news)
            Text(stuff.name)
        }
    }
}

and here it is! ...

///Here it is!
struct TestWidgBundle: WidgetBundle {
    var body: some Widget {
        TestWidg()
    }
}
// end of file

Solution

  • It's possible this info may help people googling here. I really can't see how to "do it in code" but as a workaround,

    (And you have to manually update CFBundleVersion in the new target you made.)

    If you do these things it would seem that the sample code in the question, for example, will work.