(Also have a case ID, 9879068)
We have an app that user use to check in/out from work for example. We have a button in-app do do this. Now I'm trying to add buttons to our widgets and our new live activity so that users don't have to open the app.
It's crucial that the live activity and widgets always show the exact same state.
Otherwise it'll look pretty bad if a user has both a live activity and a widget showin at the same time.
However, we have noticed that sometimes, pressing the button in the live activity, running the app intent, will not always make the widget update (we call reloadAllTimelines()). The other way around, i.e. press the button on widget to update live activity always works. (they both call the same app intent)
When running it in debug mode on a phone from Xcode, it always works, but when running it just on the phone it's unreliable.
My first thought was, of course, that's related to the widget "budget", but according to the docs HERE, it should not be applied when interacting with a widget, calling an app intent.
My question: HOW can I make my widget reliably refresh using an app intent invoked from a live activity??
I have a ready small project with simple buttons and trace labels that display this issue that I'm happy to supply to someone.
Post
Replies
Boosts
Views
Activity
We have widgets in our app. We're now working on a Live Activitiy with a button calling an app intent. This app intent needs to update our Widgets, and we're seeing semi-great results.
When we're updating the widgets from within the app, it works great. Also from geofence triggers it usually works, so we're thinking it might have to do with the "widget update budget"?
According to the docs:
Cases in which WidgetKit doesn’t count reloads against your widget’s budget include when: The widget performs an app intent, such as when the user taps a button or toggles a switch.
But we're not really seeing that. When I run our app from within Xcode, everything runs great all the time and the widget gets updated within milliseconds, but when running the TestFlight version is more spotty.
To be clear: This is a button in a live activity, calling an app intent, and in turn, the app intent is calling reloadAllTimelines for our "regular" widgets.
The live activity itself always gets updated properly.
My question is basically, am I doing something wrong and can I do something to increase the consistency of the widget updating on time?
Abbreviated example:
final class UserEventIntent: NSObject, LiveActivityIntent {
@MainActor
func perform() async throws -> some IntentResult {
do {
let newStatus: (stat: Status, wasSame: Bool) = try await eventHelper.performEvent(status: status)
WidgetCenter.shared.reloadAllTimelines()
}catch {
await WidgetCenterBridge.updateLiveActivityForInProgress(false)
}
return .result()
}
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()
}
}
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.
Hi, just got upgraded this morning and now I can't use my local Tomcat install as server for my apps.
I can access it in safari using localhost, but not using my computers IP.
I start it from terminal.
I looked everywhere in settings, and searched online of course, but have been unable to find an answer.
Anyone knows how I can fit it? Input appreciated.
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
Hi, I have a workspace with a couple of modules, and now I'm making a Swift package. It's great.
However, i have this SwiftUI View extension you can see at the end of this post. My Swift package needs to have the target set to IOS16, so i have added #available where I need, like I've done in the past.
When i had this extension in my Widget extension target, it worked fine. However, i am now trying to move that into my new Swift package, with the Swift package declaration you can also find below.
When moved, i get an error from the compiler:
Type 'ContainerBackgroundPlacement' has no member 'widget'
It seems that my #available is ignored? I'm at a loss as to why this happens, so if anyone has any ideas i'm all ears. Otherwise guess i'll have to move it back to the Widget extension target :(
My extension code:
extension View {
func widgetBackground(_ color: Color) -> some View {
if #available(iOSApplicationExtension 17.0, *), #available(iOS 17.0, *) {
return containerBackground(color, for: .widget)
} else {
return background(color)
}
}
.....more stuff
}
Swift Package (i renamed and cropped some stuff for simplicity):
import PackageDescription
let package = Package(
name: "MyPackage",
platforms: [
.iOS(.v16)
],
products: [
.library(
name: "MyPackage",
targets: ["MyPackage"]
)
],
targets: [
.target(
name: "MyPackage"
),
.testTarget(
name: "MyPackageTests",
dependencies: ["MyPackage"]
)
]
)
I am looking into a piece of old code where the mentioned method is called.
+ (bool)isLocationServicesEnabled {
return [CLLocationManager locationServicesEnabled];
}
I'm getting the classic "This method can cause UI unresponsiveness if invoked on the main thread. Instead, consider waiting for the -locationManagerDidChangeAuthorization: callback and checking authorizationStatus first."
I have 2 questions:
What is that error about, really? The locationServicesEnabled() has nothing to do with authorisation, it's just about the "location services" settings global on-off switch? (the authorisation check is .authorizationStatus)
I don't understand why that call is such a big issue? It's just a setting? Why would that be so costly?
Thankful for pointers! Have a good one
Please find below a complete app example.
It has a button, when you press it, a local notification is created. However, the UnNotificationCenter.delegate is called twice, and I can't understand why.
I am trying to move my project from Objective-C to Swift, and my similar code there doesn't get called twice, so I'm confused.
Can anybody shine a light on this? Pointers appreciated.
App:
@main
struct NotifTestApp: App {
init() {
UNUserNotificationCenter.current().delegate = NotificationReceiveHandler.shared
configureUserNotifications()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
private func configureUserNotifications() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
if granted {
print("Notification permission granted.")
} else if let error = error {
print("Error requesting notification permissions: \(error)")
}
}
}
}
class NotificationReceiveHandler: NSObject, UNUserNotificationCenterDelegate {
static let shared = NotificationReceiveHandler()
//>> THIS IS CALLED TWICE WHEN I PRESS THE BUTTON
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
NSLog(">>> Will present notification!")
completionHandler([.sound])
}
}
///THE UI
struct ContentView: View {
var body: some View {
VStack {
Text("👾")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Notification test!")
Text("When i press the button, will present is called twice!!").font(.footnote)
.padding(10)
Button("Create Notification") {
createNotification(
message: "This is a test notification",
header: "Test Notification",
category: "TEST_CATEGORY",
playSound: true,
dictionary: nil,
imageName: nil)
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
.padding()
}
}
#Preview {
ContentView()
}
private func createNotification(message: String, header: String, category: String, playSound: Bool = true, dictionary: NSDictionary? = nil, imageName: String? = nil) {
let content = UNMutableNotificationContent()
content.title = header
content.body = message
content.categoryIdentifier = category
content.badge = NSNumber(value: 0)
if let imageName = imageName, let imageURL = Bundle.main.url(forResource: imageName, withExtension: "png") {
do {
let attachment = try UNNotificationAttachment(identifier: "image", url: imageURL, options: nil)
content.attachments = [attachment]
} catch {
print("Error creating notification attachment: \(error)")
}
}
content.sound = playSound ? UNNotificationSound(named: UNNotificationSoundName("event.aiff")) : nil
if let infoDict = dictionary {
content.userInfo = infoDict as! [AnyHashable: Any]
}
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
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.
Please consider simple example below.
I am trying to put a timer in the upper right corner of a live activity. I am done, it works, but I'm trying to get the timer to look better.
If I take a regular text, I can get it to align properly by adjusting the .frame(), , but for a text with a timer inside it, alignment is ignored from what I can see.
Text("Hello").frame(width: 90, alignment: .trailing).border(.red)
/*Text(timerInterval: timeRange, countsDown: false)
.monospacedDigit().font(.subheadline).frame(width: 90, alignment: .trailing).border(.red)*/
}
Is there any way to fix this?
Right now, I have a fixed width so that HH:mm:ss will fit, but that doesn't look super great if it's just minutes and seconds for example, there's an empty block to the right
Basically, in my widget/live activity, I want to extract reusable views into a separate file with an isolated view and preview. Dummy example below.
I cannot do it because it says "missing previewcontext".
The only way I've found is to add the view to my main app target, but I don't want to clutter my main app wiews that only exist in my widgets if I can avoid it.
Can this be done somehow? Thoughts appreciated.
Dummy example (tried with and without "previewLayout":
struct StatusActivityView: View {
let status: UserStatusData
var body: some View {
VStack(alignment: .center) {
Text("Dummy example")
}.background(.blue).padding(5)
}
}
@available(iOS 16.2, *)
struct StatusActivityView_Previews: PreviewProvider {
static var previews: some View {
let status = WidgetConstants.defaultEntry()
return StatusActivityView(status: status).previewLayout(.sizeThatFits)
}
}
Our app is a time reporting service with various functions around that. The user checks in at work, checks out when they go home.
We thought it'd be useful to provide a live activity to show how long they have worked for.
There is also a couple of other cool things we could do that users would love, but i couldn't find definitive answers to the questions below.
1.
We have a geofence-based function that checks the user in when they for example arrive at work, and check them out when they go home, so that they don't have to open the app.
However, this means that we will need to start and end the live activity from within a geofence trigger. Is this possible?
2.
It seems that the maximum time for a live activity is 8 hours? Sometimes people work for longer... How would we solve this? i would be fine with 12 since it would solve most cases.
Is it possible somehow to go beyond 8 hours up to 12?
If not, is there a callback that "8 hours are up!" so that i could do a final update on the live activity from a counter to "you started working at 09:04"
3.
I have seen that some live activities have buttons. It would be neat if the user can check out via a button on the live activity. However, since we take location and call our servers when checking out, we
need to be able to use both the locationmanager and make a network call from the live activity. Is this possible?
Thanks in advance, Cheers
Hello,
I created a lock screen widget for my app last year, as a widget app extension. It has worked fine, but I am working on a new release and it has stopped updating:
It is not called anymore when I call WidgetCenter.shared.reloadTimelines(ofKind: "MyWidget"), and the preview screen is now blank when I add it on the lockscreen.
I haven't touched anything related to the widget from what I know. The only things I can think of is that Xcode has updated my project files automagically, and I have updated cocoapods.
I looked at the device logs, and found this:
[...MyWidget] Failed to launch extension with error: Error Domain=com.apple.extensionKit.errorDomain Code=2 UserInfo={NSUnderlyingError=0x84b4d4410 {Error Domain=RBSRequestErrorDomain Code=5 UserInfo={NSLocalizedFailureReason=, NSUnderlyingError=0x84b45ea30 {Error Domain=NSPOSIXErrorDomain Code=111 UserInfo={NSLocalizedDescription=}}}}}.
I have no idea what this is, hoping someone can come up with suggestions on where to look. I have looked at my git history but I can't find anything changed.