In this post, we will learn how to create a simple Pomodoro timer using user notifications. The goal is to start a 25-minute timer, and when it expires, we will see a notification on the home screen.
We follow these steps:
- Define the user permission for local notifications.
- Take a look at the user notification properties.
- Implement the app.
Privacy and Permissions
First of all, we have to add the “Privacy – User Notifications Usage Description” to the Info.plist to define the reason for the user notification.
UserNotification
Second, how to declare a UserNotification:
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in if success { let content = UNMutableNotificationContent() content.title = "Pomodoro elapsed" content.subtitle = "Take a break" content.sound = UNNotificationSound.default let trigger = UNTimeIntervalNotificationTrigger(timeInterval: Double(25 * 60), repeats: false) let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger) UNUserNotificationCenter.current().add(request) } else if let error { print(error.localizedDescription) } }
The first step is to request authorization. This will display a message asking for the user’s approval (only the first time, if the user accepts). If we get authorization, we create the content of the notification with a title, subtitle, and default sound. Next, we create a timer for the notification, create the request for the notification, and add it to the notification center. Note that we create a unique identifier for the notification using UUID().uuidString.
The pomodoro App
The last step is to add a timer to count the elapsed time (for the Pomodoro) and put everything together.
import SwiftUI import UserNotifications struct ContentView: View { @State var timer: Timer.TimerPublisher = Timer.publish(every: 1, on: .main, in: .common) let pomodoroLength = 25 * 60 @State var counter = 0 @State var timeString = "25:00" @State var hasPermission = false var body: some View { VStack(spacing: 10) { Button("Start pomodoro") { if hasPermission { counter = 0 timeString = "25:00" setTimer() timer.connect() let content = UNMutableNotificationContent() content.title = "Pomodoro elapsed" content.subtitle = "Take a break" content.sound = UNNotificationSound.default let trigger = UNTimeIntervalNotificationTrigger(timeInterval: Double(pomodoroLength), repeats: false) let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger) UNUserNotificationCenter.current().add(request) } } Text("\(timeString)").onReceive(timer, perform: { _ in counter += 1 let remaining = pomodoroLength - counter let minutes = remaining / 60 let secs = remaining % 60 timeString = "\(minutes):\(secs)" if counter == pomodoroLength { timer.connect().cancel() } }) }.onAppear { UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in if success { hasPermission = true } else if let error { print(error.localizedDescription) } } } } func setTimer() { self.timer = Timer.publish(every: 1, on: .main, in: .common) } }
We need to import UserNotifications, then define a timer, the Pomodoro length in seconds, the initial string minutes:seconds value, and a counter to count the elapsed time. We also introduce a hasPermission variable that is set when the application starts, so we don’t need to check the authorization every time.
When we tap on “Start Pomodoro”, the notification is created with its timer, and the counter is initialized. Every second, we calculate the remaining time and display it until it equals zero, then the timer is stopped. If everything is set up correctly, we should see a notification.
For testing, I suggest setting the Pomodoro time to a few seconds, but at least enough to lock the screen or switch applications (otherwise you won’t see the notification).
The code https://github.com/niqt/PomodoroApp
Note: English is not my native language, so I apologize for any errors. I use AI solely to generate the banner of the post; the content is human-generated.