iPhone: Incrementando o selo do aplicativo por meio de uma notificação local

é possível incrementar o selo do aplicativo por meio de uma notificação local enquanto o aplicativo não está em execução?

Eu sei como definir o selo, mas não encontrei nenhuma maneira de incrementar esse valor.

localNotification.applicationIconBadgeNumber = 23;

Atualização: Eu encontrei uma solução (longe de ser perfeita). Você pode prever o que acontecerá se o usuário não abrir o aplicativo e adicionar notifications para cada evento do +1.

Um exemplo:

  • Para o dia 1: contagem = 0
  • Para o dia 2: localNotification.applicationIconBadgeNumber = 1;
  • Para o dia 3: localNotification.applicationIconBadgeNumber = 2;
  • Para o dia 4: localNotification.applicationIconBadgeNumber = 3;

==> Coloque essas notifications em uma matriz e configure-as antes da saída do aplicativo.

No entanto, estou procurando uma solução melhor que essa solução alternativa.

A única maneira de definir dinamicamente o número do selo quando o aplicativo não está sendo executado é com notifications por push. Você terá que rastrear as atualizações no lado do servidor.

Eu encontrei, implementei e testei uma ‘solução alternativa’ para (aparentemente) incrementar automaticamente o número do selo do ícone do aplicativo, que funciona bem com notifications locais que não se repetem

Na verdade, não é possível para o UILocalNotifications fazer com que o iOS ‘automaticamente’ atualize / incremente o número do crachá quando várias notifications locais são triggersdas, e o usuário ‘as ignora’ ou não as manipula imediatamente, então elas se acumulam na Notificação Centro.

Além disso, “adicionar algum método de retorno de chamada” ao seu aplicativo não pode cuidar do “incremento automático”, porque a coisa toda da notificação é manipulada “fora” do seu aplicativo pelo iOS, seu aplicativo nem precisa estar em execução.

No entanto, há alguma solução alternativa, que é baseada no conhecimento que encontrei através da experimentação, porque a documentação do XCode é muito vaga na propriedade badge.

  • o emblema é apenas um “inteiro”, na verdade mais como um ‘label fictício’ que você atribui à propriedade applicationIconBadgeNumber, logo antes de registrar a notificação. Você pode dar a ele qualquer valor – quando a notificação é acionada, o iOS adiciona esse valor ao crachá, seja lá o que você definiu no momento em que você registrou a notificação. Não há mágica ‘auto-incremento’ ou outra manipulação pelo iOS (talvez seja diferente com notifications push, mas esse não é o assunto aqui). O iOS apenas pega o número (inteiro) da notificação registrada e o coloca no selo.

Portanto, para uma solução alternativa, seu aplicativo já deve fornecer o número de identificação correto e incremental para cada notificação que cria e registra “em cima das notifications pendentes”.

Como seu aplicativo não pode procurar no futuro e saber quais events você tratará imediatamente e quais você deixará “pendente” por algum tempo, há um truque a ser feito:

Quando as notifications são tratadas pelo seu aplicativo (tocando na (s) notificação (ões), ícone, …), você precisa:

  1. obter uma cópia de todas as notifications pendentes
  2. ‘renumerar’ o número do emblema dessas notifications pendentes
  3. excluir todas as notifications pendentes
  4. registre novamente a cópia das notifications com seus números de identificação corrigidos novamente

Além disso, quando seu aplicativo registra uma nova notificação, ele precisa verificar quantas notifications estão pendentes primeiro e registrar a nova notificação com:

badgeNbr = nbrOfPendingNotifications + 1; 

Olhando para o meu código, ficará mais claro. Eu testei isso e está definitivamente funcionando:

Em seu método ‘registerLocalNotification’ você deve fazer isto:

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

Quando você lida com a notificação (appDelegate), você deve chamar o método abaixo, que limpa o selo no ícone e renomeia os crachás para notifications pendentes (se houver algum)

Observe que o próximo código funciona bem para events registrados ‘seqüenciais’. Se você “adicionar” events entre os pendentes, você terá que “reordenar” esses events primeiro. Eu não fui tão longe, mas acho que é possível.

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

Para ser verdadeiramente ‘à prova de balas’, este método deve ser ‘atômico’ (kernel), impedindo que o iOS dispare uma notificação durante a execução deste método. Nós vamos ter que correr esse risco aqui, as chances são muito pequenas isso vai acontecer.

Esta é minha primeira contribuição para o Stackoverflow, então você pode comentar também se eu não estou seguindo as ‘regras’ aqui

Com base na documentação , acredito que você não pode incrementar o valor do selo, quando o aplicativo não estiver em execução. Você define o número do selo quando agenda sua notificação, por isso não é possível incrementá-lo.

Um aplicativo é responsável por gerenciar o número do selo exibido em seu ícone. Por exemplo, se um aplicativo de mensagens de texto processa todas as mensagens recebidas após receber uma notificação local, ele deve remover o emblema do ícone, definindo a propriedade applicationIconBadgeNumber do object UIApplication como 0.

Adicione o seguinte código no delegado do seu projeto.

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

isso funciona para mim. 🙂

A resposta de Whasssaabhhh no Swift 2.1, com sorting

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

A resposta de Whasssaaahhh foi muito útil para mim. Eu também precisava classificar as notifications com base em seus fireDates. Aqui está o código do Whasssaaahhh com meu código para ordenar as notifications usando o método delegado do NSArray para sorting – [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]; } } } 

Depois de algum tempo, eu precisava implementar isso no Swift, mas também precisava dar suporte à repetição de notifications locais . Eu encontrei uma solução no Swift.

Solução para o 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() } 

Observação: dateByAddingHours, dateByAddingHours, dateByAddingMonths, dateByAddingAnos, dateByRemovingSeconds são methods de uma extensão de data que estou usando e são methods autodescritivos que você pode implementar por conta própria.

Como alternativa à solução de Bionicle, pode-se usar um NSSortDescriptor para lidar com a sorting baseada no campo fireDate. Novamente, esta solução fornece todos os benefícios da resposta original do Whasssaaahhh, mas também significa que ela pode manipular as notifications adicionadas em ordem não cronológica, por exemplo, adicionando uma notificação em 30 segundos e, em seguida, em 20 segundos. Eu chamo a function abaixo ao adicionar uma notificação local e ao retornar ao aplicativo.

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

Com base nas respostas de Wassaahbbs e Bionicles acima, para o Swift 3.0, isso parece estar funcionando para Repetir notifications locais . Eu tenho que trabalhar para definir 4 notifications locais, cada um dos quais pode ser ligado e desligado de forma independente.

A function renumberBadgesOfPendingNotifications é chamada em AppDelegate applicationDidBecomeActive para que os emblemas sejam atualizados se o usuário abrir o aplicativo após ser notificado. E também em um settingsVC, onde uma function setNotification define as notifications em primeiro lugar e no caso de usuário ativar ou desativar uma notificação, necessitando de uma atualização de crachá.

Além disso, o emblema é definido como 0 em applicationDidBecomeActive com 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. ie 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. ie 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)") } } } 

Baseado nas respostas de Wassaahbbs e Bionicles acima. Swift 4.0, para todas as versões do iOS. Chame essa function em 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) } } } 

desde o iOS10 é possível definir o número do emblema diretamente no UNMutableNotificationContent.

Aqui o que funciona para mim:

Eu estou trabalhando em um aplicativo que adiciona notificação com base em uma data (com CalendarComponents), meu gatilho é UNCalendarNotificationTrigger. Meu código é simplesmente:

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

Sobre o content.badge , o documento diz:

var badge: NSNumber? { prepare-se }

Descrição O número a ser aplicado ao ícone do aplicativo.

Use essa propriedade para especificar o número a ser aplicado ao ícone do aplicativo quando a notificação chegar. Se o seu aplicativo não estiver autorizado a exibir notifications baseadas em distintivos, essa propriedade será ignorada.

Especifique o número 0 para remover o emblema atual, se presente. Especifique um número maior que 0 para exibir um emblema com esse número. Especifique nil para deixar o emblema atual inalterado.

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

O selo é incrementado quando uma notificação é adicionada, mesmo que o aplicativo não esteja em exibição. Você pode limpar o número do selo onde quiser no aplicativo com:

 UIApplication.shared.applicationIconBadgeNumber = 0