Запланируйте локальное уведомление каждые n дней (безопасный часовой пояс)

#ios #swift #uilocalnotification #nsdatecomponents #unusernotificationcenter

Вопрос:

Я полагаю, что об этом спрашивали несколько раз, но четкого ответа нет.

Существует два способа запланировать временные уведомления: UNCalendarNotification и UNTimeIntervalNotificationTrigger .

Планирование уведомления на определенное время и день недели тривиально, как и в определенный день месяца, но планирование на определенное время каждые n дней не так тривиально.

Например, каждые 5 дней в 11:00.

UNTimeIntervalNotificationTrigger может показаться, что это подходящий класс для поиска, за исключением проблем, возникающих при переходе на летнее время или изменении часового пояса. Например, летнее время заканчивается, и теперь ваше уведомление в 10:00 вместо 11:00.

day Свойство DateComponents класса вместе с UNCalendarNotification классом может содержать решение, потому что в документах указано «День или количество дней«. Я интерпретирую это как «Определенный день месяца (день) или n дней (количество дней)».

Углубившись в day документы свойств, он читает: «Это значение интерпретируется в контексте календаря, в котором оно используется».

Как вы можете использовать day свойство вместе с контекстом календаря для работы с количеством дней вместо конкретных дней месяца?

Кроме того, документы для свойств hour и minute в DateComponents также читают «Час или количество часов» и «Минута или количество минут» соответственно. Итак, даже если бы вы установили day значение «количество дней», как бы вы могли установить hour и minute правильно?

Очевидно, что эта функциональность возможна в iOS — приложение «Напоминания» является доказательством этого.

Комментарии:

1. Каждые n дней, исходя из какой даты? И нет никакой разницы, пока вы придерживаетесь одного и того же часового пояса или просто не устанавливаете разницу во времени вообще.

2. @ElTomato Спасибо за ваш комментарий, да, «каждые n дней» нужна дата привязки, но как вы устанавливаете дату привязки? Я понимаю calendar , что свойство может быть включено DateComponents , но меняет ли это контекст при установке day свойства с «день» на «количество дней»?

3. Видеть date(byAdding:to:options:) . Вы можете использовать .day .

Ответ №1:

Вы можете настроить их заранее, используя UNCalendarNotificationTrigger n несколько раз и используя скорректированный календарь для текущего часового пояса

 import SwiftUI

class NotificationManager: NSObject, UNUserNotificationCenterDelegate{
    static let shared: NotificationManager = NotificationManager()
    let notificationCenter = UNUserNotificationCenter.current()
    
    private override init(){
        super.init()
        requestNotification()
        notificationCenter.delegate = self
        getnotifications()
    }
    
    func requestNotification() {
        print(#function)
        notificationCenter.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
            
            if let error = error {
                // Handle the error here.
                print(error)
            }
            
            // Enable or disable features based on the authorization.
        }
    }
    /// Uses [.day, .hour, .minute, .second] in current timeZone
    func scheduleCalendarNotification(title: String, body: String, date: Date, repeats: Bool = false, identifier: String) {
        print(#function)
        
        let content = UNMutableNotificationContent()
        content.title = title
        content.body = body
        
        let calendar = NSCalendar.current

        let components = calendar.dateComponents([.day, .hour, .minute, .second], from: date)
        
        let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: repeats)
        
        let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
        notificationCenter.add(request) { (error) in
            if error != nil {
                print(error!)
            }
        }
    }
    ///Sets up multiple calendar notification based on a date
    func recurringNotification(title: String, body: String, date: Date, identifier: String, everyXDays: Int, count: Int){
        print(#function)
        for n in 0..<count{
            print(n)
            let newDate = date.addingTimeInterval(TimeInterval(60*60*24*everyXDays*n))
            //Idenfier must be unique so I added the n
            scheduleCalendarNotification(title: title, body: body, date: newDate, identifier: identifier   n.description)
            print(newDate)
        }
    }
    ///Prints to console schduled notifications
    func getnotifications(){
        notificationCenter.getPendingNotificationRequests { request in
            for req in request{
                if req.trigger is UNCalendarNotificationTrigger{
                    print((req.trigger as! UNCalendarNotificationTrigger).nextTriggerDate()?.description ?? "invalid next trigger date")
                }
            }
        }
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        
        completionHandler(.banner)
    }
}
class ZuluNotTriggerViewModel:NSObject, ObservableObject, UNUserNotificationCenterDelegate{
    @Published var currentTime: Date = Date()
    let notificationMgr = NotificationManager.shared
    
    
    ///Sets up multiple calendar notification based on a date
    func recurringNotification(title: String, body: String, date: Date, identifier: String, everyXDays: Int, count: Int){
        print(#function)
        notificationMgr.recurringNotification(title: title, body: body, date: date, identifier: identifier, everyXDays: everyXDays, count: count)
        
        //just for show now so you can see the current date in ui
        self.currentTime = Date()
    }
    ///Prints to console schduled notifications
    func getnotifications(){
        notificationMgr.getnotifications()
    }
    
}
struct ZuluNotTriggerView: View {
    @StateObject var vm: ZuluNotTriggerViewModel = ZuluNotTriggerViewModel()
    var body: some View {
        VStack{
            Button(vm.currentTime.description, action: {
                vm.currentTime = Date()
            })
            Button("schedule-notification", action: {
                let twoMinOffset = 120
                //first one will be in 120 seconds
                //gives time to change settings in simulator
                //initial day, hour, minute, second
                let initialDate = Date().addingTimeInterval(TimeInterval(twoMinOffset))
                //relevant components will be day, hour minutes, seconds
                vm.recurringNotification(title: "test", body: "repeat body", date: initialDate, identifier: "test", everyXDays: 2, count: 10)
            })
            
            Button("see notification", action: {
                vm.getnotifications()
            })
        }
    }
}

struct ZuluNotTriggerView_Previews: PreviewProvider {
    static var previews: some View {
        ZuluNotTriggerView()
    }
}
 

Комментарии:

1. Привет, Лорем ипсум, спасибо за такой подробный ответ. Первоначальная награда за ответ истекла до того, как у меня появилась возможность вознаградить вас, я начал новую и намерен вознаградить ее, как только кнопка станет доступной.