swiftuilocalnotificationnsdatecomponents

Is it possible to have two DateCompontents for one Local Notification?


I am trying to set two different times for Local Notifications - the default one being 10 am every day (if the user does not touch the date picker to pick their own notification time AKA it is nil), and one based on the user's input. The user input one does work and sends notifications every day, but if no time is chosen the default time does not work.

This is my code, can someone tell me what I am doing wrong? I basically want to check that if the user input is NIL it should revert to the default time set.

In settings:

IBOutlet var userDatePicker: UIDatePicker!

IBAction func pickerValueChanged(_ sender: UIDatePicker) {
    var selectedTime = Date()
    selectedTime = sender.date

    // convert to data type DateComponents
    let convertedSelectedTime = calendar.dateComponents([.hour, .minute,], from: selectedTime)
    let delegate = UIApplication.shared.delegate as? AppDelegate
    delegate?.sendNotification(with: convertedSelectedTime)
    UserDefaults.standard.set(selectedTime, forKey: "SavedTime")
}

In AppDelegate:

func sendNotification(with userSelectedTime: DateComponents?) {
    let center = UNUserNotificationCenter.current()
    let content = UNMutableNotificationContent()
    content.title = "Testing"
    content.body = "testing"
    var defaultTime = DateComponents()
    defaultTime.hour = 10
    defaultTime.minute = 00`// trying to set default time to 10 am if user never picks a time`
    let components = userSelectedTime ?? defaultTime//option between the two
    let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: true)
    let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
    center.add(request)
}

Solution

  • Try this. All of its fields are optional. So you can use those you need.

    DateComponents(calendar:timeZone:era:year:month:day:hour:minute:second:nanosecond:weekday:weekdayOrdinal:quarter:weekOfMonth:weekOfYear:yearForWeekOfYear:)
    

    let triggerDaily = DateComponents(calendar: Calendar.current,
      timeZone: Calendar.current.timeZone,
      hour: 10, // Use 22 if PM 
      minute: 00,
      second: 00,
      nanosecond: 00)
    
    let trigger = UNCalendarNotificationTrigger(dateMatching: triggerDaily, repeats: false)
    

    More info

    DateComponents - Apple


    Update

    In pickerValueChanged, you are schedulling notification every time DatePicker value is changed. That is bad. There you should only store date.

    func pickerValueChanged(_ sender: UIDatePicker) {
        UserDefaults.standard.set(sender.date, forKey: "SavedTime")
    }
    

    sendNotification should only send notification.

    func sendNotification(with dateComponents: DateComponents) {
        let center = UNUserNotificationCenter.current()
        let content = UNMutableNotificationContent()
        content.title = "Testing"
        content.body = "testing"
        let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
        let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
        center.add(request)
    }
    

    UIApplication has alife method that is called when an App goes to background. You can schedule notifications there.

    func applicationDidEnterBackground(_ application: UIApplication) {
        let date = UserDefaults.standard.value(forKey: "SavedTime") as? Date
        if let date = date {
            let convertedSelectedTime = Calendar.current.dateComponents([.hour, .minute,], from: date)
            sendNotification(with: convertedSelectedTime)
        } else {
            let dateComponent = DateComponents(calendar: Calendar.current,
              timeZone: Calendar.current.timeZone,
              hour: 10, // Use 22 if PM
              minute: 00,
              second: 00,
              nanosecond: 00)
           sendNotification(with: dateComponent)
        }
    }
    

    Use SavedTime if it exists in UserDefaaults. Else use 10:00.


    If you use SceneDelegate, use sceneDidEnterBackground instead of applicationDidEnterBackground

    func sceneDidEnterBackground(_ scene: UIScene) {
    
    }
    

    They both are life-cycle methods.

    More info.

    Managing Your App's Life Cycle

    UIApplication