6

I'm trying to extract the badge value out of the userInfo dictionary of a remote notification. I read many post and found a solution of my problem but I'm highly not satisfied!

So here is my data structure (I removed the useless lines): { aps = { badge = 7 } }

To extract this number '7' out of my userInfo I would like to do the following:

self.updateAppIcon(userInfo["aps"]["badge"] as? Int)

But of course I get the following error :

Swift : '(NSObject, AnyObject)' does not have a member named 'subscript'

If I'm not wrong, it's because [] returns an AnyObject which cannot be interpreted as another dictionary.

A working solution would be to do the following:

func handleRemoteNotifiation(userInfo: [NSObject : AnyObject]) {

    if let aps: AnyObject = userInfo["aps"] {
        if let apsDict = aps as? [String : AnyObject]{
           if let badge: AnyObject = apsDict["badge"] {
              self.updateAppIconBadgeNumber(badge as? Int)
           }
        }
    }
}

func updateAppIconBadgeNumber(number: Int?) {
    // do stuff here
}

But seriously... could I do it in a more sexy way ? less lines, less if clauses, less casts, etc? This is such a "code-complex" solution of an easy thing.

Thanks

Kevin Delord
  • 2,498
  • 24
  • 22

2 Answers2

9

The shortest one is:

// Xcode 6.0.1
func handleRemoteNotifiation(userInfo: [NSObject : AnyObject]) {
    if let badge = [userInfo["aps"]?["badge"]][0] as? Int {
        self.updateAppIconBadgeNumber(badge)
    }
}

// Xcode 6.1
func handleRemoteNotifiation(userInfo: [NSObject : AnyObject]) {
    if let badge = userInfo["aps"]?["badge"] as? Int {
        self.updateAppIconBadgeNumber(badge)
    }
}

? between ["aps"] and ["badge"] is called "Optional Chaining". You need this because userInfo["aps"] can returns nil. And you don't have to cast it to [String : AnyObject] because every AnyObject has 'subscript' member.

And, Why we need [ ... ][0] in Xcode 6.0.1 is... I don't know :( .a bug, maybe.

rintaro
  • 51,423
  • 14
  • 131
  • 139
3

You could use nil coleascing operator and make it short but you may loose readability. If have a single line version of the method like this,

func handleRemoteNotification(userInfo: [NSObject : AnyObject]) {
  if let badge = ((userInfo["aps"] as? [String: AnyObject]) ?? ([String: AnyObject]()))["badge"] as? Int{
      self.updateAppIconBadgeNumber(badge)
  }
}

You could typealias [String: AnyObject] and make it look little more readable.

typealias Dict = [String: AnyObject]
func handleRemoteNotifiation(userInfo: [NSObject : AnyObject]) {
    if let badge = ((userInfo["aps"] as? Dict) ?? Dict())["badge"] as? Int{
        self.updateAppIconBadgeNumber(badge)
    }
}
Sandeep
  • 20,908
  • 7
  • 66
  • 106
  • Thanks for your answer, this actually works perfectly as well but is, as you say, less readable. The answer gave by @rintaro seems better to me. But, could explain me or share a link about this 'nil coleascing operator' '??', I have no clue what this is about. – Kevin Delord Oct 22 '14 at 15:51
  • The `nil coalescing operator: ??` is explained in detail in Apple's iBook. I can't seem to block-quote it in an SO comment, but here's a direct copy of what the book says: The nil coalescing operator `(a ?? b)` unwraps an optional a if it contains a value, or returns a default value b if a is nil. The expression a is always of an optional type. The expression b must match the type that is stored inside a. The nil coalescing operator is shorthand for the code below: `a != nil ? a! : b` – ArtSabintsev Jan 21 '15 at 07:54