94

I just updated from Xcode 7 to the 8 GM and amidst the Swift 3 compatibility issues I noticed that my device tokens have stopped working. They now only read '32BYTES'.

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)
{
    print(deviceToken) // Prints '32BYTES'
    print(String(data: deviceToken , encoding: .utf8)) // Prints nil
}

Before the update I was able to simply send the NSData to my server, but now I'm having a hard time actually parsing the token.

What am I missing here?

Edit: I just testing converting back to NSData and I'm seeing the expected results. So now I'm just confused about the new Data type.

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)
{
    print(deviceToken) // Prints '32BYTES'
    print(String(data: deviceToken , encoding: .utf8)) // Prints nil

    let d = NSData(data: deviceToken)
    print(d) // Prints my device token
}
user1537360
  • 4,385
  • 6
  • 23
  • 22
  • 2
    Changing to `NSData` simply prints the `description` of the `NSData`. You still don't get a string from that. – rmaddy Sep 14 '16 at 16:47

11 Answers11

190
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
    print(token)
}
Rok Gregorič
  • 2,292
  • 1
  • 10
  • 8
35

I had the same problem. This is my solution:

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    var token = ""
    for i in 0..<deviceToken.count {
        token = token + String(format: "%02.2hhx", arguments: [deviceToken[i]])
    }
    print(token)
}
Oleg
  • 451
  • 3
  • 6
  • 1
    This is an alternate approach to encoding the `NSData` into a string. I suggested using base64 encoding in my answer. This uses base16 encoding. – rmaddy Sep 15 '16 at 22:29
  • @rmaddy your way is also interesting but it would be more helpful if you give us some guidance with code !!! – vivek agravat Feb 13 '17 at 07:44
29

Here is my Swift 3 extension to get a base-16 encoded hex string:

extension Data {
    var hexString: String {
        return map { String(format: "%02.2hhx", arguments: [$0]) }.joined()
    }
}
phatmann
  • 17,007
  • 6
  • 57
  • 47
  • I've noticed that "%02x" format also works. I also can't understand what "hh" does – andrei Jun 21 '17 at 12:11
  • 1
    @andrei "hh" tells the string formatter to treat the input as a char. However in swift, Data is a collection of UInt8, so you don't need it – wyu Jun 29 '17 at 20:13
27

The device token has never been a string and certainly not a UTF-8 encoded string. It's data. It's 32 bytes of opaque data.

The only valid way to convert the opaque data into a string is to encode it - commonly through a base64 encoding.

In Swift 3/iOS 10, simply use the Data base64EncodedString(options:) method.

rmaddy
  • 298,130
  • 40
  • 468
  • 517
  • Well the difference here is the new Data type. I just tried converting the deviceToken to NSData and it now prints my device token just as before. Do you have an example of how you would deal with this without NSData? Because this feels hacky, but also more straightforward than what they seem to expect us to do. – user1537360 Sep 14 '16 at 16:43
  • 3
    I stand by what I said. `NSData` or `Data`, it doesn't matter. The bytes of the data are not and have never been a string. The documentation clearly states that it is an opaque set of data. The fact that your code used to work is luck. It was always the incorrect way to handle it. Simply convert the data to a string by base64 encoding the data. That's the proper solution now and before. – rmaddy Sep 14 '16 at 16:46
  • Base64 decoding does not work if you use a service such as Amazon SNS. Solutions that convert the data into hexadecimal characters, such as **@satheeshwaran**'s, produce device token strings that resemble the ones prior to the changes to the SDK. – Alexander Mar 30 '17 at 17:03
  • @Alexander Who said anything about Base64 decoding? The whole point of the question and the answers is to *encode* the raw data, not decode, into a string. The only reason to do any of this is to view the raw data. The specific encoding scheme is irrelevant. The other answer is using base 16 encoding. I mentioned using base 64 encoding. – rmaddy Apr 20 '17 at 23:33
  • @rmaddy I meant Base64 encoding in my comment, my apologies. – Alexander Apr 20 '17 at 23:36
15

Try this:

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {

   let token = String(data: deviceToken.base64EncodedData(), encoding: .utf8)?.trimmingCharacters(in: CharacterSet.whitespaces).trimmingCharacters(in: CharacterSet(charactersIn: "<>")) 
}
user
  • 613
  • 6
  • 5
8

try this

if #available(iOS 10.0, *) {
   let deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02X", $1)})
}
bluenowhere
  • 2,373
  • 4
  • 22
  • 34
7

Swift 3

The best and easiest way.

deviceToken.base64EncodedString()
Maselko
  • 3,698
  • 1
  • 18
  • 21
  • 3
    This is easiest, but be careful what your server is using if you are handing the token off. Many API expect with Hex encoded or Base-16. For example, Django Push Notifications. – sww314 Feb 12 '17 at 19:10
4

This one wasn't stated as an official answer (saw it in a comment), but is what I ultimately did to get my token back in order.

let tokenData = deviceToken as NSData
let token = tokenData.description

// remove any characters once you have token string if needed
token = token.replacingOccurrences(of: " ", with: "")
token = token.replacingOccurrences(of: "<", with: ""
token = token.replacingOccurrences(of: ">", with: "")
Bill Burgess
  • 13,768
  • 6
  • 47
  • 85
4
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {

    let token = deviceToken.map({ String(format: "%02.2hhx", $0)}).joined()
     print("TOKEN: " + token)


}
PacoG
  • 56
  • 3
  • 4
    While this code block may answer the question, it would be best if you could provide a little explanation for why it does so. – Crispin Mar 31 '17 at 15:35
3

I just did this,

let token = String(format:"%@",deviceToken as CVarArg).components(separatedBy: CharacterSet.alphanumerics.inverted).joined(separator: "")

it gave the result same as,

let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
Satheesh
  • 10,292
  • 6
  • 47
  • 89
0

Get device token with proper format.

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) 
{
            var formattedToken = ""
            for i in 0..<deviceToken.count {
                formattedToken = formattedToken + String(format: "%02.2hhx", arguments: [deviceToken[i]])
            }
            print(formattedToken)
}
Basir Alam
  • 1,082
  • 14
  • 23