首页 > 解决方案 > 为什么通知不会在 ios 模拟器上触发?

问题描述

本地通知过去常常在模拟器中为我触发,直到我基本上使用不同的数据模型重写了我的应用程序。现在,即使一切似乎都在发生,需要发生通知才能触发,但事实并非如此。

以下是我可以确认的按顺序显示在日志中的内容:

不打印任何错误。这使我相信警报正在正确创建,并且通知正在添加到通知中心,但由于某种原因它没有触发。下面是我的代码,经过删节以减少不必要的复杂性。我只删除了与通知无关的内容。

class AlarmTableViewController: UITableViewController {

//MARK: Public properties
var alarms = [Alarm]()
let ALARM_CELL_IDENTIFIER = "AlarmTableViewCell"

override func viewDidLoad() {
    super.viewDidLoad()
    requestUserNotificationsPermissionsIfNeeded()
    NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(didReceiveNotification), name: NotificationNames.randomAlarmNotification, object: nil)
    loadAlarms()

    for alarm in self.alarms {
        os_log("There are %d notifications for alarm %d", log: OSLog.default, type: .debug, alarm.notificationUuids.count, alarm.alarmNumber)
    }
}

deinit {
    NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)
    NotificationCenter.default.removeObserver(self, name: NotificationNames.randomAlarmNotification, object: nil)
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    guard let cell = tableView.dequeueReusableCell(withIdentifier: ALARM_CELL_IDENTIFIER, for: indexPath) as? AlarmTableViewCell else {
        fatalError("The dequeued cell is not an instance of AlarmTableViewCell.")
    }

    guard let alarm = self.alarms[safe: indexPath.row] else {
        os_log("Could not unwrap alarm for indexPath in AlarmTableViewController.swift", log: OSLog.default, type: .default)
        self.tableView.reloadData()
        return AlarmTableViewCell()
    }

    let alarmNumber = alarm.value(forKey: "alarmNumber") as! Int
    cell.alarmNumberLabel.text = "Alarm " + String(alarmNumber)

    let beginTime = alarm.value(forKey: "startTimeInterval") as! Double
    let endTime = alarm.value(forKey: "endTimeInterval") as! Double
    let beginTimeHour = Alarm.extractHourFromTimeDouble(alarmTimeDouble: beginTime)
    let beginTimeMinute = Alarm.extractMinuteFromTimeDouble(alarmTimeDouble: beginTime)
    cell.beginTimeLabel.text = formatTime(hour: beginTimeHour, minute: beginTimeMinute)
    let endTimeHour = Alarm.extractHourFromTimeDouble(alarmTimeDouble: endTime)
    let endTimeMinute = Alarm.extractMinuteFromTimeDouble(alarmTimeDouble: endTime)
    cell.endTimeLabel.text = formatTime(hour: endTimeHour, minute: endTimeMinute)

    guard let notificationUuids = self.getNotificationUuidsFromAlarmMO(alarm: alarm) else {
        os_log("Could not get notificationUuids from AlarmMO in tableView(cellForRowAt:) in AlarmTableViewController.swift", log: OSLog.default, type: .debug)
        return cell
    }
    os_log("----- notificationUuids: -----", log: OSLog.default, type: .debug)
    for uuid in notificationUuids {
        os_log("uuid: %@", log: OSLog.default, type: .debug, uuid)
    }

    return cell

}

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {

    let alarmNotificationIdentifier = response.notification.request.identifier
    var alarmActedOn: Alarm? = nil
    for alarm in self.alarms {
        guard let notificationUuids = self.getNotificationUuidsFromAlarmMO(alarm: alarm) else {
            os_log("Could not get notificationUuids from AlarmMO in tableView(forRowAt:) in AlarmTableViewController.swift", log: OSLog.default, type: .debug)
            return
        }
        for notificationUuid in notificationUuids {
            if notificationUuid == alarmNotificationIdentifier {
                alarmActedOn = alarm
            }
        }
    }
    if response.actionIdentifier == "snooze" {
        alarmActedOn?.setNewRandomAlarmTime()
    }
    completionHandler()

}

// MARK: Actions

@IBAction func unwindToAlarmList(sender: UIStoryboardSegue) {

    if let sourceViewController = sender.source as? AddAlarmViewController, let alarm = sourceViewController.alarm {
        let newIndexPath = IndexPath(row: self.alarms.count, section: 0)
        os_log("There are %d notificationUuids attached to the alarm created", log: OSLog.default, type: .debug, alarm.notificationUuids.count)
        saveAlarm(alarmToSave: alarm)
        tableView.insertRows(at: [newIndexPath], with: .automatic)
    }

}

// MARK: Private functions

@objc private func didReceiveNotification() {

    os_log("entered the function", log: OSLog.default, type: .debug)

}

private func removeNotifications(notificationUuids: [String]) {

    os_log("Removing %d alarm notifications", log: OSLog.default, type: .debug, notificationUuids.count)
    let notificationCenter = UNUserNotificationCenter.current()
    notificationCenter.removePendingNotificationRequests(withIdentifiers: notificationUuids)

}

private func loadAlarms() {

    os_log("loadAlarms() called", log: OSLog.default, type: .debug)
    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
            return
    }
    let managedContext = appDelegate.persistentContainer.viewContext
    let fetchRequest = NSFetchRequest<Alarm>(entityName: "Alarm")

    do {
        if self.alarms.count == 0 {
            self.alarms = try managedContext.fetch(fetchRequest)
            os_log("Loading %d alarms", log: OSLog.default, type: .debug, self.alarms.count)
        } else {
            os_log("Didn't need to load alarms", log: OSLog.default, type: .debug)
        }
    } catch let error as NSError {
        print("Could not fetch alarms. \(error), \(error.userInfo)")
    }

}

private func saveAlarm(alarmToSave: Alarm) {

    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
            return
    }
    let managedContext = appDelegate.persistentContainer.viewContext
    let entity = NSEntityDescription.entity(forEntityName: "Alarm", in: managedContext)!
    let alarm = Alarm(entity: entity, insertInto: managedContext)

    alarm.setValue(alarmToSave.alarmTime, forKeyPath: "alarmTime")
    alarm.setValue(alarmToSave.alarmNumber, forKeyPath: "alarmNumber")
    alarm.setValue(alarmToSave.startTimeInterval, forKeyPath: "startTimeInterval")
    alarm.setValue(alarmToSave.endTimeInterval, forKeyPath: "endTimeInterval")
    alarm.setValue(RecurrenceOptions.getRawValue(value: alarmToSave.recurrence), forKeyPath: "recurrenceIndex")
    alarm.setValue(alarmToSave.notificationUuids, forKeyPath: "notificationUuids")
    alarm.setValue(alarmToSave.note, forKeyPath: "note")

    if managedContext.hasChanges {
        do {
            try managedContext.save()
            self.alarms.append(alarm)
        } catch let error as NSError {
            print("Could not save alarm to CoreData. \(error), \(error.userInfo)")
        }
    } else {
        os_log("No changes to the context to save", log: OSLog.default, type: .debug)
    }

}

private func getNotificationUuidsFromAlarmMO(alarm: Alarm) -> [String]? {

    guard let notificationUuids = alarm.value(forKey: "notificationUuids") as! [String]? else {
        os_log("Found nil when attempting to unwrap notificationUuids in getNotificationUuidsFromAlarmMO() in AlarmTableViewController.swift, returning nil",
               log: OSLog.default, type: .default)
        return nil
    }
    return notificationUuids

}

private func requestUserNotificationsPermissionsIfNeeded() {

    let notificationCenter = UNUserNotificationCenter.current()

    notificationCenter.getNotificationSettings { (settings) in
        guard settings.authorizationStatus == .authorized else {
            return
        }
        os_log("User has notifications permissions enabled", log: OSLog.default, type: .debug)
    }

    notificationCenter.requestAuthorization(options: [.alert, .sound, .badge]) {
        (granted, error) in
        if !granted {
            self.presentNotificationsPermissionDenialConfirmationAlert()
        } else {
            os_log("User enabled notifications permissions", log: OSLog.default, type: .debug)
        }
    }

    return

}

private func presentNotificationsPermissionDenialConfirmationAlert() {

    let alert = UIAlertController(title: "Are you sure you don't want to allow notifications?",
                                  message: "The application cannot function without notifications.", preferredStyle: .alert)

    alert.addAction(UIAlertAction(title: "Yes", style: .cancel, handler: {
        action in
        fatalError("User declined to allow notifications permissions")
    }))
    alert.addAction(UIAlertAction(title: "No", style: .default, handler: {
        action in
        self.requestUserNotificationsPermissionsIfNeeded()
    }))

    self.present(alert, animated: true)

}

}

那是我的 AlarmTableViewController 代码。这是我删减的 Alarm.swift 代码:

class Alarm: NSManagedObject {

@NSManaged var alarmTime: Double
@NSManaged var alarmNumber: Int
@NSManaged var startTimeInterval: Double
@NSManaged var endTimeInterval: Double
@NSManaged var note: String
@NSManaged var notificationUuids: [String]
@NSManaged var recurrenceIndex: Int
let NUMBER_OF_ALLOWED_NOTIFICATIONS_CREATED_AT_ONE_TIME = 10

}

extension Alarm {

static func newAlarm(context: NSManagedObjectContext, alarmNumber: Int, timeIntervals: TimeIntervals, note: String, recurrence: RecurrenceOptions) -> Alarm? {

    let startInterval = Alarm.convertToTimeDouble(hour: timeIntervals.hourStartInterval, minute: timeIntervals.minuteStartInterval)
    let endInterval = Alarm.convertToTimeDouble(hour: timeIntervals.hourEndInterval, minute: timeIntervals.minuteEndInterval)

    if endInterval < startInterval {
        os_log("Error: Alarm time endInterval is before startInterval", log: OSLog.default, type: .info)
        return nil
    }
    let newAlarm = Alarm(context: context)
    newAlarm.alarmNumber = alarmNumber
    newAlarm.note = note
    newAlarm.recurrenceIndex = RecurrenceOptions.getRawValue(value: recurrence)
    newAlarm.notificationUuids = [String]()
    newAlarm.startTimeInterval = startInterval
    newAlarm.endTimeInterval = endInterval

    newAlarm.setNewRandomAlarmTime()

    return newAlarm
}

var recurrence: RecurrenceOptions {
    get {
        return RecurrenceOptions.getValueFromIndex(index: recurrenceIndex)
    }

    set {
        self.recurrenceIndex = RecurrenceOptions.getRawValue(value: newValue)
    }
}

var hour: Int {
    return Int(floor(self.alarmTime))
}

var minute: Int {
    return Int(round((self.alarmTime - floor(self.alarmTime)) * 60))
}

func setNewRandomAlarmTime() {

    let startInterval = self.startTimeInterval
    let endInterval = self.endTimeInterval

    let currentDateAndTime = Date()
    let currentDateAndTimeComponents = Calendar.current.dateComponents([.hour, .minute], from: currentDateAndTime)
    guard let currentHour = currentDateAndTimeComponents.hour else {
        os_log("Could not unwrap currentDateAndTimeComponents.hour in Alarm.setNewRandomAlarmTime()", log: OSLog.default, type: .default)
        return
    }
    guard let currentMinute = currentDateAndTimeComponents.minute else {
        os_log("Could not unwrap currentDateAndTimeComponents.minute in Alarm.setNewRandomAlarmTime()", log: OSLog.default, type: .default)
        return
    }
    let currentTimeDouble = Alarm.convertToTimeDouble(hour: currentHour, minute: currentMinute)

    // We should start the random alarm interval from the current
    // time if the current time is past the startInterval already
    if currentTimeDouble > startInterval {
        self.alarmTime = Double.random(in: currentTimeDouble ... endInterval)
    } else {
        self.alarmTime = Double.random(in: startInterval ... endInterval)
    }

    scheduleNotifications()

}

func scheduleNotifications() {
    os_log("Attempting to create alarm with time %d:%02d", log: OSLog.default, type: .info, self.hour, self.minute)
    let date = Date()
    let calendar = Calendar.current
    let currentDateComponents = calendar.dateComponents([.year, .month, .day, .timeZone, .hour, .minute], from: date)
    createNotifications(dateComponents: currentDateComponents)
}

//MARK: Private functions

private func createNotifications(dateComponents: DateComponents) {

    switch (self.recurrence) {
    case .today:
        self.createNotification(for: dateComponents)
    case .tomorrow:
        self.createNotification(for: self.day(after: dateComponents))
    case .daily:
        var numberOfCreatedNotifications = 0
        var currentDay: DateComponents? = dateComponents
        while numberOfCreatedNotifications < self.NUMBER_OF_ALLOWED_NOTIFICATIONS_CREATED_AT_ONE_TIME {
            self.createNotification(for: currentDay)
            currentDay = self.day(after: currentDay)
            numberOfCreatedNotifications += 1
        }
    }

}

private func createNotification(for dateComponents: DateComponents?) {

    let center = UNUserNotificationCenter.current()

    let content = UNMutableNotificationContent()
    content.title = "Random Alarm"
    content.subtitle = "It's time!"
    content.body = self.note
    content.sound = UNNotificationSound.default

    guard let dateComponents = dateComponents else {
        os_log("Could not unwrap dateComponents in createNotification() in Alarm.swift", log: OSLog.default, type: .debug)
        return
    }
    let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)

    let uuidString = UUID().uuidString
    let request = UNNotificationRequest(identifier: uuidString, content: content, trigger: trigger)
    self.notificationUuids.append(uuidString)

    guard let day = dateComponents.day else {
        os_log("Could not unwrap dateComponents.day in createNotification() in Alarm.swift", log: OSLog.default, type: .debug)
        return
    }
    guard let hour = dateComponents.hour else {
        os_log("Could not unwrap dateComponents.hour in createNotification() in Alarm.swift", log: OSLog.default, type: .debug)
        return
    }
    guard let minute = dateComponents.minute else {
        os_log("Could not unwrap dateComponents.minute in createNotification() in Alarm.swift", log: OSLog.default, type: .debug)
        return
    }
    os_log("Creating notification for day: %d, time: %d:%02d, with uuid=%s", log: OSLog.default, type: .debug, day, hour, minute, uuidString)

    center.add(request) { (error) in
        if let err = error {
            print("error \(err.localizedDescription)")
        }
    }

}

private func day(after dateComponents: DateComponents?) -> DateComponents? {

    let calendar = Calendar.autoupdatingCurrent

    guard let dateComponents = dateComponents,
        let date = calendar.date(from: dateComponents),
        let tomorrow = calendar.date(byAdding: .day, value: 1, to: date)
        else {
            os_log("Could not calculate tomorrow in Alarm.swift", log: OSLog.default, type: .debug)
            return nil
    }
    let newDateComponents = calendar.dateComponents([.year, .month, .day, .timeZone, .hour, .minute], from: tomorrow)
    return newDateComponents

}

}

标签: iosswiftnotifications

解决方案


我想到了。在 Alarm.swift 的 createNotification() 中,我正在为当前时间创建通知,而不是随机警报时间。一个简单的错误,但这意味着闹钟时间在间隔之前结束,一旦你重新打开应用程序,它就会删除闹钟,因为它是“旧的”。


推荐阅读