web-dev-qa-db-fra.com

iPhone: incrémentation du badge d'application via une notification locale

est-il possible d'incrémenter le badge d'application via une notification locale lorsque l'application n'est pas en cours d'exécution?

Je sais comment définir le badge, mais je n'ai trouvé aucun moyen d'incrémenter cette valeur.

localNotification.applicationIconBadgeNumber = 23;

Mise à jour: J'ai trouvé une solution (loin d'être parfaite). Vous pouvez prédire ce qui se passera si l'utilisateur n'ouvre pas l'application et n'ajoute pas de notifications pour chaque événement +1.

Un exemple:

  • Pour le jour 1: Count = 0
  • Pour le jour 2: localNotification.applicationIconBadgeNumber = 1; 
  • Pour le jour 3: localNotification.applicationIconBadgeNumber = 2; 
  • Pour le jour 4: localNotification.applicationIconBadgeNumber = 3;

==> Placez ces notifications dans un tableau et définissez-les avant la fermeture de l'application.

Cependant, je cherche une meilleure solution que cette solution de contournement.

27
Stefan

Les notifications Push constituent le seul moyen de définir de manière dynamique le numéro de badge lorsque votre application n'est pas en cours d'exécution. Vous devrez suivre les mises à jour côté serveur.

13
visakh7

J'ai trouvé, mis en œuvre et testé une solution de contournement pour (apparemment) incrémenter automatiquement le numéro de badge de l'icône de l'application, qui fonctionne bien avec notifications locales non répétitives

Il n’est en effet pas possible pour UILocalNotifications d’avoir iOS «automatiquement» de mettre à jour/incrémenter le numéro de badge lorsque plusieurs notifications locales sont déclenchées, et l’utilisateur les «ignore» ou ne les gère pas immédiatement, de sorte qu’elles «s’empilent» dans la notification. centre. 

De plus, "l'ajout d'une méthode de rappel" à votre application ne peut pas prendre en charge l'incrémentation automatique, car tout le processus de notification est géré "en dehors" de votre application par iOS, votre application n'a même pas besoin d'être exécutée.

Cependant, il existe une solution de contournement, basée sur les connaissances que j'ai découvertes lors d'expérimentations, car la documentation XCode est trop vague pour la propriété badge.

  • le badge est juste un "entier", en fait plus une "étiquette factice" que vous affectez à la propriété applicationIconBadgeNumber, juste avant d'enregistrer la notification. Vous pouvez lui donner any - lorsque la notification est déclenchée, iOS ajoutera that value au badge, quel que soit le paramétrage que vous avez défini au moment de l'enregistrement de la notification. Il n'y a pas de magie "incrémentation automatique" ou autre manipulation par iOS (peut-être que c'est différent avec les notifications Push, mais ce n'est pas le sujet ici). iOS prend simplement le nombre (entier) de la notification enregistrée et le met dans le badge.

Ainsi, pour une "solution de contournement", votre application doit déjà fournir le numéro de badge incrémentant correct pour chaque notification qu'elle crée et qu'il enregistre "au-dessus des notifications en attente". 

Puisque votre application ne peut pas regarder dans le futur et savoir quels événements vous allez gérer immédiatement, et ceux que vous laisserez "en attente" pendant un moment, il y a un truc à faire:

Lorsque les notifications sont gérées par votre application (en appuyant sur la ou les notifications, l'icône, ...), vous devez:

  1. obtenir une copie de toutes les notifications en attente
  2. 'renumérote' le numéro de badge de ces notifications en attente
  3. supprimer toutes les notifications en attente
  4. ré-enregistrer la copie des notifications avec leurs numéros de badge corrigés

En outre, lorsque votre application enregistre une nouvelle notification, elle doit d'abord vérifier le nombre de notifications en attente et enregistrer la nouvelle notification avec:

badgeNbr = nbrOfPendingNotifications + 1;

En regardant mon code, ça va devenir plus clair. J'ai testé cela et ça marche vraiment:

Dans votre méthode 'registerLocalNotification', vous devriez faire ceci:

NSUInteger nextBadgeNumber = [[[UIApplication sharedApplication] scheduledLocalNotifications] count] + 1;
localNotification.applicationIconBadgeNumber = nextBadgeNumber;

Lorsque vous gérez la notification (appDelegate), vous devez appeler la méthode ci-dessous, qui efface le badge sur l'icône et renumérote les badges pour les notifications en attente (le cas échéant).

Notez que le code suivant fonctionne correctement pour les événements enregistrés «séquentiels». Si vous souhaitez "ajouter" des événements entre ceux en attente, vous devrez d'abord "trier à nouveau" ces événements. Je ne suis pas allé aussi loin, mais je pense que c'est possible.

- (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];
        }
    }
}

Pour être vraiment "à l'épreuve des balles", cette méthode doit être du code "atomique" (noyau), empêchant l'iOS de déclencher une notification lors de l'exécution de cette méthode. Nous devrons prendre ce risque ici, il y a de fortes chances que cela se produise.

Ceci est ma première contribution à Stackoverflow, donc vous pouvez aussi commenter si je ne suis pas les règles ici

47
Ronny Webers

Sur la base de documentation , je pense que vous ne pouvez pas incrémenter la valeur du badge lorsque votre application n’est pas en cours d’exécution. Vous définissez le numéro de badge lorsque vous planifiez votre notification, il n'est donc pas possible de l'incrémenter.

Une application est responsable de la gestion du numéro de badge affiché sur son icône. Par exemple, si une application de messagerie texte traite tous les messages entrants après avoir reçu une notification locale, elle doit supprimer le badge de l'icône en définissant la propriété applicationIconBadgeNumber de l'objet UIApplication sur 0.

6
Krishnan

La réponse de Whasssaabhhh dans Swift 2.1, avec tri

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)
        }
    }
}
2
Edgardo Agno

La réponse de Whasssaaahhh m'a été très utile. Je devais aussi trier les notifications en fonction de leurs fireDates. Voici le code de Whasssaaahhh avec mon code pour trier les notifications à l'aide de la méthode de délégation de NSArray pour le tri - [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];
        }
    }
}

Après quelque temps, je devais implémenter cela sur Swift mais je devais aussi supporter répéter les notifications locales . J'ai trouvé une solution sur Swift.

Solution pour 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()
    }

Remarque: dateByAddingHours, dateByAddingHours, dateByAddingMonths, dateByAddingYears, dateByRemovingSeconds sont des méthodes d'un DateExtension que j'utilise et sont des méthodes auto-descriptives que vous pouvez implémenter vous-même.

2
Bionicle

Ajoutez le code suivant dans votre délégué de projet.

- (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;
    }
}

cela fonctionne pour moi. :-)

2
Tarang

Basé sur les réponses de Wassaahbbs et Bionicles ci-dessus. Swift 4.0, pour toutes les versions d'iOS. Appelez cette fonction dans 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
Beniamin Sarkisian

depuis iOS10, il est possible de définir le numéro de badge directement sur le contenu de UNMutableNotificationContent.

Voici ce qui fonctionne pour moi:

Je travaille sur une application qui ajoute une notification basée sur une date (avec CalendarComponents). Mon déclencheur est UNCalendarNotificationTrigger. Mon code est simplement:

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

À propos de content.badge, le doc dit:

badge var: NSNumber? { se mettre }

Description Numéro à appliquer à L'icône de l'application. 

Utilisez cette propriété pour spécifier le numéro à appliquer à L'icône de l'application lorsque la notification arrive. Si votre application n'est pas autorisée à afficher des notifications basées sur des badges, cette propriété est ignorée.

Spécifiez le nombre 0 pour supprimer le badge actuel, le cas échéant. Spécifiez un nombre supérieur à 0 pour afficher un badge avec ce numéro. Spécifiez la valeur nil pour laisser le badge actuel inchangé.

SDK iOS 10.0+, tvOS 10.0+, watchOS 3.0+

Le badge s'incrémente quand une notification est ajoutée même si l'application n'est pas en cours d'exécution. Vous pouvez effacer le numéro de badge où vous voulez dans l'application avec:

UIApplication.shared.applicationIconBadgeNumber = 0
0
Mehdi Chennoufi

D'après les réponses précédentes à Wassaahbbs et Bionicles, cela semble fonctionner pour Swift 3.0 pour Répétition des notifications locales . Je le travaille pour définir 4 notifications locales, chacune pouvant être activée et désactivée indépendamment.

La fonction renumberBadgesOfPendingNotifications est appelée dans AppDelegate applicationDidBecomeActive afin que les badges soient mis à jour si l'utilisateur ouvre l'application après avoir été averti. Et également dans un settingsVC où une fonction setNotification définit les notifications en premier lieu et dans le cas où l'utilisateur active ou désactive une notification, nécessitant ainsi une mise à jour du badge.

De plus, le badge est défini sur 0 dans applicationDidBecomeActive avec 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)")
        }
    }
}
0
richc

En guise d'alternative à la solution de Bionicle, il est possible d'utiliser un descripteur NSSortDescriptor pour gérer le tri en fonction du champ fireDate. Encore une fois, cette solution offre tous les avantages de la réponse originale de Whasssaaahhh, mais signifie également qu'elle peut gérer les notifications ajoutées dans un ordre non chronologique, par exemple. ajout d'une notification dans 30 secondes, puis dans 20 secondes. J'appelle la fonction ci-dessous lors de l'ajout d'une notification locale et lors du retour à l'application.

// 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];
}
0
Steven Craft