0

I want to update app icon badge number by totalling all badge counts. Following is my function. The problem is that count goes out of sync since I'm fetching the count values from APIs and closures make it out of sync. updateBadgeCounts() will be called many times during app usage.

How do I make it work?

extension UIViewController {

    func updateBadgeCounts() {
        fetchValue1() { (result, error) in
            UIApplication.shared.applicationIconBadgeNumber = result!.data!.count!
        }

        fetchValue2() { (result, error) in
           UIApplication.shared.applicationIconBadgeNumber += result!.data!.count!
        }
    }

}

Calling above func

class MainTabBarController: UITabBarController, UITabBarControllerDelegate {

    override func viewDidLoad() {

        // Do other stuff...

        DispatchQueue.main.async() {
            self.updateBadges()
        }

    }

    override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
        DispatchQueue.main.async() {
            self.updateBadges()
        }
    }

}
Josh
  • 9
  • 3
  • What do you mean it goes out of sync? Can you provide some sample data with actual result and expected result. – Peter Warbo Jan 29 '19 at 23:33
  • Let's say count in fetchValue1 is 4 and count in value2 is 10. When I close the app it shows badge number 4 then updates to 14 after few secs. Sometimes it always shows 4, sometimes 14. So I'm not sure what's going on, I'm assuming it's due to being not executed in main thread. – Josh Jan 29 '19 at 23:43
  • @Josh I have added an answer. So just to check, it does sometimes show the full total amount correctly? – Chris Jan 29 '19 at 23:58
  • Yes it does add total sometimes correctly. – Josh Jan 30 '19 at 00:20

2 Answers2

1

The two values are fetched by separate asynchronous calls, so they will return at different times.

You could store the two values in view controller variables and use didSet to update the badge.

var value1: Int = 0 {
    didSet {
        updateBadge()
    }
}

var value2: Int = 0 {
    didSet {
        updateBadge()
    }
}

func updateBadge() {
    UIApplication.shared.applicationIconBadgeNumber = value1 + value2
}

func updateValues() {
    fetchValue1() { (result, error) in
        if let result = result {
            self.value1 = result.data!.count
        }
    }
    fetchValue2() { (result, error) in
        if let result = result {
            self.value2 = result.data!.count
        }
    }
}

On a separate note, I have added optional binding to check result is not nil. Depending on your data, you may need to handle it differently. For example, you might need to cast your result.data to a specific type.

Chris
  • 4,009
  • 3
  • 21
  • 52
  • 1
    Tx Chris, you understood is perfectly clearly and solution is also clear to me. Learned new thing today. My problem is still not solved as my updateBadgeCounts() is function of UIViewController extension so can't declare variables. I've updated my question to reflect it. Sorry I should have done it earlier. – Josh Jan 30 '19 at 00:14
  • @Josh Ok thanks. You can declare the variable in the original view controller code and access it in the extension, but I am not sure whether this is good practice. You could so run one async call nested inside the other and use a variable only within the scope of the function. I’ll update my answer with this option. – Chris Jan 30 '19 at 00:22
  • @Josh On second thoughts, you might need to look into Dispatch Groups, which can handle multiple async calls. I am not very familiar with them. What I suggested in the comment above is unlikely to work as expected. – Chris Jan 30 '19 at 00:37
  • 1
    Thanks for introducing Dispatch groups. I've done a quick reading and it seems to be the solution. I'll do more reading and will try. – Josh Jan 30 '19 at 00:56
  • @Josh It seems easy to be notified of when each async task is completed, but I can’t figure out how to get the return values easily without using a variable. – Chris Jan 30 '19 at 09:49
1

If you haven't figured it out yet then here is one solution. You can implement it in nested functions. Something like this

func updateValues() {
    fetchValue1() { (result, error) in
        fetchValue2() { (result, error) in
            // update all badges numbers here
        }
    }
}
Palmer
  • 21
  • 4