iPhone:通过本地通知增加应用程序徽章

30

在应用程序未运行时,是否可以通过本地通知来增加应用程序的徽章?

我知道如何设置徽章,但尚未找到任何途径来增加此值。

localNotification.applicationIconBadgeNumber = 23;

更新: 我找到了一种(远非完美的)解决方案。如果用户没有打开应用程序,则可以为每个+1事件添加通知,并预测会发生什么事情。

例如:

  • 对于第1天:Count = 0
  • 对于第2天:localNotification.applicationIconBadgeNumber = 1;
  • 对于第3天:localNotification.applicationIconBadgeNumber = 2;
  • 对于第4天:localNotification.applicationIconBadgeNumber = 3;

==> 将这些通知放入数组中,并在应用程序退出之前设置它们。

然而,我正在寻找比这个解决方法更好的解决方案。

11个回答

51
我已经找到、实现并测试了一个“解决方法”,可以(似乎)自动增加应用程序图标的徽章数字,它可以与非重复本地通知良好配合使用。
UILocalNotifications无法在多个本地通知被触发时自动更新/增加徽章数字,当用户“忽略”它们或未立即处理它们时,它们会在通知中心中“堆积起来”。此外,“添加一些回调方法”到您的应用程序也无法处理“自动增量”,因为整个通知功能是由iOS在应用程序外部处理的,您的应用程序甚至不需要运行。
然而,有一个基于我通过实验发现的知识的解决方法,因为XCode文档对徽章属性过于模糊。
徽章只是一个“整数”,实际上更像是您在注册通知之前将其分配给applicationIconBadgeNumber属性的“虚拟标签”。您可以给它赋予任何值-当通知触发时,iOS将把值添加到徽章中,无论您在注册通知时将其设置为什么值。iOS没有任何神奇的“自动增量”或其他操作(也许推送通知有所不同,但这不是本文的主题)。iOS只是从注册的通知中获取数字(整数),并将其放入徽章中。
因此,对于一个“解决方法”,您的应用程序必须为每个新创建和注册的通知提供正确的、递增的徽章数字,以覆盖“待处理通知”上面的已有通知。
由于您的应用程序无法预知未来,也不知道您将立即处理哪些事件,哪些事件会留下“待处理状态”,所以需要一些技巧:
当您的应用程序处理通知(通过点击通知、图标等)时,您必须:
1. 获取所有待处理通知的副本 2. “重新编号”这些待处理通知的徽章数字 3. 删除所有待处理通知 4. 再次使用已更正的徽章号码重新注册通知的副本
此外,当您的应用程序注册新通知时,必须首先检查有多少待处理通知,并使用:
badgeNbr = nbrOfPendingNotifications + 1;

看一下我的代码就会更清楚了。我测试过了,它绝对有效:

在你的'registerLocalNotification'方法中,你应该这样做:

NSUInteger nextBadgeNumber = [[[UIApplication sharedApplication] scheduledLocalNotifications] count] + 1;
localNotification.applicationIconBadgeNumber = nextBadgeNumber;
当你处理通知(在appDelegate中),你应该调用下面的方法,它会清除图标上的标记并对待处理的通知重新编号(如果有)。请注意,下面的代码对“顺序”注册的事件运行良好。如果您在等待的事件之间“添加”事件,则首先必须“重新排序”这些事件。我没有走那么远,但我认为这是可能的。
- (void)renumberBadgesOfPendingNotifications
{
    // clear the badge on the icon
    [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];

    // first get a copy of all pending notifications (unfortunately you cannot 'modify' a pending notification)
    NSArray *pendingNotifications = [[UIApplication sharedApplication] scheduledLocalNotifications];

    // if there are any pending notifications -> adjust their badge number
    if (pendingNotifications.count != 0)
    {
        // clear all pending notifications
        [[UIApplication sharedApplication] cancelAllLocalNotifications];

        // the for loop will 'restore' the pending notifications, but with corrected badge numbers
        // note : a more advanced method could 'sort' the notifications first !!!
        NSUInteger badgeNbr = 1;

        for (UILocalNotification *notification in pendingNotifications)
        {
            // modify the badgeNumber
            notification.applicationIconBadgeNumber = badgeNbr++;

            // schedule 'again'
            [[UIApplication sharedApplication] scheduleLocalNotification:notification];
        }
    }
}
为了真正做到“防弹”,这种方法应该是“原子”(内核)代码,防止iOS在执行此方法期间触发通知。我们必须在这里冒险,但这种情况发生的可能性非常小。
这是我第一次在Stackoverflow上贡献,如果我没有遵循“规则”的话,您也可以发表评论。

1
+1 这是一个非常好而且详尽的回答。这就是我们在 Stack Overflow 喜欢的回答类型。欢迎加入! - Jesse
为什么它可以在非重复通知中起作用?如果是重复通知会发生什么? - Code Monkey
一个“重复”的通知实际上被注册为单个通知,并将“repeatInterval”属性设置为所需值。对于该单个(重复)通知,您只能为应用图标徽章号码的badge属性设置一个值(请记住,根据我的说明,设置数量的是您而不是iOS)。因此所有重复的通知都将显示相同的徽章ID。猜测没有解决办法。您最多可以注册64个通知。如果足够了,您可以通过这种方式创建(最多64个)“重复”的通知来“愚弄”用户。 - Ronny Webers
这很棒,但是当用户在通知中心中取消通知时,我该如何减少数字? - kanstraktar
看一下'- (void)renumberBadgesOfPendingNotifications'函数:这个函数通过删除所有待处理通知并重新设置它们来“伪造”减少数字的效果。请注意,当您关闭通知时,不能仅仅“减少”徽章数字:当您点击通知时,iOS只会移除徽章,无论它显示的是什么(1、2、3...)。实际上,您正在确认一个或多个通知。如果徽章显示“3”,则在您点击通知时,您正在确认最后3个(未读)通知。 - Ronny Webers
显示剩余3条评论

14

当你的应用程序没有运行时,唯一能够动态设置徽章数字的方法是使用推送通知。你将需要在服务器端跟踪更新。


6

根据文档,我认为当您的应用程序未运行时,无法增加徽章的值。您在安排通知时设置徽章号码,因此无法对其进行递增。

应用程序负责管理其图标上显示的徽章数字。例如,如果文本消息应用程序在接收本地通知后处理所有传入消息,则应通过将UIApplication对象的applicationIconBadgeNumber属性设置为0来删除图标徽章。


3

iOS10以后,可以直接在UNMutableNotificationContent上定义标记数字。

以下是适用于我的代码:

我正在开发一个基于日期(使用CalendarComponents)添加通知的应用程序,我的触发器是UNCalendarNotificationTrigger。我的代码很简单:

let content = UNMutableNotificationContent()
        content.title = "Title"
        content.body = "Your message"
        content.sound = .default()
        content.badge = NSNumber(value: UIApplication.shared.applicationIconBadgeNumber + 1)

关于content.badge,文档说明如下:
变量 badge: NSNumber? { get set }
描述:应用图标上要显示的数字。
使用此属性来指定通知到达时应用程序图标上要显示的数字。如果您的应用程序未获得显示基于徽章的通知的授权,则此属性将被忽略。
将数字设置为0以删除当前的徽章(如果存在)。将数字设置为大于0的值以显示带该数字的徽章。将数字设为nil以保持当前徽标不变。
SDK支持:iOS 10.0+, tvOS 10.0+,watchOS 3.0+
即使应用程序没有运行,当添加通知时,徽章也会自增。你可以使用以下代码在应用程序中的任何位置清除徽章数:
UIApplication.shared.applicationIconBadgeNumber = 0

能否解释一下在哪里设置这段代码 UIApplication.shared.applicationIconBadgeNumber = 0 来清除应用程序图标的未读数字,就像打开这样的通知。如果可以的话,请添加清除和增加标记计数的代码。谢谢。 - Naresh

2

Whasssaabhhh在Swift 2.1中的答案,包含排序

func renumberBadgesOfPendingNotifications() {
    let app = UIApplication.sharedApplication()
    let pendingNotifications = app.scheduledLocalNotifications

    // clear the badge on the icon
    app.applicationIconBadgeNumber = 0

    // first get a copy of all pending notifications (unfortunately you cannot 'modify' a pending notification)
    // if there are any pending notifications -> adjust their badge number
    if let pendings = pendingNotifications where pendings.count > 0 {

        // sorted by fire date.
        let notifications = pendings.sort({ p1, p2 in p1.fireDate!.compare(p2.fireDate!) == .OrderedAscending })

        // clear all pending notifications
        app.cancelAllLocalNotifications()

        // the for loop will 'restore' the pending notifications, but with corrected badge numbers
        var badgeNumber = 1
        for n in notifications {

            // modify the badgeNumber
            n.applicationIconBadgeNumber = badgeNumber++

            // schedule 'again'
            app.scheduleLocalNotification(n)
        }
    }
}

这真是太聪明了。 - Joshua Pinter

2
在您的项目代理中添加以下代码。
- (void)applicationDidEnterBackground:(UIApplication *)application
{
    NSLog(@"%s",__FUNCTION__);

    NSArray *arrayOfLocalNotifications = [[UIApplication sharedApplication] scheduledLocalNotifications] ;

    for (UILocalNotification *localNotification in arrayOfLocalNotifications) {
        NSLog(@"the notification: %@", localNotification);
        localNotification.applicationIconBadgeNumber= application.applicationIconBadgeNumber+1;
    }
}

这个对我有效。:-)


1
好的,这只会在应用程序即将移动到后台时更新ApplicationIconBadgeNumber。如果您在应用程序处于后台时收到本地通知,则徽章数字不会正确显示。 - thomasgalliker

2

Whasssaaahhh的回答对我非常有帮助。我还需要根据它们的fireDates对通知进行排序。这是Whasssaaahhh的代码和我的代码,使用NSArray的委托方法进行排序 - [NSArray sortedArrayUsingComparator:^(id obj1, id obj2) {}];

- (void)renumberBadgesOfPendingNotifications
{
    // clear the badge on the icon
    [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];

    // first get a copy of all pending notifications (unfortunately you cannot 'modify' a pending notification)
    // Sort the pending notifications first by their fireDate
    NSArray *pendingNotifications = [[[UIApplication sharedApplication] scheduledLocalNotifications] sortedArrayUsingComparator:^(id obj1, id obj2) {
        if ([obj1 isKindOfClass:[UILocalNotification class]] && [obj2 isKindOfClass:[UILocalNotification class]])
        {
            UILocalNotification *notif1 = (UILocalNotification *)obj1;
            UILocalNotification *notif2 = (UILocalNotification *)obj2;
            return [notif1.fireDate compare:notif2.fireDate];
        }

        return NSOrderedSame;
    }];

    // if there are any pending notifications -> adjust their badge number
    if (pendingNotifications.count != 0)
    {
        // clear all pending notifications
        [[UIApplication sharedApplication] cancelAllLocalNotifications];

        // the for loop will 'restore' the pending notifications, but with corrected badge numbers
        // note : a more advanced method could 'sort' the notifications first !!!
        NSUInteger badgeNbr = 1;

        for (UILocalNotification *notification in pendingNotifications)
        {
            // modify the badgeNumber
            notification.applicationIconBadgeNumber = badgeNbr++;

            // schedule 'again'
            [[UIApplication sharedApplication] scheduleLocalNotification:notification];
        }
    }
}

过了一段时间后,我需要在Swift上实现这个功能,还需要支持重复的本地通知。我已经想出了一个Swift的解决方案。

Swift 2.3的解决方案

func renumberBadgesOfPendingNotifications() {
    let app = UIApplication.sharedApplication()
    let pendingNotifications = app.scheduledLocalNotifications

    // clear the badge on the icon
    app.applicationIconBadgeNumber = 0

    // first get a copy of all pending notifications (unfortunately you cannot 'modify' a pending notification)
    // if there are any pending notifications -> adjust their badge number
    if let pendings = pendingNotifications where pendings.count > 0 {

        // Reassign firedate.
        var notifications = pendings
        var i = 0
        for notif in notifications {
            if notif.fireDate?.compare(NSDate()) == NSComparisonResult.OrderedAscending &&
            notif.repeatInterval.rawValue == NSCalendarUnit.init(rawValue:0).rawValue {
                // Skip notification scheduled earlier than current date time
                // and if it is has NO REPEAT INTERVAL
            }
            else {
                notif.fireDate = getFireDate(notif)
            }

            i+=1
        }

        // sorted by fire date.
        notifications = pendings.sort({ p1, p2 in p1.fireDate!.compare(p2.fireDate!) == .OrderedAscending })

        // clear all pending notifications
        app.cancelAllLocalNotifications()

        // the for loop will 'restore' the pending notifications, but with corrected badge numbers
        var badgeNumber: Int = 1
        for n in notifications {
            // modify the badgeNumber
            n.applicationIconBadgeNumber = badgeNumber

            badgeNumber+=1
            // schedule 'again'
            app.scheduleLocalNotification(n)
        }
    }
}

private func getFireDate(notification:UILocalNotification?) -> NSDate? {
        if notification == nil {
            return nil
        }

        let currentDate: NSDate = NSDate().dateByRemovingSeconds()
        let originalDate: NSDate = notification!.fireDate!
        var fireDate: NSDate? = originalDate

        if originalDate.compare(currentDate) == NSComparisonResult.OrderedAscending ||
            originalDate.compare(currentDate) == NSComparisonResult.OrderedSame {

            let currentDateTimeInterval = currentDate.timeIntervalSinceReferenceDate
            let originalDateTimeInterval = originalDate.timeIntervalSinceReferenceDate
            var frequency:NSTimeInterval = 0

            switch notification?.repeatInterval {
            case NSCalendarUnit.Hour?:
                frequency = currentDate.dateByAddingHours(1).timeIntervalSinceDate(currentDate)
                print(frequency)
                break
            case NSCalendarUnit.Day?:
                frequency = currentDate.dateByAddingDays(1).timeIntervalSinceDate(currentDate)
                print(frequency)
                break
            case NSCalendarUnit.WeekOfYear?:
                frequency = currentDate.dateByAddingDays(7).timeIntervalSinceDate(currentDate)
                print(frequency)
                break
            case NSCalendarUnit.Month?:
                frequency = currentDate.dateByAddingMonths(1).timeIntervalSinceDate(currentDate)
                print(frequency)
                break
            case NSCalendarUnit.Year?:
                frequency = currentDate.dateByAddingYears(1).timeIntervalSinceDate(currentDate)
                print(frequency)
                break
            default:
                originalDate
            }

            let timeIntervalDiff = (((currentDateTimeInterval - originalDateTimeInterval) / frequency) + frequency) + originalDateTimeInterval
            fireDate = NSDate(timeIntervalSinceReferenceDate: timeIntervalDiff)
        }

        return fireDate?.dateByRemovingSeconds()
    }

注意:dateByAddingHours、dateByAddingHours、dateByAddingMonths、dateByAddingYears和dateByRemovingSeconds是我正在使用的DateExtension中的方法,它们是一些自描述的方法,你可以自己实现。请注意保留HTML标签,以下是需要翻译的内容。

请升级到iOS 10并使用UserNotifications框架。这将非常棒! - Mannopson

0
这是一个棘手的问题。由于iOS不会为您跟踪本地通知的标记数量,因此您需要及时维护每个通知的数量并更新它们。
更复杂的是,提供在UNTimeIntervalNotificationTrigger类中提供的nextTriggerDate函数无法正常工作。因此,如果您依赖它来按发送顺序排序待处理通知,则会导致混乱。
我找到的一个实用且简化的解决方案是首先删除所有通知,然后根据您的逻辑重新发送它们,每当您需要发送新通知时。这样它们的标记号码保证全部正确。
总之,您应该在以下至少这些条目中执行此类操作:
1. userNotificationCenter(_:didReceive:withCompletionHandler:) 2. userNotificationCenter(_:willPresent:withCompletionHandler:) 3. 发送新通知的位置。 4. 将您的应用程序的标记号码设置为零的位置。

0

根据Wassaahbbs和Bionicles上面的答案。Swift 4.0,适用于所有iOS版本。在func applicationDidBecomeActive(_ application: UIApplication)中调用此函数。

func renumberBadgesOfPendingNotifications() {
    if #available(iOS 10.0, *) {
        UNUserNotificationCenter.current().getPendingNotificationRequests { pendingNotificationRequests in
            if pendingNotificationRequests.count > 0 {
                let notificationRequests = pendingNotificationRequests
                    .filter { $0.trigger is UNCalendarNotificationTrigger }
                    .sorted(by: { (r1, r2) -> Bool in
                        let r1Trigger = r1.trigger as! UNCalendarNotificationTrigger
                        let r2Trigger = r2.trigger as! UNCalendarNotificationTrigger
                        let r1Date = r1Trigger.nextTriggerDate()!
                        let r2Date = r2Trigger.nextTriggerDate()!

                        return r1Date.compare(r2Date) == .orderedAscending
                    })

                let identifiers = notificationRequests.map { $0.identifier }
                UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: identifiers)

                notificationRequests.enumerated().forEach { (index, request) in
                    if let trigger = request.trigger {
                        let content = UNMutableNotificationContent()
                        content.body = request.content.body
                        content.sound = .default()
                        content.badge = (index + 1) as NSNumber

                        let request = UNNotificationRequest(identifier: request.identifier, content: content, trigger: trigger)
                        UNUserNotificationCenter.current().add(request)
                    }
                }
            }
        }
    } else if let pendingNotifications = UIApplication.shared.scheduledLocalNotifications, pendingNotifications.count > 0 {
        let notifications = pendingNotifications
            .filter { $0.fireDate != nil }
            .sorted(by: { n1, n2 in n1.fireDate!.compare(n2.fireDate!) == .orderedAscending })

        notifications.forEach { UIApplication.shared.cancelLocalNotification($0) }
        notifications.enumerated().forEach { (index, notification) in
            notification.applicationIconBadgeNumber = index + 1
            UIApplication.shared.scheduleLocalNotification(notification)
        }
    }
}

0
作为 Bionicle 解决方案的替代,可以使用 NSSortDescriptor 来处理基于 fireDate 字段的排序。再次强调,这种解决方案提供了 Whasssaaahhh 原始答案的所有优点,但也意味着它可以处理非按时间顺序添加通知的情况,例如在 30 秒后添加通知,然后在 20 秒后添加通知。当添加本地通知并返回应用程序时,我调用下面的函数。
// When we add/remove local notifications, if we call this function, it will ensure each notification
// will have an ascending badge number specified.
- (void)renumberBadgesOfPendingNotifications
{
    // Clear the badge on the icon
    [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];

    // First get a copy of all pending notifications (unfortunately you cannot 'modify' a pending notification)
    NSMutableArray * pendingNotifications = [[[UIApplication sharedApplication] scheduledLocalNotifications] mutableCopy];

    // Sorted by fire date.
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"fireDate" ascending:TRUE];
    [pendingNotifications sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
    [sortDescriptor release];

    // if there are any pending notifications -> adjust their badge number
    if (pendingNotifications.count != 0)
    {
        // clear all pending notifications
        [[UIApplication sharedApplication] cancelAllLocalNotifications];

        // the for loop will 'restore' the pending notifications, but with corrected badge numbers
        // note : a more advanced method could 'sort' the notifications first !!!
        NSUInteger badgeNbr = 1;

        for (UILocalNotification *notification in pendingNotifications)
        {
            // modify the badgeNumber
            notification.applicationIconBadgeNumber = badgeNbr++;

            // schedule 'again'
            [[UIApplication sharedApplication] scheduleLocalNotification:notification];
        }
    }

    // Release our copy.
    [pendingNotifications release];
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接