Reputation: 89
I support Live Activities in my app, and am unable to dismiss Live Activities when the app gets closed from the background. I understand that I can send an update from the backend but am unsure how to determine when the app gets terminated in order to send it.
Currently, I subscribe to the willTerminateNotification
notification in order to perform actions when the app gets terminated. This gets triggered when the app is terminated in the foreground. However, if it gets terminated from the background, it does not get triggered and my Live Activity stays after the app gets closed. How can I prevent this from happening?
Upvotes: 5
Views: 1932
Reputation: 244
Found a solution! It's a little involved across a few files. I'm using React Native but since this is purely iOS code, the solution still applies.
Note: For whatever reason, in XCode, the logger only fires in applicationWillTerminate
once per app initialization (hitting the play button in XCode), but this code actually does fire every time the user re-opens the app and swipes away.
Keep in mind, this function is reliable if the app is RUNNING and the user opens up the multi-app viewer and kills the app from there. The behavior may be unreliable if the app has already been suspended by the Apple system due to memory issues or because of the watch dog.
Apple Developer docs didn't help at all.
applicationWillTerminate
function in your AppDelegate.mm fileStep 1: (React Native only): In LiveActivityModule.m
Make sure your LiveActivity is properly declared.
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
// Important to note that the name "LiveActivity" was the name I declared as an Objective C module in my LiveActivityModule.swift file. It ensures the class can be referenced in react native
@interface RCT_EXTERN_MODULE(LiveActivity, NSObject)
RCT_EXTERN_METHOD(startActivity)
RCT_EXTERN_METHOD(updateActivity: (NSString *) name)
RCT_EXTERN_METHOD(endActivity)
// I don't think this is mandatory
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
@end
Step 2 & 3: In AppDelegate.mm
, create your App Delegate applicationWillTerminate function and reference your LiveActivity EndActivity function.
// other imports
#import <os/log.h>
// (React Native) You DO NOT need your bridging header to be imported, just the entire folder name of your app with '-Swift.h'. NOT the file name of the file that holds the EndActivity function
#import <AppName-Swift.h>
// I like to have a logger in this file, not mandatory
// Make sure you create the logger object in EACH FUNCTION BODY
os_log_t logger = nil;
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// other initialization
logger = os_log_create("com.bundle.id", "AppDelegate");
os_log(logger, "In AppDelegate Initializing the app");
// final stuff you want to run
}
- (void)applicationWillTerminate:(UIApplication *)application {
logger = os_log_create("com.bundle.id", "AppDelegate");
os_log(logger, "App terminated! Good bye!");
// Create an instance of the LiveActivityModule class
if (@available(iOS 16.2, *)) {
os_log(logger, "Starting call to LiveActivity! Good bye!");
LiveActivity *liveActivityModule = [[LiveActivity alloc] init];
// Call the endActivity method
[liveActivityModule endActivity];
} else {
// Fallback on earlier versions
}
}
@end
Step 4. Modify your End Activity Function in Swift Code to ensure that Tasks will finish even when app is killed suddenly. This is in my file named LiveActivityModule.swift
import SwiftUI
import ActivityKit
import OSLog
@available(iOS 16.2, *)
// Declaring this LiveActivity as an objective-c function
@objc(LiveActivity)
class LiveActivityModule: NSObject {
private var content: ActivityContent<LiveAttributes.ContentState>?
let logger = Logger(subsystem: "com.bundle.id", category: "dynamicIsland")
@objc(startActivity)
func startActivity() {
// other code
logger.debug("Starting activity")
}
@objc(updateActivity:)
func updateActivity(name: String) {
// other code
logger.debug("Updating activity")
}
@objc(endActivity)
func endActivity() {
logger.debug("Ending Live Activities")
// You need to add this Sephamore code. This way the Task will actually run
let semaphore = DispatchSemaphore(value: 0)
Task
{
logger.debug("We're in that task before the for loop")
for activity in Activity<LiveAttributes>.activities
{
logger.debug("Ending Live Activity: \(activity.id)")
await activity.end(nil, dismissalPolicy: .immediate)
}
semaphore.signal()
}
semaphore.wait()
}
}
Upvotes: 0
Reputation: 1922
// To kill the live activity immediately:
await liveActivity.end(nil, dismissalPolicy: .immediate)
Upvotes: -3