Ending Live Activity in new iOS 16.2 ActivityKit API

2.2k Views Asked by At

I am following along the tutorial at https://www.youtube.com/watch?v=AUOoalBwxos

However, the ActivityKit API used to start and end the live activity have been deprecated in iOS 16.2.

I have figured out how to update the start method to the new API by replacing activity = try? Activity<TimeTrackingAttributes>.request(attributes: attributes, contentState: state, pushType: nil) with:

let activityContent = ActivityContent(state: state, staleDate: Calendar.current.date(byAdding: .hour, value: 3, to: Date())!)

do {
    let myActivity = try Activity<TimeTrackingAttributes>.request(attributes: attributes, content: activityContent, pushType: nil)
    print("Requested MyActivity live activity. ID: \(myActivity.id)")
} catch let error {
    print("Error requesting live activity: \(error.localizedDescription)")
}

However, I am having trouble ending the live activity started with the new API. With the old, deprecated API, I could start and end my live activity. But when I use the old end API, I get the message end(using:dismissalPolicy:)' was deprecated in iOS 16.2: Use end(content:dismissalPolicy:) instead. The old ‘end’ API does not end the activity started with the new 'start' API.

Would anyone be able to offer some advice for how to end a live activity with the new ActivityKit API in iOS 16.2?

Documentation for the new API: https://developer.apple.com/documentation/activitykit/activity/end(_:dismissalpolicy:)

Documentation for the old API: https://developer.apple.com/documentation/activitykit/activity/end(using:dismissalpolicy:)

The full code for ContentView:

import SwiftUI
import ActivityKit

struct ContentView: View {
    @State private var isTrackingTime: Bool = false
    
    @State private var startTime: Date? = nil
    
    @State private var activity: Activity<TimeTrackingAttributes>? = nil;
    
    var body: some View {
        NavigationView {
            VStack {
                if let startTime {
                    Text(startTime, style: .relative)
                }
                
                Button {
                    isTrackingTime.toggle()
                    
                    if isTrackingTime {
                        startTime = .now
                        
                        // start the live activity
                        let attributes = TimeTrackingAttributes()
                        guard let startTime else { return }
                        let state = TimeTrackingAttributes.ContentState(startTime: startTime)
                        
                        activity = try? Activity<TimeTrackingAttributes>.request(attributes: attributes, contentState: state, pushType: nil)
                        
                        // TODO: how to match the new 'start' API to the new 'end' API ?
//                        let activityContent = ActivityContent(state: state, staleDate: Calendar.current.date(byAdding: .hour, value: 3, to: Date())!)
//                        do {
//                            let myActivity = try Activity<TimeTrackingAttributes>.request(attributes: attributes, content: activityContent, pushType: nil)
//                            print("Requested MyActivity live activity. ID: \(myActivity.id)")
//                        } catch let error {
//                            print("Error requesting live activity: \(error.localizedDescription)")
//                        }
                    } else {
                        // end the live activity
                        guard let startTime else { return }
                        let state = TimeTrackingAttributes.ContentState(startTime: startTime)
                        
                        // TODO: how to match the new 'end' API to the new 'start' API ?
                        Task {
                             await activity?.end(using: state, dismissalPolicy: .immediate)
                        }
                        
                        self.startTime = nil
                    }
                } label: {
                    Text(isTrackingTime ? "STOP" : "START")
                        .fontWeight(.light)
                        .foregroundColor(.white)
                        .frame(width: 200, height: 200)
                        .background(Circle().fill(isTrackingTime ? .red : .green))
                }
                .navigationTitle("Basic Time Tracker")
            }
        }
    }
}

The full code for TimeTrackingAttributes:

import Foundation
import ActivityKit

struct TimeTrackingAttributes: ActivityAttributes {
    public typealias TimeTrackingStatus = ContentState
    
    public struct ContentState: Codable, Hashable {
        var startTime: Date
    }
}
2

There are 2 best solutions below

1
On

It worked for me:

func stopLiveActivity() {
    let state = TimeTrackingAttributes.TimeTrackingStatus(endTime: .now) 

    Task {
        let content = ActivityContent(state: state, staleDate: .now)
        await activity?.end(content, dismissalPolicy: .immediate)
    }
}

This code ends the activity immediately by creating content with staleDate: .now and dismissalPolicy: .immedite

1
On

Pretty sure the reason as to why it needs the content is so that the 'Stale date' of the activity is also updated to its final value. Hope it helps, this way works perfectly for me, I'll mention a thing here also:

  • if you end the activity with dismissal policy of '.after(date:)' the dynamic island part of the activity gets immediately removed, even tho the lockscreen/noficiation view gets to stay until the dismissal date.

    let state = TimeTrackingAttributes.ContentState(startTime: startTime)
    
    Task {
       await activity?.end(ActivityContent(state: state, staleDate: someDate, dismissalPolicy: .immediate))
     }