How to use Dynamic Island in SwiftUI

Combine Live Activities with the extra power of Dynamic Island to engage your app's users

Bruno Lorenzo
4 min readSep 5, 2023
Image from Apple

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.

Image from Apple

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.

Image from Apple

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

Image from Apple

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.

Image from Apple

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.

Have any questions? Feel free to drop me a message! πŸ™‚

  • πŸ€“ Join me on Twitter for regular content on iOS development tips and insights
  • πŸš€ Check out my GitHub where I share all my example projects (this one included πŸ˜‰)

--

--

Bruno Lorenzo

Software Engineer | Innovation Manager at www.houlak.com | Former iOS Tech Lead | I write about iOS, tech, and producitivy