web-dev-qa-db-ja.com

iPhone:ローカル通知を通じてアプリケーションバッジを増やす

アプリが実行されていないときにローカル通知を通じてアプリケーションバッジを増やすことはできますか?

バッジの設定方法は知っていますが、この値を増やす方法が見つかりません。

localNotification.applicationIconBadgeNumber = 23;

pdate:(完璧とはほど遠い)解決策を見つけました。ユーザーがアプリを開かず、+ 1イベントごとに通知を追加しない場合、何が起こるかを予測できます。

例:

  • 1日目:カウント= 0
  • 2日目:localNotification.applicationIconBadgeNumber = 1;
  • 3日目:localNotification.applicationIconBadgeNumber = 2;
  • 4日目:localNotification.applicationIconBadgeNumber = 3;

==>これらの通知を配列に入れ、アプリケーションが終了する前に設定します。

しかし、私はこの回避策よりも良い解決策を探しています。

28
Stefan

アプリケーションが実行されていないときにバッジ番号を動的に設定できるようにする唯一の方法は、プッシュ通知を使用することです。サーバー側で更新を追跡する必要があります。

13
visakh7

アプリアイコンのバッジ番号を(適切に)自動インクリメントするための「回避策」を見つけ、実装し、テストしました。これは繰り返しのないローカル通知

複数のローカル通知が起動されたときにUILocalNotificationsがiOSでバッジ番号を「自動的に」更新/インクリメントすることは実際には不可能であり、ユーザーはそれらを「無視」するか、すぐに処理しないため、通知に「積み上げ」られますセンター。

また、アプリに「コールバックメソッドを追加する」ことは「自動インクリメント」を処理できません。通知全体がiOSによってアプリの「外部」で処理されるため、アプリを実行する必要さえありません。

ただし、回避策はいくつかあります。これは、XCodeのドキュメントがバッジプロパティについて曖昧すぎるため、実験で見つけた知識に基づいています。

  • バッジは単なる「整数」であり、実際には通知を登録する直前にapplicationIconBadgeNumberプロパティに割り当てる「ダミーラベル」に似ています。 anyの値を指定できます-通知が発生すると、iOSがthat通知の登録時に設定したバッジの価値。魔法の「自動インクリメント」やiOSによるその他の操作はありません(おそらくプッシュ通知では異なりますが、ここでは取り上げません)。 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への私の最初の貢献なので、ここで「ルール」に従っていない場合もコメントできます

47
Ronny Webers

documentation に基づいて、アプリケーションが実行されていないときは、バッジの値を増やすことができないと思います。通知のスケジュールを設定するときにバッジ番号を設定するため、増分することはできません。

アプリケーションは、アイコンに表示されるバッジ番号を管理する責任があります。たとえば、テキストメッセージングアプリケーションがローカル通知を受信した後にすべての受信メッセージを処理する場合、UIApplicationオブジェクトのapplicationIconBadgeNumberプロパティを0に設定して、アイコンバッジを削除する必要があります。

6
Krishnan

Swift 2.1でのWhasssaabhhhの回答、ソートあり

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

Whasssaaahhhの答えは私にとても役に立ちました。また、fireDatesに基づいて通知を並べ替える必要もありました。これは、ソートのためにNSArrayのデリゲートメソッドを使用して通知をソートするためのコードとWhasssaaahhhのコードです-[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
Bionicle

プロジェクトデリゲートに次のコードを追加します。

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

これでうまくいきます。 :-)

2
Tarang

iOS10以降、UNMutableNotificationContentで直接バッジ番号を定義することが可能です。

ここで私のために何がうまくいくのですか?

Date(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
1
Mehdi Chennoufi

上記の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

上記のWassaahbbsとBioniclesの回答に基づいて、Swift 3.0の場合、これはRepeating Local Notificationsで機能しているようです。それぞれ4つのローカル通知を設定するために機能しています。それぞれ個別にオンとオフを切り替えることができます。

RenumberBadgesOfPendingNotifications関数はAppDelegate applicationDidBecomeActiveで呼び出されるため、ユーザーが通知を受けた後にユーザーがアプリを開いた場合、バッジは更新されます。また、setNotification関数が最初に通知を設定するsettingsVCでも、ユーザーが通知をオンまたはオフにしてバッジの更新が必要な場合。

また、UIApplication.shared.applicationIconBadgeNumber = 0のapplicationDidBecomeActiveでバッジが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

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