【问题标题】:IOS 10.2 UserNotifications problems on simple Alert and Badge简单警报和徽章上的 IOS 10.2 UserNotifications 问题
【发布时间】:2017-05-03 18:48:30
【问题描述】:

在写这篇文章之前,我对 UserNotification 框架进行了很多研究,它在 IOS 10 中取代了 UILocalNotification。我还按照本教程学习了有关这个新功能的所有信息:http://useyourloaf.com/blog/local-notifications-with-ios-10/

今天我在实现这样微不足道的通知时遇到了很多麻烦,而且由于它是最近的新功能,我找不到任何解决方案(尤其是在目标 C 中)!我目前有 2 个不同的通知,一个 Alert 和一个 Badge update

警报问题

在将我的手机从 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,但均未成功。

【问题讨论】:

    标签: ios objective-c iphone notifications nsusernotification


    【解决方案1】:

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

    这是对 OP 描述的 徽章 问题的修复(但不是警报问题)。

    第 1 步:发送带有徽章 = 否定 1 的通知

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

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

    第 2 步:实现 UNUserNotificationCenterDelegate

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

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

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

    【讨论】:

    • 只有content.badge = @-1 的通知在我尝试复制时不会触发。
    【解决方案2】:

    关于警报:

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

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

    关于徽章:

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

    我已为此提交了radar

    作为一种解决方法,我发现在UNMutableNotificationContent 对象上设置title 属性,标记值为NSNumber(value: 0)确实会触发。如果title 属性缺失,则不会触发。

    添加 title 属性仍然不会向用户显示警报(更新:iOS 11 不再是这种情况!),因此这是一种静默更新徽章值的方法0 而无需调用 UIApplication 对象(通过UIApplication.shared.applicationIconBadgeNumber = 0)。

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

    //
    //  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])
        }
    }
    

    【讨论】:

    • 设置 sound 属性(不需要 title)的解决方法会导致通知正确触发。也适用于 iOS11。为自己创建一个空白声音文件并执行content.sound = UNNotificationSound.init(named: "blank.aiff").
    • ……但是,播放声音也会触发振动,所以这仍然不是一个“无声”的解决方案。
    【解决方案3】:

    那么我终于设法使这两个警报发挥作用。好像在stackoverflow上发布这个问题帮助我打开了我对过去几天困扰我的那个主题的想法(这些都是非常简单的答案,非常可耻)。

    如果有人看到这篇文章,这是我的解决方案。

    警报问题

    对于应在应用关闭时显示的警报,例如当应用在后台被用户杀死时,代码 sn-p 总体上是“正确的”。关键是,当appDelegate 触发applicationWillTerminate: 函数时,系统已经开始释放/拆除应用程序的整个内存。因此,如果您的应用程序加载了许多视图,并且有许多数据要释放,则将通知添加到中心的线程有足够的时间来完成它的任务。但是如果应用程序只有很少的内存要处理,则通知永远不会添加到通知中心的队列中。

    - (void)applicationWillTerminate:(UIApplication *)application {
        NSLog(@"applicationWillTerminate");
    
        // Notification terminate
        [Utils closePollenNotification];
    
        // Pause the termination thread
        [NSThread sleepForTimeInterval:0.1f];
    
    }
    

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

    徽章问题

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

    //Reset badge icon
    +(void) resetBadgeIcon {
        NSLog(@"reset badge");
    
        // remove basge
        [UIApplication sharedApplication].applicationIconBadgeNumber = 0;
    }
    

    如此简单。

    希望这可以帮助某人。

    【讨论】:

    • 您是否提交了关于通过本地通知将徽章值设置为 0 的错误报告?我遇到了同样的问题。根据文档:“当此属性中的数字为 0 时,系统不显示徽章。当数字大于 0 时,系统显示具有指定数字的徽章。” badge 的类是 NSNumber?,因此 nil0 之间存在差异,应该得到尊重。
    • 我认为文档是正确的,认为有点混乱。创建 UNMutableNotification 时,您可以设置属性面板,包括徽章编号。如果您不指定徽章编号,则将其设置为“0”。它不会发送消息“清除应用程序图标”,它只是说“没有修改徽章图标”。所以我不认为这是一个错误
    • 将 applicationIconBadgeNumber 设置为零会导致清除通知中心中的所有警报的意外后果。如果这在您的应用中不受欢迎,请参阅我的解决方案。
    • Aso UIApplication.sharedApplication.applicationIconBadgeNumber 仅在应用程序本身中可用。不能使用例如来自应用扩展程序。
    猜你喜欢
    • 2013-08-14
    • 1970-01-01
    • 2013-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-24
    相关资源
    最近更新 更多