I'm using Live Activity features in my app, but I want to customize the user experience across different Apple devices. Specifically, I'd like to:
Keep Live Activity enabled and functioning on the iPhone Disable or prevent Live Activity from appearing on the connected Apple Watch
Is this level of device-specific control possible with Live Activity? If so, what's the best approach to implement this functionality? What I've tried:
I've looked through Apple's documentation on Live Activity, but couldn't find specific information about device-level control. I've experimented with ActivityKit, but haven't found a clear way to distinguish between iPhone and Apple Watch when pushing updates.
ActivityKit
RSS for tagHelp people keep track of tasks and events that they care about with Live Activities on the Lock Screen, the Dynamic Island, and in StandBy.
Posts under ActivityKit tag
111 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
I want to add Live Activities to my e-commerce app to show the estimated delivery ETA when an order is out for delivery, with the Live Activity initiated from the server.
Issue 1: How can I consistently obtain a pushToStart token when a user logs in to my e-commerce app? Currently, I’m considering starting and ending a dummy Live Activity to retrieve the token. Is there a better way to trigger pushToStartTokenUpdates and send the token to my server?
Issue 2: How do I properly invalidate the pushToStart token when a user logs out, ensuring the next user doesn’t inherit the same token? Ideally, I don't want the user to wait until the token is automatically invalidated before logging out.
In the app's Live Activity, Text (timerInterval:) is well displayed on Dynamic Island, but there seems to be a bug in Live Activity (Smart Stack in watchOS) that automatically transfers from iOS 18 to watchOS 11. Does anyone know about this?
I have defined a colour in the asset catalog of my widget extension. I am using this widget extension for my live activity. This colour has both an ‘Any’ and ‘Dark’ appearance. I am using this colour in the activityBackgroundTint modifier for my main live activity view (the ActivityPreviewViewKind is content, visible on the Lock Screen). In light mode, this works as expected, but in dark mode, the background colour is still the light mode variant. The content within the view changes according to whether the system is in light or dark mode, but the background does not.
I want to avoid using just the background modifier instead of activityBackgroundTint, as this does not colour the ‘Allow Live Activities from …’ and ‘Clear’ sections, which is undesirable.
I have also tried reading colorScheme from the environment and choosing the light or dark mode colour variant accordingly, but this workaround also doesn’t completely work. If you keep changing from light and dark mode (via the Settings app, not the control centre, for whatever reason), the background colour actually becomes the opposite of the expected value in this case.
Is there a workaround that can help me achieve what I want, or am I simply doing something wrong?
I have filed a bug report under FB15148099
I have a Live Activity with a button that updates a SwiftData model. This used to work in iOS 17, but not on iOS 18. The reason is that in iOS 17, when you run an AppIntent from a Live Activity, the perform() method would run in the main app's process, meaning it had access to the app's ModelContainer/ModelContext. However, in iOS 18, this is no longer the case, and the perform() method of an AppIntent now runs in the extension's process.
While I can still construct a new ModelContainer & ModelContext in the AppIntent's perform() method, the main app's container and context will not see these changes until the app is relaunched.
How can I make this work in iOS 18 now that an AppIntent executed from an extension runs in a different process from the main app?
I have a button on a live activity, and I want to end the live activity and update the Control Center widgets and home screen widgets after clicking this button. So, in the perform() method of the button's LiveActivityIntent, I called the methods ControlCenter.shared.reloadAllControls() and WidgetCenter.shared.reloadAllTimelines(), but they didn't work. Home screen widgets and Control Center widgets do not refresh, resulting in a poor user experience.
I have an app that starts a Live Activity on a certain user action. This Live Activity contains a button that the user can tap, which updates a SwiftData model instance. However, when you return to the main app after tapping the button on the Live Activity, the views do not update to reflect the changes, even though the changes were written to the database.
The underlying issue here is that the ModelContainer/ModelContext used by the AppIntent (performed from the LiveActivity when the button is tapped), are different from the instances in the main app. Meaning that while the changes are written to the underlying storage, the in-memory instances of ModelContext/ModelContainer in the main app don't get the changes from the extension, so SwiftUI doesn't update either.
What is the recommended way to handle this scenario? Or is there one? :) Shared access to a SwiftData container is clearly supported through App Groups, so is there not a mechanism to ensure changes made by an extension are updated in real-time for the main app?
Otherwise, it seems I would have to go through and manually rerun queries that views depend on to make sure they are showing the most recent data. This is cumbersome and error-prone.
Perhaps I'm missing something? Any suggestions would be greatly appreciated.
Hi, I'm updating our app to use Xcode 16 and Swift 6 language mode. I'm stuck on updating our live activity code. I looked at the Emoji Rangers sample project and after switching it to Swft 6 mode, it has the exact same errors as our project.
The main problem seems to be that Activity is not Sendable, which prevents us from passing it to child tasks to await things like activityStateUpdates and contentUpdates (those are also not Sendable).
Is there any guidance on this? Updated sample code? Another project?
The documentation doesn't state how to get the new APNs push token that is associated with a Live Activity that is started via an APNs push notification. I have been able to get the new token by listening to all instances of the Live Activities for my attribute type, but it makes me wonder how the "input-push-token" option for iOS 18 is supposed to work.
Is there another way to retrieve the newly created Live Activity push token when using "input-push-token"?
Hello, we have an app that has a case where the user can turn on a feature that starts a timer for a thing when they arrive at a specific location.
Our app also has a live activity to show the timer.
Naturally, we're trying to make our live activity to start counting when the geofence triggers, but we get ActivityAuthorizationError.visibility. If an activity is already running, it's possible to turn it off.
So, our question is basically if there's any way to make the geofence trigger start our live activity?
Thanks
We are facing a serious issue affecting the Live Activity of the our app. If the app is built with Xcode 16 (16A242) the Live Activity does not appear on devices running iOS 17. The live activity does appear on devices running iOS 18 RC.
If we build the code with Xcode 15, the Live Activity appears on iOS 17 as expected.
To investigate this we also prepared a demo bare-bones project to make sure the Live Activity presence is not affected by part of the existing code. Again the issue appears even of the demo app.
Is this a known issue with Xcode16/iOS 18?
I can successfully start a Live Activity using push-to-start token, but in order to update the Live Activity i need another token as stated in the docs:
While the system starts the new Live Activity and wakes up your app, you receive the push token you use for updates.
When the app is open, i can retrieve this update token and successfully update the Live Activity using this example code:
func observeActivityPushToken() {
Task {
for await activityData in Activity<LiveActivityAttributes>.activityUpdates {
Task {
for await tokenData in activityData.pushTokenUpdates {
let token = tokenData.map { String(format: "%02x", $0) }.joined()
print("Push token: \(token)")
}
}
}
}
}
But when the app is closed, how/where do i manage to get this update token and send it to the server "when the system wakes up the app"?
Somewhat rarely, but often enough to be a problem, our live activities will entirely lock up with a frozen progress spinner and a dimmed appearance. We cannot figure out what this spinner means nor how to avoid it as it is not documented anywhere. No logs appear in Xcode nor in Console.app. We have more frequent updates enabled and are sending an update maybe once every 2 minutes, nothing that should cause us to exceed our quota. We cannot reproduce in a debugger
Can we get some guidance on how to avoid this?
Hello. I am working with the iOS 18b8 and Xcode 16b6 betas, updating a Live Activity Widget to adopt the new FormatStyle variants of the SwiftUI Text views.
We used to use:
Text(workoutStartDate, style: .relative)
and it seems with iOS 18 we can replace it with:
Text(workoutStartDate, format: .relative(presentation: .numeric, unitsStyle: .wide))
The former code would auto-increment, allowing a user to look at their Live Activity and see the duration of their workout so far ticking by. The new code does provide a nice relative duration string, but it doesn't auto-increment in the Live Activity's View – I need that functionality.
I also updated other Texts in the Live Activity's View to adopt the .timer and .stopwatch FormatStyles. Those auto-increment and update no problem – once I realized I needed to provide a TimeDataSource<Date>.currentDate and not a simple Date.
But in this .relative case, there is no auto-incrementing, and I'm guessing it may be due to providing a Date (workoutStartDate) and not a TimeDataSource<Date> as TimeDataSource seems to be the magic to make the Text auto-increment (or it's possible this format simply doesn't support auto-increment? bummer if so since the prior way did).
How can I have a TimeDataSource<Date> that vends my arbitrary date (workoutStartDate)?
I dug around, didn't find anything, but I wouldn't be surprised if I was overlooking something.
PS: I have tried other approaches to solve this, such as simply using a .timer or .stopwatch format. That would change the under experience under iOS 18, and we think degrade it (the relative textual representation is nicer and provides distinction from the .timer also in the same View). We could keep the former approach, despite the minor layout issues under iOS 18. I have considered additional approaches as well – and am totally open to more! Here tho, I am truly curious how one might provide a custom TimeDataSource anchored at a date we provide (perhaps dynamically). Thank you.
Live Activity works only when the device is unlocked and open the app is the app live activity can start from remote via push notification
Hi,
We’ve developed a workout app with a Live Activity feature to help users launch the mirroring view on iPhone, similar to the built-in workout app for biking activities.
While Live Activities are now available on watchOS 11, the integration feels a bit off for our Workout app. Is there a way to disable or exclude our Live Activity from appearing on watchOS?
Currently, when a user starts a workout, the Live Activity appears at the bottom of the screen, requiring users to tap the screen before they can use our app. The built-in Workout app doesn’t have this issue.
Additionally, our Live Activity appears in the Smart Stack, duplicating content with the built-in Workout Live Activity.
We’re unsure if we missed any keys or settings to exclude Live Activity from watchOS.
The Live Activity work well in my iPhone13 & iPhone14, but nothing happened in the lock screen after I activate the Live Activity in the iPhone12, ran iOS16.7.2, with all the permission are approved.
Active
@objc func onOpen() {
guard ActivityAuthorizationInfo().areActivitiesEnabled else {
return
}
do {
let attributes = LiveActivityWidgetAttributes(location: "OF01 Apartments", machine: "DS 12")
let content = actvityContent(progress: 0, washingTime: Int(self.runningTime), isDone: false)
self.liveActivity = try Activity<LiveActivityWidgetAttributes>.request(attributes: attributes, content: content)
self.initialTimeStamp = Int(Date().timeIntervalSince1970)
self.activeTimer()
self.lastTimeStamp = Int(Date().timeIntervalSince1970)
} catch {
debugPrint("failed open")
}
}
Update
@objc func onUpdate() {
Task {
let current = Int(Date().timeIntervalSince1970)
let passed = current - self.initialTimeStamp
let pgs = (Double(passed) / 60.0 / self.runningTime) > 1 ? 1 : Double(passed) / 60.0 / self.runningTime
let remainingMnts = Int(ceil((self.runningTime * 60.0 - Double(passed)) / 60))
self.progress = ceil(Double(passed) / self.timerStep)
let content = actvityContent(progress: pgs, washingTime: remainingMnts, isDone: (pgs == 1))
await liveActivity?.update(content)
}
}
OK, so I'm trying to share some utils classes for logging. The problem is a use-case where I want to create a debug notification.
However, inside the app, I want to show a popup instead if the app is showing, but I can't share that code because it uses UIApplication.shared.ApplicationState.
I've tried gating it off with all sorts of methods, @available etc. but I get compilation error "unavailable for application extensions"
Example of me trying:
static func doNotification(_ header: String, message: String) {
//so I'm trying to gate off the code that only can be called in an extension , and in addition I have @available below
if(isAppExtension()){
doNotificationFromExtension(header, message: message)
}else{
doNotificationFromApp(header, message: message)
}
}
@available(iOSApplicationExtension, unavailable)
static func doNotificationFromApp(_ header: String, message: String) {
let state = UIApplication.shared.applicationState
if state != .active {
//my dialog handler in-app popup
}else{
NotifUtils.createLocalNotification(message, header: header, category: NOTIFICATION_CATEGORY_DEBUG, playSound: false)
}
}
//here I know that I'm in an extension, so app isn't showing - always local notification
static func doNotificationFromExtension(_ header: String, message: String) {
NotifUtils.createLocalNotification(message, header: header, category: NOTIFICATION_CATEGORY_DEBUG, playSound: false)
}
static func isAppExtension() -> Bool {
return Bundle.main.executablePath?.contains(".appex/") ?? false
}
Is there any way to share the code like this? The reason I want to do this is because I have various live activity code that I'd want to re-use, but this is a show.-stopper.
I am working on a live activity with a button that will do a network call when pressed on.
I want to make sure that it can only be pressed once, and then ignored while the request is processed.
This is what I did:
Since it seems that a new intent object is created for each button press, I am using a static synchroniser like this:
private var _isBusy: Bool = false
private var _timestamp: Date? = nil
private let queue: DispatchQueue
private let resetInterval: TimeInterval
init(resetInterval: TimeInterval = 60, queueLabel: String = "default.synchronizedBoolQueue") {
self.resetInterval = resetInterval
self.queue = DispatchQueue(label: queueLabel)
}
var isBusy: Bool {
get {
return queue.sync {
return _isBusy
}
}
set {
queue.sync {
_isBusy = newValue
}
}
}
func setIfNotBusy() -> Bool {
return queue.sync {
let now = Date()
if let timestamp = _timestamp {
if now.timeIntervalSince(timestamp) > resetInterval {
// Reset if it was more than the specified interval ago
_isBusy = false
_timestamp = nil
}
}
if !_isBusy {
_isBusy = true
_timestamp = now
return true
}
return false
}
}
func clearBusy() {
queue.sync {
_isBusy = false
_timestamp = nil
}
}
}
Then, in my intent I have:
private static let synchronizedBoolean = SynchronizedBoolean(queueLabel: "myIntent")
...
func perform() async throws -> some IntentResult {
NSLog("---LIVE perform() called")
if(!UserEventIntent.synchronizedBoolean.setIfNotBusy()){
NSLog("---LIVE Was already in progress!")
}else{
//doing intent logic here
UserEventIntent.synchronizedBoolean.clearBusy()
}
}
I am pretty new to Swift, so my hope is that someone more experienced than me could tell me if this a reasonable approach or if I'm missing anything?
A big question - let's say I go into the perform() method, and while I'm in there, the user presses the button and the intent is called again. I get the "already in progress", but the method must still provide a result. Will that effect the intent already in progress?
Thoughts much appreciated.
Hello,
I get this error:
How can I fix it and hope you can help me.
greetings Fabian
SendProcessControlEvent:toPid: encountered an error: Error Domain=com.apple.dt.deviceprocesscontrolservice Code=8 "Failed to show Widget 'de.polygondata.BikeComputer.BikeComputerWidget' error: Error Domain=FBSOpenApplicationServiceErrorDomain Code=1 "The request to open "com.apple.springboard" failed." UserInfo={NSLocalizedFailureReason=The request was denied by service delegate (SBMainWorkspace)., BSErrorCodeDescription=RequestDenied, NSUnderlyingError=0x600000c37150 {Error Domain=SBAvocadoDebuggingControllerErrorDomain Code=1 "Failed to get descriptors for extensionBundleID (de.polygondata.BikeComputer.BikeComputerWidget)" UserInfo={NSLocalizedDescription=Failed to get descriptors for extensionBundleID (de.polygondata.BikeComputer.BikeComputerWidget)}}, FBSOpenApplicationRequestID=0xbf3b, NSLocalizedDescription=The request to open "com.apple.springboard" failed.}." UserInfo={NSLocalizedDescription=Failed to show Widget 'de.polygondata.BikeComputer.BikeComputerWidget' error: Error Domain=FBSOpenApplicationServiceErrorDomain Code=1 "The request to open "com.apple.springboard" failed." UserInfo={NSLocalizedFailureReason=The request was denied by service delegate (SBMainWorkspace)., BSErrorCodeDescription=RequestDenied, NSUnderlyingError=0x600000c37150 {Error Domain=SBAvocadoDebuggingControllerErrorDomain Code=1 "Failed to get descriptors for extensionBundleID (de.polygondata.BikeComputer.BikeComputerWidget)" UserInfo={NSLocalizedDescription=Failed to get descriptors for extensionBundleID (de.polygondata.BikeComputer.BikeComputerWidget)}}, FBSOpenApplicationRequestID=0xbf3b, NSLocalizedDescription=The request to open "com.apple.springboard" failed.}., NSUnderlyingError=0x600000c36e50 {Error Domain=FBSOpenApplicationServiceErrorDomain Code=1 "The request to open "com.apple.springboard" failed." UserInfo={NSLocalizedFailureReason=The request was denied by service delegate (SBMainWorkspace)., BSErrorCodeDescription=RequestDenied, NSUnderlyingError=0x600000c37150 {Error Domain=SBAvocadoDebuggingControllerErrorDomain Code=1 "Failed to get descriptors for extensionBundleID (de.polygondata.BikeComputer.BikeComputerWidget)" UserInfo={NSLocalizedDescription=Failed to get descriptors for extensionBundleID (de.polygondata.BikeComputer.BikeComputerWidget)}}, FBSOpenApplicationRequestID=0xbf3b, NSLocalizedDescription=The request to open "com.apple.springboard" failed.}}}
Domain: DTXMessage
Code: 1
User Info: {
DVTErrorCreationDateKey = "2024-08-16 17:14:32 +0000";
}
SendProcessControlEvent:toPid: encountered an error: Error Domain=com.apple.dt.deviceprocesscontrolservice Code=8 "Failed to show Widget 'de.polygondata.BikeComputer.BikeComputerWidget' error: Error Domain=FBSOpenApplicationServiceErrorDomain Code=1 "The request to open "com.apple.springboard" failed." UserInfo={NSLocalizedFailureReason=The request was denied by service delegate (SBMainWorkspace)., BSErrorCodeDescription=RequestDenied, NSUnderlyingError=0x600000c37150 {Error Domain=SBAvocadoDebuggingControllerErrorDomain Code=1 "Failed to get descriptors for extensionBundleID (de.polygondata.BikeComputer.BikeComputerWidget)" UserInfo={NSLocalizedDescription=Failed to get descriptors for extensionBundleID (de.polygondata.BikeComputer.BikeComputerWidget)}}, FBSOpenApplicationRequestID=0xbf3b, NSLocalizedDescription=The request to open "com.apple.springboard" failed.}." UserInfo={NSLocalizedDescription=Failed to show Widget 'de.polygondata.BikeComputer.BikeComputerWidget' error: Error Domain=FBSOpenApplicationServiceErrorDomain Code=1 "The request to open "com.apple.springboard" failed." UserInfo={NSLocalizedFailureReason=The request was denied by service delegate (SBMainWorkspace)., BSErrorCodeDescription=RequestDenied, NSUnderlyingError=0x600000c37150 {Error Domain=SBAvocadoDebuggingControllerErrorDomain Code=1 "Failed to get descriptors for extensionBundleID (de.polygondata.BikeComputer.BikeComputerWidget)" UserInfo={NSLocalizedDescription=Failed to get descriptors for extensionBundleID (de.polygondata.BikeComputer.BikeComputerWidget)}}, FBSOpenApplicationRequestID=0xbf3b, NSLocalizedDescription=The request to open "com.apple.springboard" failed.}., NSUnderlyingError=0x600000c36e50 {Error Domain=FBSOpenApplicationServiceErrorDomain Code=1 "The request to open "com.apple.springboard" failed." UserInfo={NSLocalizedFailureReason=The request was denied by service delegate (SBMainWorkspace)., BSErrorCodeDescription=RequestDenied, NSUnderlyingError=0x600000c37150 {Error Domain=SBAvocadoDebuggingControllerErrorDomain Code=1 "Failed to get descriptors for extensionBundleID (de.polygondata.BikeComputer.BikeComputerWidget)" UserInfo={NSLocalizedDescription=Failed to get descriptors for extensionBundleID (de.polygondata.BikeComputer.BikeComputerWidget)}}, FBSOpenApplicationRequestID=0xbf3b, NSLocalizedDescription=The request to open "com.apple.springboard" failed.}}}
Domain: DTXMessage
Code: 1