Create Dynamic Notifications on Apple Watch with SwiftUI And WatchKit App Delegate

Hamidreza Farzi
4 min readJan 6, 2021

I wanted to show a dynamic notification on a standalone watchOS App, and none of the available articles about this worked for me! So I decided to write this article, so if your dynamic notification doesn’t appear on apple watch read this article.

what we are going to do? first of all, creating a project; then , setting up local notifications, setting up dynamic views; and finally, showing dynamic notifications.

Set Up an Apple Watch project:

open Xcode and select “create a new Xcode project”, select “WatchOS” Tap and “Watch App” and then click on “Next”.

On the following page select your Product Name & Team & Organization Identifier, for Interface choose “SwiftUI”, Life Cycle should be “WatchKit App Delegate” and don’t forget to check the checkbox for “Include Notification Scene”.

And then select a location for your project and click on “Create”.

Set Up Local Notifications:

We are going to have two buttons in first view, one for getting user’s permission and the other for scheduling local notification, so our first view should be like this:

import SwiftUIimport UserNotificationsstruct ContentView: View {var body: some View {VStack{Button("Request permission"){UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.sound,.badge]) { (success, error) inif success{print("All set")} else if let error = error {print(error.localizedDescription)}}}Button("Schedule Notification"){let content = UNMutableNotificationContent()content.title = "Drink some milk!"content.subtitle = "you have 10 sec"content.sound = .defaultcontent.categoryIdentifier = "myCategory"let category = UNNotificationCategory(identifier: "myCategory", actions: [], intentIdentifiers: [], options: [])UNUserNotificationCenter.current().setNotificationCategories([category])let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)let request = UNNotificationRequest(identifier: "milk", content: content, trigger: trigger)UNUserNotificationCenter.current().add(request) { (error) inif let error = error{print(error.localizedDescription)}else{print("scheduled successfully")}}}}}}

Where we want to create the notification content we should also set the notification category, category identifier in this section should be equal with category name in “PushNotificationPayload.apns” file:

Set Up Dynamic Views

Now let’s set up our dynamic views for notification. you can use “NotificationView.swift” file to set up dynamic view, currently we are showing a counter for drink some milk in this view:

import SwiftUIstruct NotificationView: View {@Environment(\.presentationMode) var presentationMode@State private var ringAnimation: CGFloat = 0@State private var counter = 10var count = 10var body: some View {VStack{Text("Drink some milk!").font(.subheadline)ZStack{//MARK: - Background CircleCircle().stroke(Color.white.opacity(0.1), style: StrokeStyle(lineWidth: 15))//MARK: - Progress CircleCircle().trim(from: ringAnimation, to: 1.0).stroke(LinearGradient(gradient: Gradient(colors: [Color( colorLiteral(red: 0.2588235438, green: 0.7568627596, blue: 0.9686274529, alpha: 1)), Color( colorLiteral(red: 0.2666666667, green: 0.8431372549, blue: 0.7137254902, alpha: 1))]), startPoint: .topTrailing, endPoint: .bottomLeading),style: StrokeStyle(lineWidth: 15, lineCap: .round)).rotationEffect(Angle(degrees: 90)).rotation3DEffect(Angle(degrees: 180), axis: (x: 1, y: 0, z: 0)).shadow(color: Color( colorLiteral(red: 0.2666666667, green: 0.8431372549, blue: 0.7137254902, alpha: 1)).opacity(0.3), radius: 3, x: 0, y: 3).animation(.easeInOut)//MARK: - PercentageText("🥛\n\(counter)").font(.title3).multilineTextAlignment(.center)}.padding().onAppear{updateRing()}.frame(minWidth: 100, idealWidth: 110, maxWidth: .infinity, minHeight: 100, idealHeight: 110, maxHeight: .infinity, alignment: .center)}}func updateRing(){if counter == 0 {self.presentationMode.wrappedValue.dismiss()ringAnimation = 10return}else{DispatchQueue.main.asyncAfter(deadline: (.now() + 1)) {ringAnimation += CGFloat(1) / CGFloat(count)counter -= 1updateRing()}}}}

Perfect! but still, our dynamic notification doesn’t appear, so there is one more step.

Showing Dynamic Notifications

open “Interface.storyboard”:

there are two notification scene one is static and the other is dynamic, for now we are only showing the static notification, for showing dynamic notification, we should activate the segue between them, in order to do that, click on arrow that placed on the left side of “Static Notification Interface”, and from attribute inspector section check the checkbox for “Has Dynamic Interface” to activate segue between static and dynamic notification scene.

Also I should mention, here in Name textfield you should set the exact name that you have previously select for category identifier, otherwise your dynamic notification will not appear.

Congratulations, Now you have setup a dynamic notification like this👇

Tell me if this article is helpful or not, and if your problem still exist, let me know in comment section.