Does iOS 13 has new way of getting device notification token?
Asked Answered
C

8

34

So my friend got this email from OneSignal

Due to a change that may occur as part of the upcoming iOS 13 release, you must update to the latest version of the iOS SDK before building your app with Xcode 11. All of OneSignal’s wrapper SDKs including React Native, Unity, and Flutter have been updated as well. The reason for this is that Xcode 11, which is being released alongside iOS 13, breaks a common technique that apps and libraries like OneSignal were using to get a push token for the device. If you do not use our new SDK then new users will not be able to subscribe to notifications from your app.

And I got curious about it.

This is the way we got the device notification token on iOS 12

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("Notification token = \(token)")
}

Whats the proper way to get it on iOS 13? Should I do the new way for my currently developing apps or the old way is still fine?

Cachou answered 8/9, 2019 at 6:40 Comment(0)
O
29

The way you do it is fine and it should continue to work on iOS 13. But some developers do it like this. To convert Data into base-16 strings, they call description, which returns something like

<124686a5 556a72ca d808f572 00c323b9 3eff9285 92445590 3225757d b83997ba>

And then they trim < and > and remove spaces.

On iOS 13 the description called on token data returns something like

{ length = 32, bytes = 0xd3d997af 967d1f43 b405374a 13394d2f ... 28f10282 14af515f }

Which obviously makes this way broken.

Another example of wrong implementation (already edited to include correct implementation as well).

Some more examples might be found in this thread.

Overdye answered 8/9, 2019 at 14:36 Comment(6)
Anyone using description to convert the token to a string is doing it wrong. It was never the correct approach.Stgermain
if you used the description to convert the token to a string - it does not affect the version in the store. when you update the app with Xcode 11 need to update code and remove old description styleSanitary
Is there a way to convert the { length = 32, bytes = 0xd3d997af into a token outside of the iOS as a temp fix say on a server the iOS device communicates with?Loess
@AndyDavies Unfortunately no, because description drops part of the token value replacing it with ....Overdye
does the new way of getting device token on iOS 13 works on older versions too?Hereon
@SaiSunkari Actually this is not a "new" way, it's just a more correct and stable way, because it doesn't rely on the description. And of course it works for older iOS versionsOverdye
I
51

You may use this method to fetch the device token on iOS 13 onwards:

Objective-C:

+ (NSString *)stringFromDeviceToken:(NSData *)deviceToken {
    NSUInteger length = deviceToken.length;
    if (length == 0) {
        return nil;
    }
    const unsigned char *buffer = deviceToken.bytes;
    NSMutableString *hexString  = [NSMutableString stringWithCapacity:(length * 2)];
    for (int i = 0; i < length; ++i) {
        [hexString appendFormat:@"%02x", buffer[i]];
    }
    return [hexString copy];
}

Swift 5.0 (Untested)

class func string(fromDeviceToken deviceToken: Data?) -> String? {
    let length = deviceToken?.count ?? 0
    if length == 0 {
        return nil
    }
    let buffer = UInt8(deviceToken?.bytes ?? 0)
    var hexString = String(repeating: "\0", count: length * 2)
    for i in 0..<length {
        hexString += String(format: "%02x", buffer[i])
    }
    return hexString
}

Taken from OneSignal blog

Interpolate answered 17/9, 2019 at 5:56 Comment(1)
The casting line won't compile. Fix is: const unsigned char *buffer = (const unsigned char *)NSDeviceToken.bytes;Plenty
O
29

The way you do it is fine and it should continue to work on iOS 13. But some developers do it like this. To convert Data into base-16 strings, they call description, which returns something like

<124686a5 556a72ca d808f572 00c323b9 3eff9285 92445590 3225757d b83997ba>

And then they trim < and > and remove spaces.

On iOS 13 the description called on token data returns something like

{ length = 32, bytes = 0xd3d997af 967d1f43 b405374a 13394d2f ... 28f10282 14af515f }

Which obviously makes this way broken.

Another example of wrong implementation (already edited to include correct implementation as well).

Some more examples might be found in this thread.

Overdye answered 8/9, 2019 at 14:36 Comment(6)
Anyone using description to convert the token to a string is doing it wrong. It was never the correct approach.Stgermain
if you used the description to convert the token to a string - it does not affect the version in the store. when you update the app with Xcode 11 need to update code and remove old description styleSanitary
Is there a way to convert the { length = 32, bytes = 0xd3d997af into a token outside of the iOS as a temp fix say on a server the iOS device communicates with?Loess
@AndyDavies Unfortunately no, because description drops part of the token value replacing it with ....Overdye
does the new way of getting device token on iOS 13 works on older versions too?Hereon
@SaiSunkari Actually this is not a "new" way, it's just a more correct and stable way, because it doesn't rely on the description. And of course it works for older iOS versionsOverdye
G
3

The same code for Swift 5 but bit shorter variant. Verified at iOS 13.

func getStringFrom(token:NSData) -> String {
    return token.reduce("") { $0 + String(format: "%02.2hhx", $1) }
}
Georgy answered 15/10, 2019 at 11:31 Comment(0)
A
2

Correctly capture iOS 13 Device Token in Xamarin.iOS

public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
    //DeviceToken = Regex.Replace(deviceToken.ToString(), "[^0-9a-zA-Z]+", "");
    //Replace the above line whick worked up to iOS12 with the code below:
    byte[] bytes = deviceToken.ToArray<byte>();
    string[] hexArray = bytes.Select(b => b.ToString("x2")).ToArray();
    DeviceToken = string.Join(string.Empty, hexArray);
}

Here is what's going on here:

  1. First we have to grab all the bytes in the device token by calling the ToArray() method on it.
  2. Once we have the bytes which is an array of bytes or, byte[], we call LINQ Select which applies an inner function that takes each byte and returns a zero-padded 2 digit Hex string. C# can do this nicely using the format specifier x2. The LINQ Select function returns an IEnumerable, so it’s easy to call ToArray() to get an array of string or string[].
  3. Now just call Join() method on an array of string and we end up with a concatenated string.

Reference: https://dev.to/codeprototype/correctly-capture-ios-13-device-token-in-xamarin-1968

Solution 2: This also works fine

public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
    byte[] result = new byte[deviceToken.Length];
    Marshal.Copy(deviceToken.Bytes, result, 0, (int)deviceToken.Length);
    var token = BitConverter.ToString(result).Replace("-", "");
}
Aerodrome answered 14/10, 2020 at 6:1 Comment(0)
R
1

Nice solution in C# with Xamarin:

// In AppDelegate class:
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
    var bytes = deviceToken.ToArray();
    var deviceTokenString = string.Concat(bytes.Select(b => $"{b:x2}"));
    // TODO: handle deviceTokenString
}

Rms answered 4/2, 2022 at 20:22 Comment(0)
B
0
func getStringFrom(deviceToken: Data) -> String {
    var token = ""
    for i in 0..<deviceToken.count {
        token += String(format: "%02.2hhx", arguments: [deviceToken[i]])
    }
    return token
}
Barretter answered 4/10, 2019 at 20:18 Comment(1)
This seems to be working for me on iOS 13 and iOS 12. I'm a bit rusty on my printf format though. From the docs I've gathered: x - unsigned 32 bit integer hh - applies to signed or unsigned char. Can you help me understand the 02.2 in this context? Is it for data padding? Thanks!Ardine
M
-3

You can have look in the below code as I was also stuck on this problem. Here is the code by which you can get the device token in below iOS 13 and above.

    NSString *str = [NSString stringWithFormat:@"%@", devTokendata]; // devTokendata is NSData
    str = [str stringByReplacingOccurrencesOfString:@" " withString:@""];
    str = [str stringByReplacingOccurrencesOfString:@"<" withString:@""];
    str = [str stringByReplacingOccurrencesOfString:@">" withString:@""];
    if (@available(iOS 13, *)) {
        str = [self hexadecimalStringFromData:devToken];
    NSLog(@"APNS Token: %@",str);
}

-(NSString *)deviceTokenFromData:(NSData *)data
{
    NSUInteger dataLength = data.length;
    if (dataLength == 0) {
        return nil;
    }
    const unsigned char *dataBuffer = (const unsigned char *)data.bytes;
    NSMutableString *hexString  = [NSMutableString stringWithCapacity:(dataLength * 2)];
    for (int i = 0; i < dataLength; ++i) {
        [hexString appendFormat:@"%02x", dataBuffer[i]];
    }
    return [hexString copy];
}
Merce answered 31/3, 2020 at 6:25 Comment(0)
A
-11

use deviceToken.debugDescription

Ariella answered 25/9, 2019 at 3:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.