UNCalendarNotificationTrigger 未启动

UNCalendarNotificationTrigger not initiating

好的 - 我现在对这段代码感到非常沮丧并准备放弃!基本上,当模拟到模拟器或实际设备时,我得到 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 的文档以获得更多理解:

Create a UNCalendarNotificationTrigger object when you want to schedule the delivery of a local notification at the specified date and time. You specify the temporal information using an NSDateComponents object, which lets you specify only the time values that matter to you. The system uses the provided information to determine the next date and time that matches the specified information.

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,依此类推...

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

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

If a notification arrives while your app is in the foreground, you can silence that notification or tell the system to continue to display the notification interface. The system silences notifications for foreground apps by default, delivering the notification’s data directly to your app...

因此,如果是这种情况,您必须为 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 通过标识符删除它们或全部删除。