IOS 10.2 UserNotifications 简单的 Alert 和 Badge 问题

IOS 10.2 UserNotifications problems on simple Alert and Badge

在写这篇文章之前 post 我对 UserNotification Framework 做了很多研究,它取代了 IOS 10 中的 UILocalNotification。我还按照本教程学习了关于这个新功能的一切:http://useyourloaf.com/blog/local-notifications-with-ios-10/.

今天我在实现如此琐碎的通知时遇到了很多麻烦,而且由于这是最近的一项新功能,我找不到任何解决方案(尤其是在 objective C 中)!我目前有 2 个不同的通知,一个 Alert 和一个 Badge updtate.

警报问题

在将我的 Phone 从 IOS 10.1 更新到 10.2 之前,我在用户手动关闭应用程序时立即触发的 Appdelegate 上发出警报:

-(void)applicationWillTerminate:(UIApplication *)application {
        NSLog(@"applicationWillTerminate");

        // Notification terminate
        [self registerTerminateNotification];
}

// Notification Background terminate 
-(void) registerTerminateNotification {
    // the center
    UNUserNotificationCenter * notifCenter = [UNUserNotificationCenter currentNotificationCenter];

    // Content
    UNMutableNotificationContent *content = [UNMutableNotificationContent new];
    content.title = @"Stop";
    content.body = @"Application closed";
    content.sound = [UNNotificationSound defaultSound];
    // Trigger 
    UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 repeats:NO];
    // Identifier
    NSString *identifier = @"LocalNotificationTerminate";
    // création de la requête
    UNNotificationRequest *terminateRequest = [UNNotificationRequest requestWithIdentifier:identifier content:content trigger:trigger];
    // Ajout de la requête au center
    [notifCenter addNotificationRequest:terminateRequest withCompletionHandler:^(NSError * _Nullable error) {
        if (error != nil) {
            NSLog(@"Error %@: %@",identifier,error);
        }
    }];
}

在 IOS 10.2 之前它工作得很好,当我手动关闭应用程序时,会出现一个警告。但是自从我更新到IOS 10.2之后,无缘无故什么都没有出现,我什么都没改,也看不出少了什么..

徽章问题

我也尝试(这次只在 IOS 10.2 中)在我的应用程序图标上实现徽章效果很好,直到我试图删除它。这是执行此操作的函数:

+(void) incrementBadgeIcon {
    // only increment if application is in background 
    if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground){

        NSLog(@"increment badge");

        // notif center 
        UNUserNotificationCenter *notifCenter = [UNUserNotificationCenter currentNotificationCenter];

        // Content
        UNMutableNotificationContent *content = [UNMutableNotificationContent new];
        content.badge = [NSNumber numberWithInt:1];
        // Trigger 
        UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 repeats:NO];
        // Identifier
        NSString *identifier = @"LocalNotificationIncrementBadge";
        // request
        UNNotificationRequest *incrementBadgeRequest = [UNNotificationRequest requestWithIdentifier:identifier content:content trigger:trigger];
        // Ajout de la requête au center
        [notifCenter addNotificationRequest:incrementBadgeRequest withCompletionHandler:^(NSError * _Nullable error) {
            if (error != nil) {
                NSLog(@"Error %@: %@",identifier,error);
            }
        }];
    }
}

现在它不会增加徽章编号,正如它的名字应该暗示的那样,但它只是将徽章编号设置为 1。文档说如果你设置 content.badge 到 0,它会删除它,但这不起作用。我尝试使用其他数字,当我手动将其更改为“2”、“3”等时……它发生了变化,但如果我将其设置为 0,则不起作用。

此外,在我之前链接的教程中,提到了几个函数,如 getPendingNotificationRequests:completionHandler:getDeliveredNotificationRequests:completionHandler:。我注意到,当我在调用 incrementBadgeIcon 后立即调用这些函数时,如果 content.badge 设置为“1”、“2”等...它会出现在待处理中通知列表。但是,当我将它设置为 0 时,它不会出现在任何地方。我没有收到任何错误,Xcode 中也没有警告,而且我的应用程序徽章仍然存在。

有谁知道如何修复这两个警报?

提前致谢

PS:我也尝试过使用 removeAllPendingNotificationRequestsremoveAllDeliveredNotifications 但都没有成功。

好吧,我终于设法使这两个警报正常运行。好像 post 在 Whosebug 上回答这个问题帮助我打开了我对过去几天一直困扰我的话题的想法(而且这些都是非常简单的答案,这是非常可耻的)。

如果有人遇到这个 post,这是我的解决方案。

警报问题

对于应在应用关闭时显示的警报,例如当应用在后台被用户杀死时,代码段总体为 'correct'。关键是,当 appDelegate 触发 applicationWillTerminate: 函数时,系统已经开始 dealloc/dismantle 您应用程序的全部内存。因此,如果您的应用加载了很多视图,并且有很多数据需要释放,那么将通知添加到中心的线程就有足够的时间来完成它的任务。但是如果应用程序只有很少的内存可以处理,则通知永远不会添加到通知中心的队列中。

- (void)applicationWillTerminate:(UIApplication *)application {
    NSLog(@"applicationWillTerminate");

    // Notification terminate
    [Utils closePollenNotification];

    // Pause the termination thread
    [NSThread sleepForTimeInterval:0.1f];

}

所以在我的例子中,我在创建通知后立即在 applicationWillTerminate 中添加了一个简单的 sleep,这给了它足够的时间挂号的。 (注意:我不知道这是否是一个好习惯,但它对我有用)。

徽章问题

很明显,在深入了解了Apple文档后,将content.badge设置为0并没有去掉之前设置的badge。它只是告诉通知不要更新徽章。要删除它,我只需调用 sharedApplication 函数:

//Reset badge icon
+(void) resetBadgeIcon {
    NSLog(@"reset badge");

    // remove basge
    [UIApplication sharedApplication].applicationIconBadgeNumber = 0;
}

就这么简单。

希望这可以帮助到一些人。

关于警报:

当您的本地通知触发时,您的应用程序可能仍在前台运行,因此您需要实施委托方法以便通知执行任何操作。例如,在您的委托中定义此方法将允许通知显示警报、发出声音并更新徽章:

func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
    completionHandler([.alert,.badge,.sound])
}

关于徽章:

我观察到创建 UNMutableNotificationContent object 并仅指定徽章值(作为 NSNumber object)适用于除 0 以外的所有徽章值(即,您无法清除这样的徽章)。我还没有找到任何文档说明为什么 0 的行为与任何其他值不同,特别是因为 .badge 属性 被定义为 NSNumber?,因此框架应该能够区分 [=14] =](无变化)和 0(清除徽章)。

我已经提出 radar 反对。

作为解决方法,我发现在 UNMutableNotificationContent object 上设置 title 属性,标记值为 NSNumber(value: 0) 开火。如果缺少 title 属性,则不会触发。

添加标题 属性 仍然不会向用户显示警报(更新:在 iOS 11! 中不再是这种情况) ,因此这是一种无需调用 UIApplication object(通过 UIApplication.shared.applicationIconBadgeNumber = 0)就可以静默地将徽章值更新为 0 的方法。

这是我的示例项目中的全部代码; ViewController 代码中有一个 MARK,显示插入标题的位置 属性 可以解决问题:

//
//  AppDelegate.swift
//  userNotificationZeroBadgeTest
//
//  Created by Jeff Vautin on 1/3/17.
//  Copyright © 2017 Jeff Vautin. All rights reserved.
//

import UIKit
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound]) { (success, error) -> Void in
            print("Badge auth: \(success)")
        }

        // For handling Foreground notifications, this needs to be assigned before finishing this method
        let vc = window?.rootViewController as! ViewController
        let center = UNUserNotificationCenter.current()
        center.delegate = vc

        return true
    }
}

//
//  ViewController.swift
//  userNotificationZeroBadgeTest
//
//  Created by Jeff Vautin on 1/3/17.
//  Copyright © 2017 Jeff Vautin. All rights reserved.
//

import UIKit
import UserNotifications

class ViewController: UIViewController, UNUserNotificationCenterDelegate {

    @IBAction func start(_ sender: Any) {
        // Reset badge directly (this always works)
        UIApplication.shared.applicationIconBadgeNumber = 0


        let center = UNUserNotificationCenter.current()

        // Schedule badge value of 1 in 5 seconds
        let notificationBadgeOneContent = UNMutableNotificationContent()
        notificationBadgeOneContent.badge = NSNumber(value: 1)
        let notificationBadgeOneTrigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 1*5, repeats: false)
        let notificationBadgeOneRequest = UNNotificationRequest.init(identifier: "1", content: notificationBadgeOneContent, trigger: notificationBadgeOneTrigger)
        center.add(notificationBadgeOneRequest)

        // Schedule badge value of 2 in 10 seconds
        let notificationBadgeTwoContent = UNMutableNotificationContent()
        notificationBadgeTwoContent.badge = NSNumber(value: 2)
        let notificationBadgeTwoTrigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 2*5, repeats: false)
        let notificationBadgeTwoRequest = UNNotificationRequest.init(identifier: "2", content: notificationBadgeTwoContent, trigger: notificationBadgeTwoTrigger)
        center.add(notificationBadgeTwoRequest)

        // Schedule badge value of 3 in 15 seconds
        let notificationBadgeThreeContent = UNMutableNotificationContent()
        notificationBadgeThreeContent.badge = NSNumber(value: 3)
        let notificationBadgeThreeTrigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 3*5, repeats: false)
        let notificationBadgeThreeRequest = UNNotificationRequest.init(identifier: "3", content: notificationBadgeThreeContent, trigger: notificationBadgeThreeTrigger)
        center.add(notificationBadgeThreeRequest)

        // Schedule badge value of 4 in 20 seconds
        let notificationBadgeFourContent = UNMutableNotificationContent()
        notificationBadgeFourContent.badge = NSNumber(value: 4)
        let notificationBadgeFourTrigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 4*5, repeats: false)
        let notificationBadgeFourRequest = UNNotificationRequest.init(identifier: "4", content: notificationBadgeFourContent, trigger: notificationBadgeFourTrigger)
        center.add(notificationBadgeFourRequest)

        // Schedule badge value of 0 in 25 seconds
        let notificationBadgeZeroContent = UNMutableNotificationContent()
        // MARK: Uncommenting this line setting title property will cause notification to fire properly.
        //notificationBadgeZeroContent.title = "Zero!"
        notificationBadgeZeroContent.badge = NSNumber(value: 0)
        let notificationBadgeZeroTrigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 5*5, repeats: false)
        let notificationBadgeZeroRequest = UNNotificationRequest.init(identifier: "0", content: notificationBadgeZeroContent, trigger: notificationBadgeZeroTrigger)
        center.add(notificationBadgeZeroRequest)
    }

    @IBAction func listNotifications(_ sender: Any) {
        let center = UNUserNotificationCenter.current()
        center.getDeliveredNotifications() { (notificationArray) -> Void in
            print("Delivered notifications: \(notificationArray)")
        }
        center.getPendingNotificationRequests() { (notificationArray) -> Void in
            print("Pending notifications: \(notificationArray)")
        }
    }

    // MARK: UNUserNotificationCenterDelegate

    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        print("Received notification: \(notification)")
        completionHandler([.alert,.badge,.sound])
    }
}

此修复仅适用于旧版本的 iOS。仅用于向后兼容。

这是针对 OP 描述的 badge 问题(但不是警告问题)的修复。

第 1 步:发送带有 badge = negative 1 的通知

在你的 UNMutableNotificationContent 上设置 content.badge = @-1 而不是 0。这有两个好处:

  • 与 0 不同,-1 实际上会清除徽章(如果您也执行第 2 步)
  • [UIApplication sharedApplication].applicationIconBadgeNumber = 0 不同,它不会清除您的应用在通知中心的提醒。

第 2 步:实施 UNUserNotificationCenterDelegate

您需要实施 UNUserNotificationCenterDelegate 并执行以下操作:

  • 在您的委托中实现 willPresentNotification 方法,并在主体中调用 completionHandler(UNNotificationPresentationOptionBadge)。注意:我检查请求 ID,并调用默认的 UNNotificationPresentationOptionNone 除非这是专门设计用于清除徽章的通知。
  • 不要忘记用强指针在某处保留您的委托实例。通知中心没有保留强指针

完成这 2 项更改后,您可以再次通过本地通知清除徽章。