How to schedule multiple local notifications in iOS swift?

160 views Asked by At

I am working on a medicine reminder application, where user can add medicine reminders for either daily, weekly or alternate days and select a time for the reminder. It was a straight forward implementation for daily and weekly but I am struggling to find a solution for alternate days as Apple doesn't provide any option to pass in frequency value with Calendar Notification Trigger. I have searched all over the internet but haven't found any solution. So far what I have done is if user select alternate days for that reminder, I am creating multiple notifications in a loop. But only my first notification in the loop is getting fired and if change the system date and time to check the next notification, it didn't work. I am adding my code here, someone please correct me if I am doing anything wrong.

  func scheduleNotification(_ notification: Notification) {
    let content = UNMutableNotificationContent()
    content.body = Constants.notificationDescription
    content.title = notification.title
    content.sound = .default
    content.userInfo = ["id":notification.id]
    content.categoryIdentifier = Constants.notificaitonCategoryIdentifier
    
    // Notification Actions
    let takenAction = UNNotificationAction(identifier: Constants.notiTakenActionIdentifier, title: Constants.notificationTakenText)
    let notTakenAction = UNNotificationAction(identifier: Constants.notiNotTakenActionIdentifier, title: Constants.notificationNotTakenText)
    let actionCategory = UNNotificationCategory(identifier: Constants.notificaitonCategoryIdentifier, actions: [takenAction, notTakenAction], intentIdentifiers: [])
    UNUserNotificationCenter.current().setNotificationCategories([actionCategory])
    var dateComponents = DateComponents()
    if notification.frequency == .daily {
      dateComponents.hour = notification.hour
      dateComponents.minute = notification.min
      let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
      let nextTriggerDate = trigger.nextTriggerDate()
      let request = UNNotificationRequest(
        identifier: notification.id,
        content: content,
        trigger: trigger)
      UNUserNotificationCenter.current().add(request)
    } else if notification.frequency == .weekly {
      dateComponents.hour = notification.hour
      dateComponents.minute = notification.min
      dateComponents.weekday = notification.weekDay
      let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
      let nextTriggerDate = trigger.nextTriggerDate()
      let request = UNNotificationRequest(
        identifier: notification.id,
        content: content,
        trigger: trigger)
      UNUserNotificationCenter.current().add(request)
    } else if notification.frequency == .alternateDay {
      let firstDate = notification.date
      for i in stride(from: 0, to: 30, by: 2) {
        guard let date = Calendar.current.date(byAdding: .day, value: i, to: firstDate) else {return}
        let dateComponents = date.getDateComponents()
        print(date)
        let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
        /*let timeInterval = Double(120 + (i * 120))
        print(timeInterval)
        let timeTrigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: false)
        print(timeTrigger.nextTriggerDate())*/
        let request = UNNotificationRequest(
          identifier: notification.id + String(i),
          content: content,
          trigger: trigger)
        UNUserNotificationCenter.current().add(request)
      }
      self.getPendingRequests()
    }
  }

Here I am using stride to add 2 days in every iteration and when I check the dates with print statement all the dates are printed and are correct but the notification is not coming up except for the first one. Thanks in advance.

EDIT:

extension Date {
    public func getDateComponents() -> DateComponents {
        let dateComponents = Calendar.current.dateComponents([.hour, .minute, .second, .day], from: self)
        
        return dateComponents
    }
}
1

There are 1 answers

2
deniz On

You are generally on the right track but there are a couple of problems with your implementation for the alternate days.

  1. I assume that you want the alternate day medicine reminders also to repeat, just like daily and weekly reminders you have. If this is correct, even if your example code worked, the alternate days reminder would trigger for 15 times and then stop.
  2. I think another problem is that you are using Date.getDateComponents() extension function to obtain the date components. Since you didn't provide how that was implemented, there might be a problem there. According to the documentation, you should only provide those date components that are relevant for the trigger. It is possible that you are not returning the correct set of components or returning more than you need. This is probably why it is not working.
  3. Even if your extension function Date.getDateComponents() fetched the correct date components, it would be extremely difficult to implement a repeating Gregorian calendar-based notification trigger. This is because for every other day frequency, there is almost nothing repeating: vis-a-vis, neither days of the week, nor the days of the month are repeating for every-other-day. Some months you will be on even days and some months you will be on odd days. You almost need a special calendar where each week is defined as 14 days. In that hypothetical calendar, the days-of-the-week would be repeating.

Where do we go from here? The way I see it you have 2 realistic options.

  1. You calculate the calendar days for the alternate-day frequency for the next n years (you define n). And submit a one-off/non-repeating request to UNUserNotificationCenter for each day in your calculation. However, there might be some limits to this. OS might not let you schedule that many one-off/non-repeating notifications into the future to prevent abuse from various apps. So you have to work around this limitation, if exists.
  2. You may have noticed that UNUserNotificationCenter.add(request) function takes an optional completion handler. In your example, you are passing nil to that. You can write that completion handler such that when the completion handler fires, your code submits the next scheduled trigger to UNUserNotificationCenter. It is like daisy chaining. In this scenario, you may need to handle the scenarios when the scheduled notification does not fire, for example, when the user's device is off. But this approach might get you closer to what you were looking for.