frequentPushEnablementUpdates asynchronous sequence is never called even if 'More Frequent Updates' is toggled ON or OFF.
for await frequentPushEnabled in ActivityAuthorizationInfo().frequentPushEnablementUpdates {
// never called
}
Though we are able to get the 'More Frequent Updates' value once by the following:
var isEnabled = ActivityAuthorizationInfo().frequentPushesEnabled //true if ON, false if OFF
This only gives the result once as it is not async observation sequence.
But the 'frequentPushEnablementUpdates' async sequence is never called. As per the doc - 'frequentPushEnablementUpdates' is an asynchronous sequence you use to observe whether a person permitted you to update Live Activities with frequent ActivityKit push notifications.
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
112 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
Our context involves smart kitchen appliances, where cooking may be initiated by an app or directly by the device.
When the app is not running, we can only start a Live Activity through a remote push notification. However, an increasing number of users report issues where they cannot update or terminate the Live Activity.
While we can reproduce this issue in some cases, it is inconsistent and lacks a clear pattern.
I have a sample project and would like to confirm the following questions:
When the app is not running, does each pushToStartToken update wake the app and reliably trigger the callback below?
for await pushToken in Activity<DeviceAttributes>.pushToStartTokenUpdates {
}
When the app is not running, does each pushTokenUpdates update wake the app and reliably trigger the callback below?
Task {
for await activity in Activity<DeviceAttributes>.activityUpdates {
Task {
for try await tokenData in activity.pushTokenUpdates {
}
}
}
}
Must pushToStartTokenUpdates and pushTokenUpdates be placed directly in application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?), or can they be in another wrapper, such as an RxSwift wrapper?
If pushTokenUpdates is updated, but the received pushToken fails to synchronize to the server due to network issues, how should this be handled?
Alternatively, if you have any better suggestions, I would be very grateful to hear them.
Here is a simple example.
Hello everyone,
I’m currently receiving feedback from clients in a production environment who are encountering a BadDeviceToken error with Live Activities, which is preventing their states from updating. However, for other clients, the token is working fine and everything functions as expected.
I’m collaborating with the back-end developers to gather more information about this issue, but the only log message we’re seeing is:
Failed to send a push, APNS reported an error: BadDeviceToken
I would greatly appreciate it if anyone could provide some insight or information on how to resolve this issue.
Hey there,
Create Live activity in my project, after executing the creation method, back to the background, real-time activity UI is not displayed, but click Smart Island is effective,
now do not know where the problem appears?
Has anyone experienced this problem?
Thanks
I'm unable to find sample code that demonstrates how to support a custom Live Activity layout for Apple Watch. I have read the documentation and have added the supplementalActivityFamilies with small and medium. However, I am not able to detect when the activityFamily is set to small.
This is what I'm trying to use without success:
struct MyWidgetLiveActivity: Widget {
@Environment(\.activityFamily) var activityFamily: ActivityFamily
var body: some WidgetConfiguration {
ActivityConfiguration(for: MyWidgetAttributes.self) { context in
if activityFamily == .small {
Text("Apple Watch! \(activityFamily.description)")
} else {
Text("Not small family: \(activityFamily.description)")
}
} dynamicIsland: { context in
return DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
Text("Leading")
}
DynamicIslandExpandedRegion(.trailing) {
Text("Trailing")
}
DynamicIslandExpandedRegion(.bottom) {
Text("Bottom")
}
} compactLeading: {
Text("CL")
} compactTrailing: {
Text("CT")
} minimal: {
Text("M")
}
}
.supplementalActivityFamilies([.small, .medium])
}
}
This code shows "Not small family: medium" on my Apple Watch. Could somebody provide some insight into why this doesn't work for me?
Greetings,
i have been watched the video and looked for codes to apply live activity for watch.
problem is in preview always show my widget as medium size even if apply supporting widget family and changing the content of preview to dynamicIsland .compact
however preview still shows me the medium size everytime. what is wrong ?
https://developer.apple.com/documentation/WidgetKit/Adding-interactivity-to-widgets-and-Live-Activities#Add-a-toggle
Before explaining the situation, I referred to this document.
I'm making a widget with a toggle that works with Intent. (To be precise, it's Live Activity, but Apple's literally toggling and button interaction are implemented in the same way)
If you press the toggle when the device is locked, the toggle represents the state.
However, as described at the top of the same document, the device must be unlocked to execute Intent, so I can't actually change the data.
That's fine, but how can I return the isOn of the toggle to false? I'm blocked here because no code is executed.
If it is a widget in the home screen, it will be basically unlocked, so I can proceed according to the method of the document, but if it is today's view, user can access it even if device is locked.
I tested it in today's view with the Reminders app, If I don't unlock the device after tapping the toggle it returns the toggle back to false state.
I wonder how I can achieve this.
Is there an API that I missed?
Summary.
Tap the toggle of the widget while the device is locked.
The device waits for unlocking, but the user cancels it without unlocking it. (Still locked)
Return the isOn of the toggle to false. <- The part I want.
Hi,
I'm a bit stuck when it comes to implementing live activities.
I'm trying to start one by sending a push.
my activity attributes looks like:
import Foundation
import ActivityKit
public struct NeoPrototypeActivity: ActivityAttributes {
public let classId: String
public let name: String
public let room: String
public let startTime: Date
public init(classId: String, name: String, room: String, startTime: Date) {
self.classId = classId
self.name = name
self.room = room
self.startTime = startTime
}
public struct ContentState: Codable & Hashable {
public let participationState: ParticipationState
public init(participationState: ParticipationState) {
self.participationState = participationState
}
}
}
public enum ParticipationState: String, Codable, Hashable, Equatable {
case upcoming
case upcomingOnWaitingList
case checkedIn
case waitingList
case lostSpot
case lostSpotFromWaitingList
case classCompleted
public var description: String {
switch self {
case .upcoming: return "Upcoming"
case .upcomingOnWaitingList: return "Upcoming on Waiting List"
case .checkedIn: return "Checked In"
case .waitingList: return "Waiting List"
case .lostSpot: return "Lost Spot"
case .lostSpotFromWaitingList: return "Lost Spot from Waiting List"
case .classCompleted: return "Class completed"
}
}
}
which I have in a SPM package that is imported by my widget and iOS targets.
Then I am sending a push notification (to my simulator in this case) with the following payload
{
"aps": {
"timestamp": 1728419211,
"event": "start",
"content-state": {
"participationState": "upcoming"
},
"attributes-type": "NeoPrototypeActivity",
"attributes": {
"classId": "1234",
"name": "Indoor Running",
"room": "room 2",
"startTime": "2024-10-19T13:22:59+02:00"
},
"alert": {
"title": "Hola Mundo",
"body": "Todo Bien"
}
}
}
I am using the right values for the deviceID when sending the push.
I get a 200 ok from the CloudKit console.
yet my live activity doesn't start.
I'm trying to look at the errors in the Console app and the only relevant thing is see is:
[NeoPrototypeActivity] Error creating activity: NSCocoaErrorDomain (4864) The data couldn’t be read because it isn’t in the correct format.
It seems like a decoding error, but I don't know what exactly failed
I wrote some test to try the decoding like:
import Testing
import Foundation
@testable import PrototypeActivities
@Test func decoding() async throws {
struct StateContainer: Codable {
let participationState: ParticipationState
}
let jsonString = """
{
"participationState": "upcoming"
}
"""
let json = try #require(jsonString.data(using: .utf8))
let result = try JSONDecoder().decode(StateContainer.self, from: json)
#expect(result.participationState == .upcoming)
}
@Test func decodingActivity() throws {
// let jsonString = """
// {
// "classId": "1234",
// "name": "Indoor Running",
// "room": "room 2",
// "startTime": "2024-10-19T11:22:59Z"
// }
// """
let jsonString = """
{
"classId": "1234",
"name": "Indoor Running",
"room": "room 2",
"startTime": 1729337784
}
"""
let json = try #require(jsonString.data(using: .utf8))
let jsonDecoder = JSONDecoder()
// jsonDecoder.dateDecodingStrategy = .iso8601
let activity = try jsonDecoder.decode(NeoPrototypeActivity.self, from: json)
#expect(activity.classId == "1234")
#expect(activity.name == "Indoor Running")
}```
but even with using the default JSON decoder date format, I still get the same error.
Not sure how to fix this or to get more details.
With iOS 18, Text has a new initializer that takes in a TimeDataSource and a DiscreteFormatStyle.
Similar to this question, I'd like to make a compact timer but can't find a way to do that with any of the system formats.
Since the new API takes in a DiscreteFormatStyle though I figure I could make my own. This works in an app but not in a Live Activity; in Previews it crashes and in the simulator the view looks like placeholder.
Are custom format styles not supported in this case? Or might there something wrong with my implementation?
Formatter
Preview Crash Log
When I reboot my iPhone 14 pro with Live Activity started, KeyChain information disappears.
So there is a problem that I have to sign-in again when I enter the app.
There is no problem rebooting the iPhone without Live Activity.
iOS17 didn't have this problem.
When I reboot my iPhone 14 pro with Live Activity started, Keychase information disappears.
So there is a problem that I have to sign-in again when I enter the app.
There is no problem rebooting the iPhone without Live Activity.
iOS17 didn't have this problem.
There seems to be a misalignment when using VoiceOver and selecting the leading/trailing regions in the expanded presentation for Live Activities. This is demonstrated with an extremely basic sample project, where the expanded region is defined by the following code:
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
Text("Leading")
}
DynamicIslandExpandedRegion(.trailing) {
Text("Trailing")
}
DynamicIslandExpandedRegion(.bottom) {
Text("Bottom \(context.state.emoji)")
}
}
Is this simply a bug or am I doing something incorrectly/is there a workaround?
Hi,
working on customising my live activity Smart Stack layout for ios18.
A thing that is very frustrating is that I consistently looks different for me in the Xcode preview and on the actual watch.
See attached screenshots below.
The sizes are different, and italic doesn't work on the watch, for example.
It makes it time-consuming and unpredictable, so I was wondering if this is a known issue or if I'm doing something wrong, and also can I do anything?
thanks
edit: this is the layout:
var body: some View {
VStack(alignment: .center, spacing:4) {
HStack(alignment: .center) {
IconView(resource: "n-compact-w", bgColor: Color.checkedIn, padding: 2, paddingRight: 6, paddingBottom: 6)
.frame(maxWidth: 25, maxHeight: 25).aspectRatio(1, contentMode: .fit)
Text("Checked Out")
.font(.title3).bold()
}
Text(status.loc)
.font(.headline)
.multilineTextAlignment(.center)
Text(FormatUtils.getFormattedDateTime(status.time)).font(.subheadline)
.multilineTextAlignment(.center).italic()
}
}
When I request channel list for bundle id "com.apnspush.LiveActivityPushDemo", the request returns "TopicMismatch".
Then when I request channel list for bundle id "com.apnspush.liveactivitypush", the request return is normal.
I have tried all my bundle ids that include capital letters, they all failed with "TopicMismatch".
Does anyone know the reason?
curl -v -X GET \
-H "authorization: bearer eyJhbGciOiJFUzI1NiIsImtpZCI6IkdZSDM5WEZMREEifQ.eyJpYXQiOjE3Mjg3MDM4MDksImlzcyI6IjNXSkdFRjI4R1kifQ.wruX6J4qaovq2X-ZlO7g0YyMWjt50g8YoMoZ4G9ZsLDI5wC8u7PFTaG05BmDvbEzLpzrK9ifwPeo5BJ2eZ3hTg" \
-H "apns-request-id: 2288cf3f-70d8-46a6-97d7-dd5d00867127" \
--http2 \
https://api-manage-broadcast.sandbox.push.apple.com:2195/1/apps/com.apnspush.LiveActivityPushDemo/all-channels
Note: Unnecessary use of -X or --request, GET is already inferred.
* Host api-manage-broadcast.sandbox.push.apple.com:2195 was resolved.
* IPv6: (none)
* IPv4: 17.138.176.4
* Trying 17.138.176.4:2195...
* Connected to api-manage-broadcast.sandbox.push.apple.com (17.138.176.4) port 2195
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/ssl/cert.pem
* CApath: none
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Request CERT (13):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Certificate (11):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384 / [blank] / UNDEF
* ALPN: server accepted h2
* Server certificate:
* subject: C=US; ST=California; O=Apple Inc.; CN=api-manage-broadcast.sandbox.push.apple.com
* start date: May 30 17:31:41 2024 GMT
* expire date: Apr 10 00:00:00 2025 GMT
* subjectAltName: host "api-manage-broadcast.sandbox.push.apple.com" matched cert's "api-manage-broadcast.sandbox.push.apple.com"
* issuer: CN=Apple Public Server RSA CA 12 - G1; O=Apple Inc.; ST=California; C=US
* SSL certificate verify ok.
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://api-manage-broadcast.sandbox.push.apple.com:2195/1/apps/com.apnspush.LiveActivityPushDemo/all-channels
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: api-manage-broadcast.sandbox.push.apple.com:2195]
* [HTTP/2] [1] [:path: /1/apps/com.apnspush.LiveActivityPushDemo/all-channels]
* [HTTP/2] [1] [user-agent: curl/8.7.1]
* [HTTP/2] [1] [accept: */*]
* [HTTP/2] [1] [authorization: bearer eyJhbGciOiJFUzI1NiIsImtpZCI6IkdZSDM5WEZMREEifQ.eyJpYXQiOjE3Mjg3MDM4MDksImlzcyI6IjNXSkdFRjI4R1kifQ.wruX6J4qaovq2X-ZlO7g0YyMWjt50g8YoMoZ4G9ZsLDI5wC8u7PFTaG05BmDvbEzLpzrK9ifwPeo5BJ2eZ3hTg]
* [HTTP/2] [1] [apns-request-id: 2288cf3f-70d8-46a6-97d7-dd5d00867127]
> GET /1/apps/com.apnspush.LiveActivityPushDemo/all-channels HTTP/2
> Host: api-manage-broadcast.sandbox.push.apple.com:2195
> User-Agent: curl/8.7.1
> Accept: */*
> authorization: bearer eyJhbGciOiJFUzI1NiIsImtpZCI6IkdZSDM5WEZMREEifQ.eyJpYXQiOjE3Mjg3MDM4MDksImlzcyI6IjNXSkdFRjI4R1kifQ.wruX6J4qaovq2X-ZlO7g0YyMWjt50g8YoMoZ4G9ZsLDI5wC8u7PFTaG05BmDvbEzLpzrK9ifwPeo5BJ2eZ3hTg
> apns-request-id: 2288cf3f-70d8-46a6-97d7-dd5d00867127
>
* Request completely sent off
< HTTP/2 403
< apns-request-id: 2288cf3f-70d8-46a6-97d7-dd5d00867127
<
* Connection #0 to host api-manage-broadcast.sandbox.push.apple.com left intact
{"reason":"TopicMismatch"}%
curl -v -X GET \
-H "authorization: bearer eyJhbGciOiJFUzI1NiIsImtpZCI6IkdZSDM5WEZMREEifQ.eyJpYXQiOjE3Mjg3MDM4MDksImlzcyI6IjNXSkdFRjI4R1kifQ.wruX6J4qaovq2X-ZlO7g0YyMWjt50g8YoMoZ4G9ZsLDI5wC8u7PFTaG05BmDvbEzLpzrK9ifwPeo5BJ2eZ3hTg" \
-H "apns-request-id: 2288cf3f-70d8-46a6-97d7-dd5d00867127" \
--http2 \
https://api-manage-broadcast.sandbox.push.apple.com:2195/1/apps/com.apnspush.liveactivitypush/all-channels
Note: Unnecessary use of -X or --request, GET is already inferred.
* Host api-manage-broadcast.sandbox.push.apple.com:2195 was resolved.
* IPv6: (none)
* IPv4: 17.138.176.4
* Trying 17.138.176.4:2195...
* Connected to api-manage-broadcast.sandbox.push.apple.com (17.138.176.4) port 2195
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/ssl/cert.pem
* CApath: none
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Request CERT (13):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Certificate (11):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384 / [blank] / UNDEF
* ALPN: server accepted h2
* Server certificate:
* subject: C=US; ST=California; O=Apple Inc.; CN=api-manage-broadcast.sandbox.push.apple.com
* start date: May 30 17:31:41 2024 GMT
* expire date: Apr 10 00:00:00 2025 GMT
* subjectAltName: host "api-manage-broadcast.sandbox.push.apple.com" matched cert's "api-manage-broadcast.sandbox.push.apple.com"
* issuer: CN=Apple Public Server RSA CA 12 - G1; O=Apple Inc.; ST=California; C=US
* SSL certificate verify ok.
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://api-manage-broadcast.sandbox.push.apple.com:2195/1/apps/com.apnspush.liveactivitypush/all-channels
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: api-manage-broadcast.sandbox.push.apple.com:2195]
* [HTTP/2] [1] [:path: /1/apps/com.apnspush.liveactivitypush/all-channels]
* [HTTP/2] [1] [user-agent: curl/8.7.1]
* [HTTP/2] [1] [accept: */*]
* [HTTP/2] [1] [authorization: bearer eyJhbGciOiJFUzI1NiIsImtpZCI6IkdZSDM5WEZMREEifQ.eyJpYXQiOjE3Mjg3MDM4MDksImlzcyI6IjNXSkdFRjI4R1kifQ.wruX6J4qaovq2X-ZlO7g0YyMWjt50g8YoMoZ4G9ZsLDI5wC8u7PFTaG05BmDvbEzLpzrK9ifwPeo5BJ2eZ3hTg]
* [HTTP/2] [1] [apns-request-id: 2288cf3f-70d8-46a6-97d7-dd5d00867127]
> GET /1/apps/com.apnspush.liveactivitypush/all-channels HTTP/2
> Host: api-manage-broadcast.sandbox.push.apple.com:2195
> User-Agent: curl/8.7.1
> Accept: */*
> authorization: bearer eyJhbGciOiJFUzI1NiIsImtpZCI6IkdZSDM5WEZMREEifQ.eyJpYXQiOjE3Mjg3MDM4MDksImlzcyI6IjNXSkdFRjI4R1kifQ.wruX6J4qaovq2X-ZlO7g0YyMWjt50g8YoMoZ4G9ZsLDI5wC8u7PFTaG05BmDvbEzLpzrK9ifwPeo5BJ2eZ3hTg
> apns-request-id: 2288cf3f-70d8-46a6-97d7-dd5d00867127
>
* Request completely sent off
< HTTP/2 200
< apns-request-id: 2288cf3f-70d8-46a6-97d7-dd5d00867127
<
* Connection #0 to host api-manage-broadcast.sandbox.push.apple.com left intact
{"channels":["vtVPwIhLEe8AAG79CdMNuQ=="]}%
We are currently experiencing the following weird issue with our iPhone app. As the title says, NSUserDefaults is losing our custom keys and values when phone is rebooted but not unlocked, and this is happening on a very specific scenario with ActivityKit.
Context:
We are using the NSUserDefaults in the app to store user data (e.g. username).
Issue: An error occurred with no permission to access cached messages after a restart.
Scenario: When receiving a Dynamic Island notification, if the phone is restarted, after unlocking the phone and tapping on the Dynamic Island to open the App, all cached information results in an error.
Reasons for the error:
After restarting the APP, the files are in a locked state. The Dynamic Island proactively invokes the App method. When executing the startup method and retrieving cached messages, an error occurs due to lack of permission.
All storage, including files, NSUserDefaults, Keychain, and Plist retrieval, results in errors.
The error message is as follows:
{
"errorCode": "-25308",
"errorDesc": "Error Domain=com.samsoffes.sskeychain Code=-25308 "(null)"",
"serviceName": "com.qunar.qunarclient8",
"account": "iid"
}
The data returned at this time is in a protected state, [UIApplication sharedApplication].isProtectedDataAvailable.
Any help or idea will be truly appreciated :)
Hello, I am updating my live activity for the new ios18 Smart Stack functionality.
I got it working through the WWDC session (Bring your live activities to Apple watch).
I'm doing
supplementalActivityFamilies([.small])
and then a custom layout in the .small ActivityFamily
However, any images I try to use in the Smart Stack just show up as grey squares. (it works on the phone live activity)
I suspect it's because my app images are not moved over to the watch? Because I don't have a watch app and such no watch target?
If anyone can help me understand if there's anything I can do in order to have a custom image show up on the smart stack, I'd be very grateful.
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.
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