How to use Dynamic Island in SwiftUI
Combine Live Activities with the extra power of Dynamic Island to engage your app's users
Live activities provide us with a more engaging way for our app to interact with its users.
This is very powerful already, but we can go one step further and leverage the dynamic island for the devices that support it (currently iPhone 14 Pro & iPhone 14 Pro Max). If we have the Live Activities implementation done beforehand, adding Dynamic Island support is really easy.
Let me show you how.
This will be a continuation of my previous article regarding Live Activities. Weβll be using the same code, so I recommend you check out that article first and then come back here
Presentation types
Before coding our implementation, we need to understand the three types of presentations of the Dynamic Island that we have available.
Compact
It's used by the system when there is only one Live Activity active at the moment.
It's the combination of two components: one for the left side of the TrueDepth camera, and another for the right side.
Minimal
If there is more than one active Live Activity, the system will display two of them.
One attached to the Dynamic Island, and another one detached from it.
Extended
The extender version of the Dynamic Island is shown when the user tap & hold the Live Activity that is in Compact or Minimal type
Here is the widget implementation that we did for our Live Activity π
struct CoffeeShopWidgetLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: OrderAttributes.self) { context in
LiveActivityView(state: context.state)
} dynamicIsland: { context in
// Here goes the dynamic island implementation
}
}
}
The only thing we need to do to fully support Dynamic Island is to provide a custom DynamicIsland
instance using its constructor: init(expanded:compactLeading:compactTrailing:minimal:)
Each parameter of the method corresponds to the presentation type.
DynamicIsland {
// Expanded presentation
} compactLeading: {
Text("Order: \(context.state.currentOrder)")
} compactTrailing: {
Image(systemName: context.state.status == .ready ? "checkmark.circle.fill" : "cup.and.saucer")
.contentTransition(.symbolEffect(.replace))
} minimal: {
Image(systemName: context.state.status == .ready ? "checkmark.circle.fill" : "cup.and.saucer")
.contentTransition(.symbolEffect(.replace))
}
For the expanded presentation, the system divides the available space into four regions. This gives us better control to place the elements. We can use the regions that suit our Live Activity best.
We need to use a custom builder to provide the UI: DynamicIslandExpandedContentBuilder
π init(_:priority:content)
struct CoffeeShopWidgetLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: OrderAttributes.self) { context in
LiveActivityView(state: context.state)
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.bottom) {
VStack {
HStack {
Image(systemName: "cup.and.saucer")
ProgressView(value: context.state.status.rawValue, total: 3)
.tint(.brown)
.background(Color.white)
Image(systemName: context.state.status == .ready ? "checkmark.circle.fill" : "cup.and.saucer.fill")
.contentTransition(.symbolEffect(.replace))
}
.padding(.horizontal, 16)
Text("\(context.state.status.description)")
.font(.system(size: 18, weight: .semibold))
.padding(.bottom)
}
}
} compactLeading: {
Text("Order: \(context.state.currentOrder)")
} compactTrailing: {
Image(systemName: context.state.status == .ready ? "checkmark.circle.fill" : "cup.and.saucer")
.contentTransition(.symbolEffect(.replace))
} minimal: {
Image(systemName: context.state.status == .ready ? "checkmark.circle.fill" : "cup.and.saucer")
.contentTransition(.symbolEffect(.replace))
}
}
}
}
If we implement several regions, we can use the priority option to prioritize a specific expanded region over other
To keep in mind
Try to display the minimum information you can. As we have limited space, focus on the most relevant details for your users.
Use animations to show updates. This way, we extend the engagement with our users.
Show the expanded presentation when ending the Live Activity. We can achieve this by passing an AlertConfiguration
instance in the activity's update method.