This is the first project example referring to the latest Apple ActivityKit beta and Dynamic Island (NEW) release.
Live Activities will help you follow an ongoing activity right from your Lock Screen, so you can track the progress of your food delivery or use the Now Playing controls without unlocking your device.
Your appโs Live Activities display on the Lock Screen and in Dynamic Island โ a new design that introduces an intuitive, delightful way to experience iPhone 14 Pro and iPhone 14 Pro Max.
https://twitter.com/1998design/status/1552681295607566336?s=21&t=waceX8VvaP-VCGc2KJmHpw https://twitter.com/1998design/status/1552686498276814848?s=21&t=waceX8VvaP-VCGc2KJmHpw https://twitter.com/1998design/status/1570225193095933952?s=21&t=LoYk1Llj0cLpEhG0MBFZLw
- iOS 16 beta 4* ... iOS 16.1 beta 1
- Xcode 14 beta 4 ... Xcode 14.1 beta 1
*Real Devices on iOS 16 beta 5-8, RC, GM will crash, please use simulators instead.
Medium: https://1998design.medium.com/how-to-create-live-activities-widget-for-ios-16-2c07889f1235
Add NSSupportsLiveActivities
key and set to YES
.
import ActivityKit
struct PizzaDeliveryAttributes: ActivityAttributes {
public typealias PizzaDeliveryStatus = ContentState
public struct ContentState: Codable, Hashable {
var driverName: String
// Changed from Date to ClosedRange<Date> - 16.1
var estimatedDeliveryTime: ClosedRange<Date>
}
var numberOfPizzas: Int
var totalAmount: String
}
func startDeliveryPizza() {
let pizzaDeliveryAttributes = PizzaDeliveryAttributes(numberOfPizzas: 1, totalAmount:"$99")
// Date() changed to Date()...Date() - 16.1
let initialContentState = PizzaDeliveryAttributes.PizzaDeliveryStatus(driverName: "TIM ๐จ๐ปโ๐ณ", estimatedDeliveryTime: Date()...Date().addingTimeInterval(15 * 60))
do {
let deliveryActivity = try Activity<PizzaDeliveryAttributes>.request(
attributes: pizzaDeliveryAttributes,
contentState: initialContentState,
pushType: nil)
print("Requested a pizza delivery Live Activity \(deliveryActivity.id)")
} catch (let error) {
print("Error requesting pizza delivery Live Activity \(error.localizedDescription)")
}
}
func updateDeliveryPizza() {
Task {
// Date() changed to Date()...Date() - 16.1
let updatedDeliveryStatus = PizzaDeliveryAttributes.PizzaDeliveryStatus(driverName: "TIM ๐จ๐ปโ๐ณ", estimatedDeliveryTime: Date()...Date().addingTimeInterval(60 * 60))
for activity in Activity<PizzaDeliveryAttributes>.activities{
await activity.update(using: updatedDeliveryStatus)
}
}
}
func stopDeliveryPizza() {
Task {
for activity in Activity<PizzaDeliveryAttributes>.activities{
await activity.end(dismissalPolicy: .immediate)
}
}
}
func showAllDeliveries() {
Task {
for activity in Activity<PizzaDeliveryAttributes>.activities {
print("Pizza delivery details: \(activity.id) -> \(activity.attributes)")
}
}
}
import ActivityKit
import WidgetKit
import SwiftUI
@main
struct Widgets: WidgetBundle {
var body: some Widget {
PizzaDeliveryActivityWidget()
}
}
struct PizzaDeliveryActivityWidget: Widget {
var body: some WidgetConfiguration {
// attributesType changed to for - 16.1
ActivityConfiguration(for: PizzaDeliveryAttributes.self) { context in
VStack(alignment: .leading) {
HStack {
VStack(alignment: .leading) {
Text("\(context.state.driverName) is on the way!").font(.headline)
HStack {
VStack {
Divider().frame(height: 6).overlay(.blue).cornerRadius(5)
}
Image(systemName: "box.truck.badge.clock.fill").foregroundColor(.blue)
VStack {
RoundedRectangle(cornerRadius: 5)
.stroke(.secondary, style: StrokeStyle(lineWidth: 1, dash: [5]))
.frame(height: 6)
}
Text(timerInterval: context.state.estimatedDeliveryTime, countsDown: true)
VStack {
RoundedRectangle(cornerRadius: 5)
.stroke(.secondary, style: StrokeStyle(lineWidth: 1, dash: [5]))
.frame(height: 6)
}
Image(systemName: "house.fill").foregroundColor(.green)
}
}.padding(.trailing, 25)
Text("\(context.attributes.numberOfPizzas) ๐").font(.title).bold()
}.padding(5)
Text("You've already paid: \(context.attributes.totalAmount) + $9.9 Delivery Fee ๐ธ").font(.caption).foregroundColor(.secondary)
}.padding(15)
}
// NEW 16.1
dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
Label("\(context.attributes.numberOfPizzas) Pizzas", systemImage: "bag")
.font(.title2)
}
DynamicIslandExpandedRegion(.trailing) {
Label {
Text(timerInterval: context.state.estimatedDeliveryTime, countsDown: true)
.multilineTextAlignment(.trailing)
.frame(width: 50)
.monospacedDigit()
.font(.caption2)
} icon: {
Image(systemName: "timer")
}
.font(.title2)
}
DynamicIslandExpandedRegion(.center) {
Text("\(context.state.driverName) is on his way!")
.lineLimit(1)
.font(.caption)
}
DynamicIslandExpandedRegion(.bottom) {
Button {
// Deep link into the app.
} label: {
Label("Contact driver", systemImage: "phone")
}
}
} compactLeading: {
Label {
Text("\(context.attributes.numberOfPizzas) Pizzas")
} icon: {
Image(systemName: "bag")
}
.font(.caption2)
} compactTrailing: {
Text(timerInterval: context.state.estimatedDeliveryTime, countsDown: true)
.multilineTextAlignment(.center)
.frame(width: 40)
.font(.caption2)
} minimal: {
VStack(alignment: .center) {
Image(systemName: "timer")
Text(timerInterval: context.state.estimatedDeliveryTime, countsDown: true)
.multilineTextAlignment(.center)
.monospacedDigit()
.font(.caption2)
}
}
.keylineTint(.cyan)
}
}
Console: Requested a pizza delivery Live Activity DA288E1B-F6F5-4BF1-AA73-E43E0CC13150
Console: Pizza delivery details: DA288E1B-F6F5-4BF1-AA73-E43E0CC13150 -> PizzaDeliveryAttributes(numberOfPizzas: 1, totalAmount: "$99")
Swiftยฎ and SwiftUIยฎ are trademarks of Apple Inc.