UNCalendarNotificationTrigger 未启动

问题描述

好的 - 我现在对这段代码感到非常沮丧,准备放弃!基本上,当模拟到模拟器或实际设备时,我让 requestAuthorisation 正常工作,但触发器永远不会启动。我在网上关注了几个人,他们的代码很容易运行!当我使用按钮启动 UNTimeIntervalNotificationTrigger 时,它可以工作,但这不是我想要的。目前在 iOS 14.3 中作为构建目标进行测试。其余的应用程序构建没有问题。我究竟做错了什么?!不禁想到,在尝试让它工作的过程中,我可能已经损坏了 info.plist 或类似的东西?!我已经测试过重复触发和不重复但都不起作用。

    override func viewDidLoad() {
        super.viewDidLoad()
        
        //NOTIFICATIONS
        // Step 1 - Ask the use for permission to notify
        let randVerseCenter = UNUserNotificationCenter.current()
        randVerseCenter.requestAuthorization(options: [.alert,.sound]){ (granted,error) in
            if granted {
                print("Yay - request authorisation worked!")
            } else {
                print ("D'oH - request Authorisation did not work!")
            }
        }
        // Step 2 - Create the Notification Content
        let randVerseContent = UNMutableNotificationContent()
        randVerseContent.title = "Random Reference"
        randVerseContent.body = "Random Verse"
        randVerseContent.sound = UNNotificationSound.default
        // Step 3 - Create the trigger for the notification by delay
        let randVerseDate = Date().addingTimeInterval(30)
        let randVerseDateComponents = Calendar.current.dateComponents([.year,.month,.day,.hour,.minute,.second],from: randVerseDate)
        let randVerseTrigger = UNCalendarNotificationTrigger(dateMatching: randVerseDateComponents,repeats: true)
        // Step 4 - Creating the request
        let randVerseUUIDString = UUID().uuidString
        let randVerseRequest = UNNotificationRequest(identifier: randVerseUUIDString,content: randVerseContent,trigger: randVerseTrigger)
        // Step 5 - Register the request
        randVerseCenter.add(randVerseRequest) { (error) in
            if let error = error{
                print (error.localizedDescription)
            }
            //Check the error parameter and handle any errors
        }
    }

解决方法

问题在于触发器是如何创建的。我们可以查看 UNCalendarNotificationTrigger 的文档以获得更多理解:

在需要时创建一个 UNCalendarNotificationTrigger 对象 安排在指定日期发送本地通知 和时间。您可以使用 NSDateComponents 对象,它允许您仅指定时间值 这对你来说很重要。系统使用提供的信息来 确定下一个与指定匹配的日期和时间 信息。

https://developer.apple.com/documentation/usernotifications/uncalendarnotificationtrigger

因此,当您想要创建触发器以匹配日期组件时,您可以使用 UNCalendarNotificationTrigger。下面的代码将创建一个触发器,它会在每天早上 8:30 发送通知,因为指定了 .hour.minute 组件:

    var date = DateComponents()
    date.hour = 8
    date.minute = 30 
    // This trigger will match these two components - hour and minute
    let trigger = UNCalendarNotificationTrigger(dateMatching: date,repeats: true)

就您而言,您使用日期的所有组件(年、月、日、小时、分钟、秒)创建了一个触发器:

let randVerseDateComponents = Calendar.current.dateComponents([.year,.month,.day,.hour,.minute,.second],from: randVerseDate)

这使得重复触发成为不可能的条件——因为不会再有 2021 年了——所以它不会被触发。

您需要考虑如何触发此通知。如果您打算在特定时间的同一秒内发送通知,则您必须仅使用 .second 日期组件:

let randVerseDateComponents = Calendar.current.dateComponents([.second],from: randVerseDate)

假设 randVerseDate 类似于 2021-01-06-20:01:35,我们使用上面的代码行。然后这将在时钟到达 35 秒时每分钟触发一次通知:20:02:35,然后是 20:03:35,然后20:04:35 等等......

,

在获得更多详细信息后,我想我知道为什么您仍然看不到正在发送的通知。我正在另一个答案中回答,不要太长,但我会保留 my previous answer 以供参考。
也许您正在等待应用程序在前台的通知?我将参考文档的另一部分:

Scheduling and Handling Local Notifications
关于当您的应用程序在前台时处理通知的部分:

如果您的应用在前台时收到通知,您可以 使该通知静音或告诉系统继续显示 通知界面。 系统将通知静音 默认的前台应用,提供通知数据 直接到您的应用...

因此,如果是这种情况,您必须为 UNUserNotificationCenter 实现一个委托。
我建议你这样做,在 AppDelegate 上你为 UNUserNotificationCenter 分配委托,因为文档说它必须在应用程序完成启动之前完成:

// AppDelegate.swift
@main
class AppDelegate: UIResponder,UIApplicationDelegate {


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

    // Rest of your code on AppDelegate...
}

extension AppDelegate: UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter,willPresent notification: UNNotification,withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        // Here we actually handle the notification
        print("Notification received with identifier \(notification.request.identifier)")
        // So we call the completionHandler telling that the notification should display a banner and play the notification sound - this will happen while the app is in foreground
        completionHandler([.banner,.sound])
    }
}

在您处理通知授权和请求注册的视图控制器上,您可以这样做:

class NotificationsViewController: UIViewController {
    
    static let notificationAuthorizedNotification = NSNotification.Name(rawValue: "NotificationAuthorizedNotification")
    let randVerseCenter = UNUserNotificationCenter.current()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // We call this method when we know that the user granted permission,so we know we can then make notification requests
        NotificationCenter.default.addObserver(self,selector: #selector(handleNotificationAuthorization),name: NotificationsViewController.notificationAuthorizedNotification,object: nil)
        
        randVerseCenter.getNotificationSettings { [weak self] settings in
            // We check current settings and asks for permission if not granted before
            if settings.authorizationStatus == .notDetermined {
                // Step 1 - Ask the use for permission to notify
                self?.randVerseCenter.requestAuthorization(options: [.alert,.sound]){ (granted,error) in
                    if granted {
                        NotificationCenter.default.post(name: NotificationsViewController.notificationAuthorizedNotification,object: nil)
                        print("Yay - request authorisation worked!")
                    } else {
                        print ("D'oH - request Authorisation did not work!")
                    }
                }
            }
        }
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        // We stop listening to those notifications here
        NotificationCenter.default.removeObserver(self)
    }
    
    @objc
    func handleNotificationAuthorization() {
        // Step 2 - Create the Notification Content
        let randVerseContent = UNMutableNotificationContent()
        randVerseContent.title = "Random Reference"
        randVerseContent.body = "Random Verse"
        randVerseContent.sound = UNNotificationSound.default
        // Step 3 - Create the trigger for the notification by delay
        let randVerseDate = Date().addingTimeInterval(30)
        let randVerseDateComponents = Calendar.current.dateComponents([.second],from: randVerseDate)
        let randVerseTrigger = UNCalendarNotificationTrigger(dateMatching: randVerseDateComponents,repeats: true)
        // Step 4 - Creating the request
        let randVerseUUIDString = UUID().uuidString
        let randVerseRequest = UNNotificationRequest(identifier: randVerseUUIDString,content: randVerseContent,trigger: randVerseTrigger)
        // Step 5 - Register the request
        randVerseCenter.add(randVerseRequest) { (error) in
            if let error = error{
                print (error.localizedDescription)
            } else {
                print("Successfully registered notification with id \(randVerseUUIDString) at every second \(randVerseDateComponents.second!) of a minute")
            }
        }
    }
}

由于您的代码在 viewDidLoad 请求通知,因此您可能仍然安排了较旧的通知,而且您可能没有删除它们或删除应用程序。
您可以在 viewDidLoad 上使用它来检查待处理的通知,例如:

        randVerseCenter.getPendingNotificationRequests() { requests in
            for request in requests {
                guard let trigger = request.trigger as? UNCalendarNotificationTrigger else { return }
                print("Notification registered with id \(request.identifier) is schedulled for \(trigger.nextTriggerDate()?.description ?? "(not schedulled)")")
            }
        }

并使用 randVerseCenter 通过标识符删除它们或将它们全部删除。