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

时间:2011-05-11 09:31:08

标签: iphone cocoa-touch ios4 push-notification uilocalnotification

是否可以在应用未运行时通过本地通知递增应用徽章?

我知道如何设置徽章,但没有找到任何方法来增加此值。

  

localNotification.applicationIconBadgeNumber = 23;

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

一个例子:

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

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

但是,我正在寻找比这种解决方法更好的解决方案。

11 个答案:

答案 0 :(得分:47)

我发现,实施了&测试了一个“解决方法”(显然)自动递增应用图标的徽章编号,该功能适用​​于 非重复的本地通知

当多个本地通知被触发时,UILocalNotifications确实不能让iOS“自动”更新/增加徽章编号,并且用户“忽略”它们或者不立即处理它们,因此它们“堆积”起来在通知中心。

此外,“为您的应用添加一些回调方法”无法处理“自动增量”,因为整个通知内容都是由iOS处理的“外部”处理,您的应用甚至不需要运行。

然而,有一些解决方法,这是基于我通过实验发现的知识,因为XCode文档在徽章属性上太模糊。

  • 徽章只是一个“整数”,实际上更像是在注册通知之前分配给applicationIconBadgeNumber属性的“虚拟标签”。您可以为其指定任何值 - 当通知触发时,iOS会将值添加到徽章中,无论您在注册通知时将其设置为什么。 iOS没有神奇的“自动增量”或其他操作(可能与推送通知不同,但这不是主题)。 iOS只会从注册的通知中获取数字(整数),并将其放入徽章中。

因此,对于“变通办法”,您的应用必须已为其新创建的每个通知提供正确的递增徽章编号,并在待处理通知之上进行注册。

由于您的应用程序将来无法查看,并且知道您将立即处理哪些事件,以及哪些事件将暂时“暂停”,因此有一些技巧要做:

当您的应用处理通知时(通过点击通知,图标,...),您必须:

  1. 获取所有待处理通知的副本
  2. '重新编号'这些待处理通知的徽章编号
  3. 删除所有待处理的通知
  4. 使用更正后的徽章重新注册通知副本 数字再次
  5. 此外,当您的应用注册新通知时,它必须首先检查有多少通知待处理,并使用以下内容注册新通知:

    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 :(得分:13)

当您的应用程序未运行时,您能够动态设置徽章编号的唯一方法是使用推送通知。您必须在服务器端跟踪更新。

答案 2 :(得分:6)

基于documentation,我相信当您的应用程序未运行时,您无法增加徽章的值。您在安排通知时设置了徽章编号,因此无法递增它。

  

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

答案 3 :(得分: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;
    }
}
这对我有用。 : - )

答案 4 :(得分: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中的方法,是您可以自行实现的自我描述方法。

答案 5 :(得分: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)
        }
    }
}

答案 6 :(得分:1)

从iOS10开始,可以直接在UNMutableNotificationContent上定义徽章编号。

这对我有用:

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

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

关于content.badge,医生说:

  

var badge:NSNumber? {设置}   

说明要申请的号码   该应用程序的图标。

使用此属性指定要应用于的数字   通知到达时该应用的图标。如果您的应用不是   授权显示基于徽章的通知,此属性是   

指定数字0以删除当前徽章(如果存在)。   指定大于0的数字以显示带有该数字的徽章。   指定nil可使当前标志保持不变。

SDKs iOS 10.0 +,tvOS   10.0 +,watchOS 3.0 +

添加通知后,徽章会自行增加,即使该应用未运行也是如此。您可以使用:

在应用中的任何位置清除徽章编号。

UIApplication.shared.applicationIconBadgeNumber = 0

答案 7 :(得分: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];
}

答案 8 :(得分:0)

基于上面的Wassaahbbs和Bionicles答案,对于Swift 3.0,这似乎适用于重复本地通知。我有它设置4个本地通知,每个通知可以独立打开和关闭。

在AppDelegate applicationDidBecomeActive中调用renumberBadgesOfPendingNotifications函数,因此如果用户在收到通知后打开应用程序,则会更新徽章。而且在一个settingsVC中,setNotification函数首先设置通知,如果用户打开或关闭通知,则需要更新徽章。

此外,在applicationDidBecomeActive中,徽章设置为0,UIApplication.shared.applicationIconBadgeNumber = 0。

func renumberBadgesOfPendingNotifications() {
    // first get a copy of all pending notifications (unfortunately you cannot 'modify' a pending notification)
    let pendingNotifications = UIApplication.shared.scheduledLocalNotifications
    print("AppDel there are \(pendingNotifications?.count) pending notifs now")

    // if there are any pending notifications -> adjust their badge number
    if var pendings = pendingNotifications, pendings.count > 0 {

        // sort into earlier and later pendings
        var notifications = pendings
        var earlierNotifs = [UILocalNotification]()
        var laterNotifs = [UILocalNotification]()

        for pending in pendings {

            // Skip notification scheduled earlier than current date time
            if pending.fireDate?.compare(NSDate() as Date) == ComparisonResult.orderedAscending {
                // and use this if it has NO REPEAT INTERVAL && notif.repeatInterval.rawValue == NSCalendar.Unit.init(rawValue:0).rawValue {

                // track earlier and later pendings
                earlierNotifs.append(pending)
            }
            else {
                laterNotifs.append(pending)
            }
        }

        print("AppDel there are \(earlierNotifs.count) earlier notifications")
        print("AppDel there are \(laterNotifs.count) later notifications")

        // change the badge on the notifications due later
        pendings = laterNotifs

        // sorted by fireDate.
        notifications = pendings.sorted(by: { p1, p2 in p1.fireDate!.compare(p2.fireDate!) == .orderedAscending })

        // clear all pending notifications. i.e the laterNotifs
        for pending in pendings {
            UIApplication.shared.cancelLocalNotification(pending)
        }

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

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

            // schedule 'again'
            UIApplication.shared.scheduleLocalNotification(n)
            print("AppDel later notif scheduled with badgenumber \(n.applicationIconBadgeNumber)")
        }

        // change the badge on the notifications due earlier
        pendings = earlierNotifs

        // sorted by fireDate.
        notifications = pendings.sorted(by: { p1, p2 in p1.fireDate!.compare(p2.fireDate!) == .orderedAscending })

        // clear all pending notifications. i.e the laterNotifs
        for pending in pendings {
            UIApplication.shared.cancelLocalNotification(pending)
        }

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

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

            // schedule 'again'
            UIApplication.shared.scheduleLocalNotification(n)
            print("AppDel earlier notif scheduled with badgenumber \(n.applicationIconBadgeNumber)")
        }
    }
}

答案 9 :(得分:0)

基于上面的Wassaahbbs和Bionicles答案。适用于所有iOS版本的Swift 4.0。在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)
        }
    }
}

答案 10 :(得分:0)

这是一个棘手的问题。由于 iOS 不会为您跟踪本地通知的徽章编号,因此您需要自行维护每个通知的该编号并及时更新。

为了让它变得更复杂,nextTriggerDate 类中提供的函数 UNTimeIntervalNotificationTrigger 无法正常工作。因此,如果您依靠它对发送通知的待处理通知进行排序,则会出现混乱。

我设法找到的一个实用且简化的解决方案是首先删除所有通知,然后在需要发送新通知时根据您的逻辑重新发送它们。这样就可以保证他们的徽章编号都是正确的。

总而言之,您应该至少在以下条目中执行此类操作:

  1. userNotificationCenter(_:didReceive:withCompletionHandler:)
  2. userNotificationCenter(_:willPresent:withCompletionHandler:)
  3. 发送新通知的位置。
  4. 您将应用的徽章编号设置为零的位置。