23 C
Canberra
Wednesday, October 22, 2025

Creating Dwell Actions in SwiftUI Apps


Dwell Actions, first launched in iOS 16, are one in every of Apple’s most fun updates for creating apps that really feel extra related to customers in actual time. As a substitute of requiring customers to consistently reopen an app, Dwell Actions let info stay seen proper on the Lock Display screen 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 vital updates only a look away.

Later in iOS 17, Apple expanded Dwell 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, Dwell Actions are extremely helpful for client-side apps that wish to enhance engagement and supply well timed suggestions.

On this tutorial, we’ll discover methods to implement Dwell Actions by constructing a Water Tracker app. The app permits customers to log their every day water consumption and immediately see their progress replace on the Lock Display screen or Dynamic Island. By the top of the tutorial, you may perceive methods to combine Dwell Actions into your SwiftUI apps.

A Fast Take a look at the Demo App

liveactivities-demo-app

Our demo app, Water Tracker, is a straightforward and enjoyable strategy to preserve monitor of your every day water consumption. You’ve in all probability heard the recommendation that ingesting eight glasses of water a day is an efficient behavior, and this app helps you keep conscious of that objective. The design is minimal on objective: there is a round progress bar displaying 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 somewhat extra.

Behind the scenes, the app makes use of a WaterTracker class to handle the logic. This class retains monitor of what number of glasses you’ve already logged and what your every 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 Dwell Actions assist to the app. As soon as applied, customers will be capable of see their progress straight on the Lock Display screen and within the Dynamic Island. The Dwell Exercise will present the present water consumption alongside the every day objective in a transparent, easy manner.

liveactivities-lockscreen-island.png

Dwell 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 undertaking.

On this demo, the undertaking is named WaterReminder. To create the extension, choose the undertaking 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 examine the Embrace Dwell Exercise possibility.

liveactivities-add-widget.png

When Xcode asks, you’ll want to activate the brand new scheme. It’ll then generate the widget extension for you, which seems as a brand new folder within the undertaking navigator together with the starter code for the Dwell Exercise and the widget.

We’ll be rewriting the complete WaterReminderWidgetLiveActivity.swift file from scratch, so it’s finest to filter out all of its current code earlier than continuing.

For the reason that Dwell Exercise doesn’t depend on the widget, you’ll be able to 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 Dwell Exercise. We’ve to undertake the protocol and outline the dynamic content material of the exercise.

Since this attributes construction is normally shared between each the primary app and widget extension, I counsel to create a shared folder to host this Swift file. Within the undertaking 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 information displayed within the Dwell Exercise—particularly, the variety of glasses consumed and the every day objective.

The extensions are used for SwiftUI previews, offering pattern information for visualization.

Please take word that the goal membership of the file needs to be accessed by each the primary app and the widget extension. You’ll be able to confirm it within the file inspector.

liveactivities-shared-target-membership.png

Implementing the Dwell Exercise View

Subsequent, let’s implement the reside exercise view, which handles the consumer 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 primary interface of the Dwell Exercise, which seems on each the Lock Display screen and the Dynamic Island. It shows a progress bar to visualise water consumption, together with the present variety of glasses consumed and the every 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 by means of
                DynamicIslandExpandedRegion(.middle) {
                    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 Dwell Exercise widget configuration for the app. In different phrases, you configure how the reside exercise ought to seem underneath completely different configurations.

To maintain it easy, we show the identical reside exercise view on the Lock Display screen and Dynamic Island.

The dynamicIsland closure specifies how the Dwell Exercise ought to look contained in the Dynamic Island. Within the expanded view, the identical WaterReminderLiveActivityView is proven within the middle 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 every day objective is reached, a inexperienced checkmark seems; in any other case, a small round indicator reveals 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 Dwell 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 helps you to preview the Dwell Exercise in numerous states while not having to run the app on a simulator or an actual gadget. By establishing a number of preview snippets, you’ll be able to shortly check how the Dwell Exercise will look on each the Lock Display screen and the Dynamic Island.

Managing Dwell Actions

Now that we’ve put together the view of the reside exercise, what’s left is to set off it when the consumer 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: - Dwell Exercise Administration
    
    func startLiveActivity(currentGlasses: Int, dailyGoal: Int) {
        guard ActivityAuthorizationInfo().areActivitiesEnabled else {
            print("Dwell Actions usually are not enabled")
            return
        }
        
        // Finish any current 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("Dwell Exercise began efficiently")
        } catch {
            print("Error beginning reside exercise: (error)")
        }
    }
    
    func updateLiveActivity(currentGlasses: Int, dailyGoal: Int) {
        guard let liveActivity = liveActivity else { return }
        
        Job {
            let contentState = WaterReminderWidgetAttributes.ContentState(
                currentGlasses: currentGlasses,
                dailyGoal: dailyGoal
            )
            
            await liveActivity.replace(ActivityContent(state: contentState, staleDate: nil))
            print("Dwell Exercise up to date: (currentGlasses)/(dailyGoal)")
        }
    }
    
    func endLiveActivity() {
        guard let liveActivity = liveActivity else { return }
        
        Job {
            await liveActivity.finish(nil, dismissalPolicy: .instant)
            self.liveActivity = nil
            print("Dwell Exercise ended")
        }
    }

}

The code works with WaterReminderWidgetAttributes that we’ve outlined earlier for managing the state of the reside exercise.

When a brand new Dwell Exercise begins, the code first checks whether or not Dwell Actions are enabled on the gadget 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 Dwell Exercise.

Updating the Dwell Exercise is easy: you merely replace the content material state of the attributes and name the replacetechnique on the Dwell Exercise object.

Lastly, the category features a helper technique to finish the at present energetic Dwell Exercise when wanted.

Utilizing the Dwell 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 needs to be ended when the consumer faucets the reset button. Due to this fact, replace the resetDaily technique like beneath:

func resetDaily() {
    currentGlasses = 0
    
    liveActivityManager.endLiveActivity()
}

That’s it! We’ve accomplished all of the code adjustments.

Updating Information.plist to Allow Dwell Actions

Earlier than your app can execute Dwell Actions, we’ve so as to add an entry known as Helps Dwell Actions within the Information.plist file of the primary app. Set the worth to YES to allow Dwell Actions.

liveactivities-infoplist.png

Nice! At this level, you’ll be able to check out Dwell Actions both within the simulator or straight on an actual gadget.

liveactivities-dynamic-island.png

Abstract

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

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

[td_block_social_counter facebook="tagdiv" twitter="tagdivofficial" youtube="tagdiv" style="style8 td-social-boxed td-social-font-icons" tdc_css="eyJhbGwiOnsibWFyZ2luLWJvdHRvbSI6IjM4IiwiZGlzcGxheSI6IiJ9LCJwb3J0cmFpdCI6eyJtYXJnaW4tYm90dG9tIjoiMzAiLCJkaXNwbGF5IjoiIn0sInBvcnRyYWl0X21heF93aWR0aCI6MTAxOCwicG9ydHJhaXRfbWluX3dpZHRoIjo3Njh9" custom_title="Stay Connected" block_template_id="td_block_template_8" f_header_font_family="712" f_header_font_transform="uppercase" f_header_font_weight="500" f_header_font_size="17" border_color="#dd3333"]
- Advertisement -spot_img

Latest Articles