2

I know similar questions appeared before but I think I need more clarification since i still don't know how to make it done. I'm a beginner programmer so please forgive me any mistakes.

I'm trying to have daily reminders for daily tasks from my app IF user didn't complete it yet, so how can i make it not show up when he had already done the task?

Solutions i found so far suggest to remove pending notification and setting up new one for future date in the same time.

I successfully set up daily notifications using this code:

    func sendDailyReminder() {
        let content = UNMutableNotificationContent()
        content.title = "Daily reminder"
        content.body = "You still have task to complete today."
        content.sound = UNNotificationSound.default
        var dateComponents = DateComponents()
        dateComponents.hour = 20
        dateComponents.minute = 00
        let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
        let request = UNNotificationRequest(identifier: "dailyTrigger", content: content, trigger: trigger)

        center.add(request) { (error) in
            if let error = error {
                print("Notification Error: ", error)
            }
        }
    }

i can also successfully remove pending notification with removePendingNotificationRequest method but how can I set it up trigger for tomorrow using dateComponents here?

Or is there any other way to achieve that? Maybe using background fetch to check if its done just before sending notification?

Some replies i found suggest that its actually impossible but then how any task or to-do app can achieve something like that?

dakota
  • 293
  • 3
  • 13
  • Couldn't you just set up a repeating notification and then when the task is done, cancel the notification? – Paulw11 Apr 18 '20 at 13:57
  • 1
    Yes, that's what i tried to do but how to set up notification again for next day? Here is an example: there is daily reminder at 8pm, user completes the task at 7pm, i cancel the notification then i have to setup new for next day and it can't be simply the same notification because then it will show up the same day anyway, right? how to set up new notification for next day? that's the part i don't know how to do – dakota Apr 18 '20 at 14:07
  • You probably need to schedule multiple notifications; one for each day of the week. Then when the task is complete you can cancel just that day's notification. – Paulw11 Apr 18 '20 at 14:21
  • As far as I understand you, you need to do two things: a) not call this method when the task is already done, and b) cancel any pending notifications for it when the task's state does from open to done. – Gereon Apr 18 '20 at 14:26
  • 1
    @ Paulw11 I thought about that, seems like a good way but then again i feel like im missing some knowledge to implement it. How to set up that missing day for next week? If there are seven recurring notification for each day and i cancel that day, in some point have to set it back again and here i'm circling back to the same issue as before. – dakota Apr 18 '20 at 14:29
  • @Gereon it's notification set up to repeat daily, how can i not call it? Yes I can cancel it but then i don't know how to set it up again for next day. Need to consider that some days user will complete the task and some day won't, also might not even open app for some days – dakota Apr 18 '20 at 14:32
  • @Paulw11 that won't work. If the app schedules the notifications and then the user finishes the task on Monday, closes the app and doesn't open it again, then the user will get notification on Tuesday, Wed, Thu, Sat, Sun. And then the app will be silent on the next Monday. – Viktor Sec Feb 04 '21 at 00:57

2 Answers2

1

I ended up using multiple notifications for each weekday but set it up in little different way:

First set up daily reminder using weekday Int as identifier

func setWeekdayReminder(weekday: Int) {
        let center = UNUserNotificationCenter.current()
        let content = UNMutableNotificationContent()
        content.title = "Daily reminder"
        content.body = "You still have some tasks to complete today."
        content.sound = UNNotificationSound.default
        var dateComponents = DateComponents()
        dateComponents.hour = 18
        dateComponents.minute = 35
        dateComponents.weekday = weekday
        let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true
        let request = UNNotificationRequest(identifier: String(weekday), content: content, trigger: trigger)

        center.add(request) { (error) in
            if let error = error {
                print("Notification Error: ", error)
            }
        }
    }

then i made a function to check if there is any missing day after users launches app (except today, so i won't request todays notification again even after removing it earlier when user completes the task)

func checkDailyReminder() {

       let currentWeekday = Calendar.current.component(.weekday, from: Date())

       center.getPendingNotificationRequests { (requests) in
            var weekdayArray : [Int] = []

            for each in requests {
               weekdayArray.append(Int(each.identifier)!)
            }
            for number in 1...7 {
                if weekdayArray.contains(number) {
                    print("weekdayArray contains weekday \(number)")
                } else {
                    print("weekdayArray doesnt contain weekday \(number)")
                    if number != currentWeekday {
                          self.setWeekdayReminder(weekday: number)
                    }
                }
            }

        }
    }

Of course it's kind of a hack and when user completes the task and somehow won't go back for a week and open it again on the same weekday then he won't get notification that day but it works for rest of the time.

dakota
  • 293
  • 3
  • 13
0

Here is what I came up with. Basically everyday the app runs, i'll run this function, to setup local notifications. It sets them up for the next 14 days. Everyday it runs, its always going to be setting up the 14th day from now. It will skip the others, they are already pending. There is also a function for canceling the notification today, once the task is completed.

func setupLocalNotifications() {
    let center = UNUserNotificationCenter.current()
    center.getNotificationSettings { setting in
        guard setting.authorizationStatus == .authorized else {
            print("Not authorized for local push notifications")
            return
        }
        
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyyMMdd"
        dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
        
        let calendar = Calendar.current
        var date = calendar.date(bySettingHour: 14, minute: 0, second: 0, of: Date())!
        center.getPendingNotificationRequests { [weak self] notificationRequests in
            (1...14).forEach { _ in
                //Capture this date.
                let thisDate = date
                
                //Prepare the next date.
                date = calendar.date(byAdding: .day, value: 1, to: date)!
                
                let identifier = dateFormatter.string(from: thisDate)
                
                guard thisDate.isPastDate == false else {
                    Log.d(TAG, "Task Date is in the past - Skipping \(identifier)")
                    return
                }
                
                // Check if we've already scheduled this identifier
                guard notificationRequests.first(where: { $0.identifier == identifier }) == nil else {
                    print("Task Already Scheduled - Skipping \(identifier)")
                    return
                }
                
                // Check if we'e already done our task for the day.
                /*
                 TODO: Special Code Guard that checks if we've already completed the task today. (I took this out because it had project specific code, but you should check your core data or whatever, to see if the task is finished for today.
                 */
                
                let content = UNMutableNotificationContent()
                
                content.title = "Title"
                content.body = "Body"
                
                let dateComponents = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: thisDate)
                
                let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
                
                let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
                
                let notificationCenter = UNUserNotificationCenter.current()
                notificationCenter.add(request) { (error) in
                    if error != nil {
                        Log.e(TAG, error)
                    }
                    print("Successfully scheduled: \(identifier)")
                }
            }
        }
    }
}

Here is the cancel task function.

func cancelTodaysNotification() {
    let center = UNUserNotificationCenter.current()
    let calendar = Calendar.current
    let dateFrom = calendar.startOfDay(for: Date())
    
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "yyyyMMdd"
    dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
    
    let identifier = dateFormatter.string(from: dateFrom)

    center.removePendingNotificationRequests(withIdentifiers: [identifier])
    
    print("Removed Local Notification for Date: \(identifier)")
    
}
Trihedron
  • 246
  • 2
  • 14
  • Obviously if they don't use the app for 14 days, they will stop getting notifications, but I think that's a good strategy! – Trihedron Apr 08 '22 at 22:39