iPhone: увеличение значка приложения через локальное уведомление - PullRequest
27 голосов
/ 11 мая 2011

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

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

localNotification.applicationIconBadgeNumber = 23;

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

Пример:

  • Для дня 1: Счет = 0
  • Для дня 2: localNotification.applicationIconBadgeNumber = 1;
  • Для дня 3: localNotification.applicationIconBadgeNumber = 2;
  • Для дня 4: localNotification.applicationIconBadgeNumber = 3;

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

Однако,Я ищу лучшее решение, чем этот обходной путь.

Ответы [ 10 ]

47 голосов
/ 17 марта 2013

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

Действительно, для 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];
        }
    }
}

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

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

13 голосов
/ 11 мая 2011

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

6 голосов
/ 11 мая 2011

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

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

2 голосов
/ 18 марта 2016

ответ 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)
        }
    }
}
2 голосов
/ 11 марта 2014

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

2 голосов
/ 13 февраля 2014

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

- (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 голос
/ 22 июня 2018

начиная с 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 badge: NSNumber? { приготовься }

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

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

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

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

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

UIApplication.shared.applicationIconBadgeNumber = 0
0 голосов
/ 11 февраля 2018

На основе ответов 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 голосов
/ 22 марта 2017

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

Функция renumberBadgesOfPendingNotifications вызывается в AppDelegate applicationDidBecomeActive, поэтому значки обновляются, если пользователь открывает приложение после получения уведомления.А также в settings 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. 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 голосов
/ 30 октября 2014

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