iPhone: добавление значка приложения через локальное уведомление

возможно ли увеличить значок приложения через локальное уведомление, пока приложение не запущено?

Я знаю, как установить значок, но не нашел способа увеличить это значение.

localNotification.applicationIconBadgeNumber = 23;

Обновление: я нашел решение (далеко не идеальное). Вы можете предсказать, что произойдет, если пользователь не откроет приложение и не добавит уведомления для каждого события +1.

Пример:

  • На 1-й день: Count = 0
  • На второй день: localNotification.applicationIconBadgeNumber = 1;
  • На третий день: localNotification.applicationIconBadgeNumber = 2;
  • На 4-й день: localNotification.applicationIconBadgeNumber = 3;

==> Поместите эти уведомления в массив и установите их до выхода приложения.

Тем не менее, я ищу лучшее решение, чем это обходное решение.

Единственный способ, которым вы сможете динамически устанавливать номер значка, когда ваше приложение не работает, – это push-уведомления. Вам нужно будет отслеживать обновления на стороне сервера.

Я нашел, внедрил и протестировал «обходной путь» для (автоматически) автоматического увеличения значка значка значка приложения, который отлично работает с не повторяющимися локальными уведомлениями

Невозможно, чтобы UILocalNotifications автоматически «обновляло» iOS / увеличивало номер значка при запуске нескольких локальных уведомлений, а пользователь «игнорировал» их или не обрабатывал их немедленно, поэтому они «накапливаются» в уведомлении центр.

Кроме того, «добавление некоторого метода обратного вызова» в ваше приложение не может позаботиться о «автоматическом приращении», потому что вся информация уведомления обрабатывается «снаружи» вашего приложения iOS, ваше приложение даже не нужно запускать.

Однако существует некоторое обходное решение, основанное на знаниях, которые я нашел в результате экспериментов, потому что документация XCode слишком расплывчата в свойстве значка.

  • значок является просто «целым числом», на самом деле больше похожим на «фиктивный ярлык», который вы назначаете свойству applicationIconBadgeNumber, прямо перед регистрацией уведомления. Вы можете придать ему любую ценность – при срабатывании уведомления iOS добавит это значение к значку, независимо от того, что вы его установили, в момент регистрации уведомления. Нет никакого волшебного «автоинкремента» или других манипуляций со стороны iOS (возможно, это отличается от push-уведомлений, но это не предмет здесь). 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]; } } } 

Чтобы быть действительно «пуленепробиваемым», этот метод должен быть «атомарным» (kernel) кодом, что предотвращает запуск iOS уведомления во время выполнения этого метода. Здесь нам придется рискнуть, шансы очень маленькие, это произойдет.

Это мой первый вклад в Stackoverflow, поэтому вы можете также прокомментировать, если я не буду следовать правилам здесь

Основываясь на документации , я считаю, что вы не можете увеличить значение значка, когда ваше приложение не запущено. Вы устанавливаете номер значка, когда планируете свое уведомление, поэтому его невозможно увеличить.

Приложение отвечает за управление номером значка, отображаемым на его значке. Например, если приложение текстовых сообщений обрабатывает все входящие сообщения после получения локального уведомления, оно должно удалить значок значка, установив для свойства applicationIconBadgeNumber объекта UIApplication значение 0.

Добавьте в свой проект делегата следующий код.

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

это работает для меня. 🙂

Ответ 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) } } } 

Ответ Васссааах был очень полезен для меня. Мне также нужно было сортировать уведомления на основе их 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, которые я использую, и являются самоописательными методами, которые вы можете реализовать самостоятельно.

В качестве альтернативы решению 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]; } 

Основываясь на ответах Wassaahbbs и Bionicles выше, для Swift 3.0 это похоже на работу с повторением локальных уведомлений . Я работаю над настройкой 4 локальных уведомлений, каждый из которых можно включать и выключать самостоятельно.

Функция renumberBadgesOfPendingNotifications вызывается в AppDelegate applicationDidBecomeActive, поэтому значки обновляются, если пользователь открывает приложение после уведомления. А также в настройкахVC, где функция setNotification устанавливает уведомления в первую очередь и в случае, если пользователь включает или отключает уведомление, тем самым нуждаясь в обновлении значка.

Также значок установлен в 0 в applicationDidBecomeActive с 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)") } } } в 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)") } } } 

На основании ответов 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) } } } 

поскольку 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 , документ говорит:

Значок var: NSNumber? { получить набор }

Описание Номер для применения к значку приложения.

Используйте это свойство, чтобы указать номер для применения к значку приложения при поступлении уведомления. Если ваше приложение не имеет права отображать уведомления на основе значков, это свойство игнорируется.

Укажите номер 0, чтобы удалить текущий значок, если он есть. Укажите число больше 0, чтобы отобразить значок с этим номером. Укажите nil, чтобы оставить текущий значок без изменений.

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

Значок увеличивается при добавлении уведомления, даже если приложение не запущено. Вы можете очистить номер значка, где бы вы ни находились в приложении:

 UIApplication.shared.applicationIconBadgeNumber = 0 
Давайте будем гением компьютера.