Search code examples
swiftnotificationsbadge

Updating Badge Count from Notification Service Extension


I am trying to update the badge count when a push notification is delivered via the Notification Service Extension. This is all working except that when reading UserDefaults from a shared AppGroup, the app is soft-opened and that causes an issue as I have biometric authentication running upon app launch. If biometric auth fails the user is presented with a username/password login screen. That is the screen that is being displayed if I update the badge count via UserDefaults.

Here's my Notification Service Extension code for the badge count:

if let userDefaults = UserDefaults(suiteName: "group.com.myApp.app") {
    let badgeCount = userDefaults.integer(forKey: "badgeCount")
    if badgeCount > 0 {
        userDefaults.set(badgeCount + 1, forKey: "badgeCount")
        bestAttemptContent.badge = badgeCount + 1 as NSNumber
    } else {
        userDefaults.set(1, forKey: "badgeCount")
        bestAttemptContent.badge = 1
    }
}

I need a way to get the current badge count and update it via the Notification Service Extension because the badge count is reduced as the user completes tasks within the app. So, I'm not trying to clear it upon app launch. I just need the badge count to be updated without the app launching and thus bypassing the biometric authentication.


Solution

  • The answer is to monitor the scenePhase Environment variable because when biometric authentication is triggered, the scenePhase moves to inactive.

    Add this line to the top of your file (inside your struct)

    @Environment(\.scenePhase) var scenePhase
    

    Then at the end of your view, instead of using an .onAppear to trigger the biometric auth (this is what I was doing), use an onChange event instead as follows:

    .onChange(of: scenePhase) { phase in
        if phase == .inactive {
            print("Inactive")
        } else if phase == .active {
            if !isAuthenticated {
                authenticate()
            }
        } else if phase == .background {
            print("Background")
        }
    }
    

    This assumes you have a function called authenticate that performs the biometric authentication, along with a bool variable called isAuthenticated that gets set when the authenticate function is run.

    What we are doing is monitoring the scenePhase of the application in order to determine whether or not to perform biometric authentication. When the app isn't running, and because reading userDefaults soft-opens the app, we don't want to run biometric authentication at that point. We only want it to run if the app is in the active scenePhase state, but we also don't want it going into an infinite loop as the scenePhase will go between inactive and active as biometric authentication is performed. This is why we check for the bool variable before running the authenticate function.