Daily Reminder Feature using DatePicker and Local Notification in SwiftUI

Kevin Jonathan
5 min readDec 2, 2023

Let’s say you want to implement a daily reminder or something similar within your app with local notification. That’s definitely possible in SwiftUI, and it’s easier than you think!

What is Local Notification?

Local notification is basically a notification system that we can implement directly within the app, and without relying on a server. It’s ideal for things like a daily reminder, etc.

The major difference between local notification and remote notification is that in remote notification, we rely on our server backend to send a notification to a specific device token. For local notifications, we can either send a one-time-only notification, or a repeated notification.

The Main Idea

There are things to consider while implementing local notification.

  1. Does the notification repeat? be it daily, weekly, or monthly? Or one-time only?
  2. If it’s repeated, can the user set their own schedule? And if it’s not repeated, on what occasion will the notification arrive to the user?
  3. If the notifications are repeated and the user can set their schedule, how can they set their own schedule?

That’s it! So from the scenario above, if we want to create a daily reminder feature:

  1. The notification is repeated daily.
  2. Users can set their own schedule for the daily reminder.
  3. They have to use the DatePicker provided within the app to set their own schedule.

Implementation

Let’s say you already have a working app (without the daily reminder feature), in this case, we can create the setting page first to set up the daily reminder feature. This is the code snippet of my sample app, FeelJournal. I have put comments on the code too so that it can be easily understood.

//
// SettingsView.swift
// FeelJournal
//
// Created by Kevin Jonathan on 25/03/23.
//

import SwiftUI

struct SettingsView: View {
// Save the state of the toggle on/off for daily reminder
@AppStorage("isScheduled") var isScheduled = false
// Save the notification time set by user for daily reminder
@AppStorage("notificationTimeString") var notificationTimeString = ""

var body: some View {
List {
Section(header: Text("Journal Reminder")) {
// The toggle if the user want to use daily reminder feature
Toggle("Daily Reminder", isOn: $isScheduled)
.tint(.indigo)
.onChange(of: isScheduled) { isScheduled in
handleIsScheduledChange(isScheduled: isScheduled)
}

// Show the date picker if the daily reminder feature is on
if isScheduled {
DatePicker("Notification Time", selection: Binding(
get: {
// Get the notification time schedule set by user
DateHelper.dateFormatter.date(from: notificationTimeString) ?? Date()
},
set: {
// On value set, change the notification time
notificationTimeString = DateHelper.dateFormatter.string(from: $0)
handleNotificationTimeChange()
}
// Only use hour and minute components, since this is a daily reminder
), displayedComponents: .hourAndMinute)
// Use wheel date picker style, recommended by Apple
.datePickerStyle(WheelDatePickerStyle())
.padding(.leading, 20)
.padding(.trailing, 20)
}
}
}
.navigationTitle("Settings")
.navigationBarTitleDisplayMode(.inline)
}
}

// MARK: Handler

private extension SettingsView {
// Handle if the user turned on/off the daily reminder feature
private func handleIsScheduledChange(isScheduled: Bool) {
if isScheduled {
NotificationManager.requestNotificationAuthorization()
NotificationManager.scheduleNotification(notificationTimeString: notificationTimeString)
} else {
NotificationManager.cancelNotification()
}
}

// Handle if the notification time changed from DatePicker
private func handleNotificationTimeChange() {
NotificationManager.cancelNotification()
NotificationManager.requestNotificationAuthorization()
NotificationManager.scheduleNotification(notificationTimeString: notificationTimeString)
}
}

// MARK: Preview

struct SettingsView_Previews: PreviewProvider {
static var previews: some View {
SettingsView()
}
}

In the code above, I used the AppStorage property wrapper to save the user preferences, and Apple’s DatePicker so that the user can adjust their own daily reminder schedule.

For the next step, let’s create a new struct called NotificationManager. This struct will help us to handle the notification system (especially the scheduling).

//
// NotificationManager.swift
// FeelJournal
//
// Created by Kevin Jonathan on 25/03/23.
//

import Foundation
import UserNotifications

struct NotificationManager {
// Request user authorization for notifications
static func requestNotificationAuthorization() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { success, error in
if success {
print("Notification authorization granted.")
} else if let error = error {
print(error.localizedDescription)
}
}
}

// Schedule daily notification at user-selected time
static func scheduleNotification(notificationTimeString: String) {
// Convert the time in string to date
guard let date = DateHelper.dateFormatter.date(from: notificationTimeString) else {
return
}

// Instantiate a variable for UNMutableNotificationContent
let content = UNMutableNotificationContent()
// The notification title
content.title = "Write in your journal"
// The notification body
content.body = "Take a few minutes to write down your thoughts and feelings."
content.sound = .default

// Set the notification to repeat daily for the specified hour and minute
let dateComponents = Calendar.current.dateComponents([.hour, .minute], from: date)
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)

// We need the identifier "journalReminder" so that we can cancel it later if needed
// The identifier name could be anything, up to you
let request = UNNotificationRequest(identifier: "journalReminder", content: content, trigger: trigger)

// Schedule the notification
UNUserNotificationCenter.current().add(request)
}

// Cancel any scheduled notifications
static func cancelNotification() {
// Cancel the notification with identifier "journalReminder"
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["journalReminder"])
}
}

And the DateHelper class.

//
// DateHelper.swift
// FeelJournal
//
// Created by Kevin Jonathan on 25/03/23.
//

import Foundation

struct DateHelper {
// The universally used DateFormatter
static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm"
return formatter
}()
}

Source: https://developer.apple.com/documentation/usernotifications/scheduling_a_notification_locally_from_your_app

For the last steps, create an AppDelegate so that we can “delegate” the notification feature.

//
// AppDelegate.swift
// FeelJournal
//
// Created by Kevin Jonathan on 15/03/23.
//

import UIKit
import UserNotifications

class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
UNUserNotificationCenter.current().delegate = self
return true
}
}

And don’t forget to use your newly created AppDelegate in your App struct to ensure that the notifications can arrive to the user.

//
// FeelJournalApp.swift
// FeelJournal
//
// Created by Kevin Jonathan on 22/10/22.
//

import SwiftUI

@main
struct FeelJournalApp: App {
// Add this property wrapper
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate

var body: some Scene {
WindowGroup {
// ...
}
}
}

The Result

Is it working? Definitely yes! Check out below (recorded using QuickTime and converted to GIF)!

That’s all about the daily reminder feature! Do note that my code isn’t perfect (in both readability and implementation) and there might be better solutions out there. I am still learning SwiftUI and currently experimenting with it (especially after the very recent Apple WWDC, with their newly introduced framework “SwiftData”). I hope this article can be useful to you.

Thank you for reading!

--

--

Kevin Jonathan

Just a student intricately weaving personal life experience and technology related stuffs, currently navigating the intersections of life.