This guiding article will help you get on well with live activities and teach you how to create your own live widgets.
What is live activity?
From iOS 16.1 Apple provides us with a framework called Activity Kit which allows us to create lock screen live widgets. According to Apple’s documentation “With the ActivityKit framework, you can start a Live Activity to share live updates from your app in the Dynamic Island and on the Lock Screen”. Devices, apart from iPhone 14 Pro, will display our activities on lock screen like usual notification but, as mentioned above activity kit supports dynamic island.
Example use case
Let’s say that you are creating a delivery app. For the purpose of this article imagine that you are in charge of a pizza delivery utility called “PizzaHub”. Using live activities will allow your app to display dynamic data about the order on users lock screen. You can show various types of data, for instance: approximate time of the delivery, name of the deliverer, as well as a progress bar showing the distance between you and your pizza.
How to start?
Moving to the coding part, first I have to mention that you will need at least XCode 14 and a device with iOS 16.1 or newer.
After creating your project I will need you to go to Info.plist, add a property called “Supports Live Activities” and set its value to YES.
Next step is to create a widget bundle by adding a new target to our project.
File > New > Target
In that popup menu search for Widget Extension
After creating Widget Extension you will notice that a new group appeared in your project.
Activity Attributes
In the live activity file you will find a struct called ActivityAttributes. Inside of it you should find another Struct called ContentState which should contain all of the dynamic data (to be specific — data that you want to display). Outside of it, as a property of ActivityAttributes, you should keep static data, for example: id or name of the activity.
struct TestActivityAttributes: ActivityAttributes {
public struct ContentState: Codable, Hashable {
var approximateTime: ClosedRange<Date>
var delivererName: String
}
var activityName: String
}
Activity View
Below, in the same file you will find the struct LiveActivity that inherits from a protocol called “Widget”. Inside of it you will notice a property called body, which is built out of two closures. First one is in charge of the lock screen view, the second one is the dynamic island.
Let’s create a simple view that will represent our dynamic data. You can access your activity data by using context.state.
var body: some WidgetConfiguration {
ActivityConfiguration(for: TestActivityAttributes.self) { context in
HStack {
Text(timerInterval: context.state.approximateTime, countsDown: true)
Text(context.state.delivererName)
}
.activitySystemActionForegroundColor(Color.black)
}
Next we have a closure with a dynamic island. First we will take care of the expanded view. You can access it by touching and holding a dynamic island. Below there is an attached image of divided parts of the expanded dynamic island.
I’ve implemented a basic view for an expanded dynamic island. In that closure we also use the value of context to access our dynamic data.
dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
Text(timerInterval: context.state.approximateTime, countsDown: true)
}
DynamicIslandExpandedRegion(.trailing) {
Text(context.state.delivererName)
}
DynamicIslandExpandedRegion(.bottom) {
Text("PizzaHub")
}
}
After that part you will see a compact view for a dynamic island. Once there is only one ongoing activity it will present its compact version.
While your device is running more than one activity, the system will display it using minimal presentation. A device will choose itself which app will be displayed as attached. By tapping the activity, users will open the app and get more information about the activity.
Here is an example of implementing the compact view.
compactLeading: {
Text(timerInterval: context.state.approximateTime, countsDown: true)
} compactTrailing: {
Text("PizzaHub")
} minimal: {
Text("🍕")
}
How to start an Activity?
To start an activity let’s jump into ContentView. First you have to import ActivityKit, then we can create a function that will send a request and initialize our activity. Keep in mind that all of our activities are stored in an array. To send a request we need to provide a request function with both static and dynamic attributes.
func startActivity() {
do {
let staticAttribues = TestActivityAttributes(activityName: "test")
let dynamicContent: TestActivityAttributes.ContentState = TestActivityAttributes.ContentState(approximateTime: Date()...Date().addingTimeInterval(60 * 60), delivererName: "Bob")
_ = try Activity<TestActivityAttributes>.request(attributes: staticAttribues, contentState: dynamicContent)
} catch {
print(error)
}
}
How to end an Activity?
As mentioned above activities are stored in an array so in that case we will use a for loop to end them all. As we will use an asynchronous function, I need you to open a Task.
func endActivity() {
Task {
for activity in Activity<TestActivityAttributes>.activities {
await activity.end(dismissalPolicy: .immediate)
}
}
}
How to update an Activity?
To update our activity we also need to provide attributes, but in that case only the dynamic data. Same as above. we will use a for loop and open a Task to use an asynchronous function.
func updateActivity() {
let updatedContent = TestActivityAttributes.ContentState(approximateTime: Date()...Date().addingTimeInterval(15 * 60), delivererName: "Alan")
Task {
for activity in Activity<TestActivityAttributes>.activities {
await activity.update(using: updatedContent)
}
}
}
Conclusion
Activity kit is a very powerful tool that allows developers to create more user friendly features and enhance user experience. In my opinion it’s the most handy way to check specific information just by reading it from your lock screen.
Want us to solve all the tech mysteries for you? We’re ready to help!