Stay Actions, first launched inĀ iOS 16, are one in every of Apple’s most fun updates for creating apps that really feel extra linked to customers in actual time. As a substitute of requiring customers to continually reopen an app, Stay Actions let info stay seen proper on the Lock Display and Dynamic Island. Whether or not it is monitoring a meals supply, checking sports activities scores, or monitoring progress towards a objective, this function retains necessary updates only a look away.
Later inĀ iOS 17, Apple expanded Stay Actions even additional by supporting push updates from the server facet, which makes them much more highly effective for apps that depend on real-time info. However even with out server-driven updates, Stay Actions are extremely helpful for client-side apps that wish to enhance engagement and supply well timed suggestions.
On this tutorial, we’ll discover the way to implement Stay Actions by constructing aĀ Water Tracker app. The app permits customers to log their each day water consumption and immediately see their progress replace on the Lock Display or Dynamic Island. By the top of the tutorial, you may perceive the way to combine Stay Actions into your SwiftUI apps.
A Fast Have a look at the Demo App

Our demo app,Ā Water Tracker, is an easy and enjoyable approach to maintain observe of your each day water consumption. Youāve most likely heard the recommendation that consuming eight glasses of water a day is an effective behavior, and this app helps you keep aware of that objective. The design is minimal on objective: there is a round progress bar exhibiting how far alongside you’re, and each time you faucet theĀ Add GlassĀ button, the counter goes up by one and the progress bar fills a little bit extra.
Behind the scenes, the app makes use of aĀ WaterTracker
Ā class to handle the logic. This class retains observe of what number of glasses youāve already logged and what your each day objective is, so the UI all the time displays your present progress. Right hereās the code that makes it work:
import Commentary
@Observable
class WaterTracker {
var currentGlasses: Int = 0
var dailyGoal: Int = 8
func addGlass() {
guard currentGlasses < dailyGoal else { return }
currentGlasses += 1
}
func resetDaily() {
currentGlasses = 0
}
var progress: Double {
Double(currentGlasses) / Double(dailyGoal)
}
var isGoalReached: Bool {
currentGlasses >= dailyGoal
}
}
What we’re going to do is so as to add Stay Actions help to the app. As soon as carried out, customers will have the ability to see their progress straight on the Lock Display and within the Dynamic Island. The Stay Exercise will present the present water consumption alongside the each day objective in a transparent, easy manner.

Stay Actions are constructed as a part of an app’s widget extension, so step one is so as to add a widget extension to your Xcode venture.
On this demo, the venture is named WaterReminder. To create the extension, choose the venture in Xcode, go to the menu bar, and select Editor > Goal > Add Goal. When the template dialog seems, choose Widget Extension, give it a reputation, and ensure to verify the Embody Stay Exercise possibility.

When Xcode asks, be sure you activate the brand new scheme. It would then generate the widget extension for you, which seems as a brand new folder within the venture navigator together with the starter code for the Stay Exercise and the widget.
Weāll be rewriting all theĀ WaterReminderWidgetLiveActivity.swift
Ā file from scratch, so itās greatest to filter all of its present code earlier than continuing.
Because the Stay Exercise doesnāt depend on the widget, you possibly can optionally take away the WaterReminderWidget.swift
file and replace the WaterReminderWidgetBundle
struct like this:
struct WaterReminderWidgetBundle: WidgetBundle {
var physique: some Widget {
WaterReminderWidgetLiveActivity()
}
}
Defining the ActivityAttributes Construction
TheĀ ActivityAttributes
Ā protocol describes the content material that seems in your Stay Exercise.Ā We have now to undertake the protocol and outline the dynamic content material of the exercise.
Since this attributes construction is normally shared between each the principle app and widget extension, I counsel to create a shared folder to host this Swift file. Within the venture folder, create a brand new folder named Shared
after which create a brand new Swift file named WaterReminderWidgetAttributes.swift
.
Replace the content material like this:
import Basis
import ActivityKit
struct WaterReminderWidgetAttributes: ActivityAttributes {
public struct ContentState: Codable, Hashable {
var currentGlasses: Int
var dailyGoal: Int
}
var activityName: String
}
extension WaterReminderWidgetAttributes {
static var preview: WaterReminderWidgetAttributes {
WaterReminderWidgetAttributes(activityName: "Water Reminder")
}
}
extension WaterReminderWidgetAttributes.ContentState {
static var pattern: WaterReminderWidgetAttributes.ContentState {
WaterReminderWidgetAttributes.ContentState(currentGlasses: 3, dailyGoal: 8)
}
static var goalReached: WaterReminderWidgetAttributes.ContentState {
WaterReminderWidgetAttributes.ContentState(currentGlasses: 8, dailyGoal: 8)
}
}
TheĀ WaterReminderWidgetAttributes
Ā struct adopts theĀ ActivityAttributes
Ā protocol and contains anĀ activityName
Ā property to determine the exercise. To evolve to the protocol, we outline a nestedĀ ContentState
Ā struct, which holds the info displayed within the Stay Exerciseāparticularly, the variety of glasses consumed and the each day objective.
The extensions are used for SwiftUI previews, offering pattern knowledge for visualization.
Please take observe that the goal membership of the file ought to be accessed by each the principle app and the widget extension. You may confirm it within the file inspector.

Implementing the Stay Exercise View
Subsequent, letās implement the reside exercise view, which handles the person interface in numerous settings. Open the WaterReminderWidgetLiveActivity.swift
file and write the code like beneath:
import ActivityKit
import WidgetKit
import SwiftUI
struct WaterReminderLiveActivityView: View {
let context: ActivityViewContext
var physique: some View {
VStack(alignment: .main, spacing: 10) {
HStack {
Textual content("š§")
.font(.title)
Textual content("Water Reminder")
.font(.headline)
.fontWeight(.semibold)
Spacer()
}
HStack {
Textual content("Present: (context.state.currentGlasses)")
.font(.title2)
.fontWeight(.daring)
Spacer()
Textual content("Objective: (context.state.dailyGoal)")
.font(.title2)
}
// Progress bar
Gauge(worth: Double(context.state.currentGlasses), in: 0...Double(context.state.dailyGoal)) {
EmptyView()
}
.gaugeStyle(.linearCapacity)
}
}
}
This view defines the principle interface of the Stay Exercise, which seems on each the Lock Display and the Dynamic Island. It shows a progress bar to visualise water consumption, together with the present variety of glasses consumed and the each day objective.
Subsequent, create the WaterReminderWidgetLiveActivity
struct like this:
struct WaterReminderWidgetLiveActivity: Widget {
var physique: some WidgetConfiguration {
ActivityConfiguration(for: WaterReminderWidgetAttributes.self) { context in
// Lock display screen/banner UI goes right here
WaterReminderLiveActivityView(context: context)
.padding()
} dynamicIsland: { context in
DynamicIsland {
// Expanded UI goes right here. Compose the expanded UI via
DynamicIslandExpandedRegion(.heart) {
WaterReminderLiveActivityView(context: context)
.padding(.backside)
}
} compactLeading: {
Textual content("š§")
.font(.title3)
} compactTrailing: {
if context.state.currentGlasses == context.state.dailyGoal {
Picture(systemName: "checkmark.circle")
.foregroundColor(.inexperienced)
} else {
ZStack {
Circle()
.fill(Coloration.blue.opacity(0.2))
.body(width: 24, top: 24)
Textual content("(context.state.dailyGoal - context.state.currentGlasses)")
.font(.caption2)
.fontWeight(.daring)
.foregroundColor(.blue)
}
}
} minimal: {
Textual content("š§")
.font(.title2)
}
}
}
}
The code above defines the Stay Exercise widget configuration for the app. In different phrases, you configure how the reside exercise ought to seem below totally different configurations.
To maintain it easy, we show the identical reside exercise view on the Lock Display and Dynamic Island.
TheĀ dynamicIsland
Ā closure specifies how the Stay Exercise ought to look contained in the Dynamic Island. Within the expanded view, the identicalĀ WaterReminderLiveActivityView
Ā is proven within the heart area. For the compact view, the main facet shows a water drop emoji, whereas the trailing facet adjustments dynamically based mostly on the progress: if the each day objective is reached, a inexperienced checkmark seems; in any other case, a small round indicator exhibits what number of glasses are left. Within the minimal view, solely the water drop emoji is displayed.
Lastly, letās add some preview code to render the preview of the Stay Exercise:
#Preview("Notification", as: .content material, utilizing: WaterReminderWidgetAttributes.preview) {
WaterReminderWidgetLiveActivity()
} contentStates: {
WaterReminderWidgetAttributes.ContentState.pattern
WaterReminderWidgetAttributes.ContentState.goalReached
}
#Preview("Dynamic Island", as: .dynamicIsland(.expanded), utilizing: WaterReminderWidgetAttributes.preview) {
WaterReminderWidgetLiveActivity()
} contentStates: {
WaterReminderWidgetAttributes.ContentState(currentGlasses: 3, dailyGoal: 8)
WaterReminderWidgetAttributes.ContentState(currentGlasses: 8, dailyGoal: 8)
}
#Preview("Dynamic Island Compact", as: .dynamicIsland(.compact), utilizing: WaterReminderWidgetAttributes.preview) {
WaterReminderWidgetLiveActivity()
} contentStates: {
WaterReminderWidgetAttributes.ContentState(currentGlasses: 5, dailyGoal: 8)
WaterReminderWidgetAttributes.ContentState(currentGlasses: 8, dailyGoal: 8)
}
Xcode permits you to preview the Stay Exercise in numerous states without having to run the app on a simulator or an actual system. By establishing a number of preview snippets, you possibly can rapidly take a look at how the Stay Exercise will look on each the Lock Display and the Dynamic Island.

Managing Stay Actions
Now that weāve put together the view of the reside exercise, whatās left is to set off it when the person faucets the Add Glass button. To make our code extra organized, we’ll create a helper class known as LiveActivityManager
to managing the reside exercise cycle.
import Basis
import ActivityKit
import SwiftUI
@Observable
class LiveActivityManager {
personal var liveActivity: Exercise?
var isLiveActivityActive: Bool {
liveActivity != nil
}
// MARK: - Stay Exercise Administration
func startLiveActivity(currentGlasses: Int, dailyGoal: Int) {
guard ActivityAuthorizationInfo().areActivitiesEnabled else {
print("Stay Actions usually are not enabled")
return
}
// Finish any present exercise first
endLiveActivity()
let attributes = WaterReminderWidgetAttributes(activityName: "Water Reminder")
let contentState = WaterReminderWidgetAttributes.ContentState(
currentGlasses: currentGlasses,
dailyGoal: dailyGoal
)
do {
liveActivity = attempt Exercise.request(
attributes: attributes,
content material: ActivityContent(state: contentState, staleDate: nil),
pushType: nil
)
print("Stay Exercise began efficiently")
} catch {
print("Error beginning reside exercise: (error)")
}
}
func updateLiveActivity(currentGlasses: Int, dailyGoal: Int) {
guard let liveActivity = liveActivity else { return }
Activity {
let contentState = WaterReminderWidgetAttributes.ContentState(
currentGlasses: currentGlasses,
dailyGoal: dailyGoal
)
await liveActivity.replace(ActivityContent(state: contentState, staleDate: nil))
print("Stay Exercise up to date: (currentGlasses)/(dailyGoal)")
}
}
func endLiveActivity() {
guard let liveActivity = liveActivity else { return }
Activity {
await liveActivity.finish(nil, dismissalPolicy: .rapid)
self.liveActivity = nil
print("Stay Exercise ended")
}
}
}
The code works with WaterReminderWidgetAttributes
that now we have outlined earlier for managing the state of the reside exercise.
When a brand new Stay Exercise begins, the code first checks whether or not Stay Actions are enabled on the system and clears out any duplicates. It then configures the attributes and makes use of theĀ request
Ā technique to ask the system to create a brand new Stay Exercise.
Updating the Stay Exercise is simple: you merely replace the content material state of the attributes and name theĀ replace
technique on the Stay Exercise object.
Lastly, the category features a helper technique to finish the presently lively Stay Exercise when wanted.
Utilizing the Stay Exercise Supervisor
With the reside exercise supervisor arrange, we will now replace the WaterTracker
class to work with it. First, declare a property to carry the LiveActivityManager
object within the class:
let liveActivityManager = LiveActivityManager()
Subsequent, replace the addGlass()
technique like this:
func addGlass() {
guard currentGlasses < dailyGoal else { return }
currentGlasses += 1
if currentGlasses == 1 {
liveActivityManager.startLiveActivity(currentGlasses: currentGlasses, dailyGoal: dailyGoal)
} else {
liveActivityManager.updateLiveActivity(currentGlasses: currentGlasses, dailyGoal: dailyGoal)
}
}
When the button is tapped for the primary time, we name the startLiveActivity
technique to start out a reside exercise. For subsequent faucets, we merely replace the content material states of the reside exercise.
The reside exercise ought to be ended when the person faucets the reset button. Subsequently, replace the resetDaily
technique like beneath:
func resetDaily() {
currentGlasses = 0
liveActivityManager.endLiveActivity()
}
Thatās it! Weāve accomplished all of the code adjustments.
Updating Data.plist to Allow Stay Actions
Earlier than your app can execute Stay Actions, now we have so as to add an entry known as Helps Stay Actions within the Data.plist
file of the principle app. Set the worth to YES to allow Stay Actions.

Nice! At this level, you possibly can check out Stay Actions both within the simulator or straight on an actual system.

Abstract
On this tutorial, we explored the way to add Stay Actions to SwiftUI apps. You’ve got realized how these options enhance person engagement by delivering real-time info on to the Lock Display and the Dynamic Island, lowering the necessity for customers to reopen your app. We lined all the course of, together with creating the info mannequin, designing the person interface, and managing the Stay Exercise lifecycle. We encourage you to combine Stay Actions into your present or future functions to supply a richer, extra handy person expertise.