Alexa: productId, code challenge: what should they be? - ios

I am following closely this document to authorize a hardware from my iOS app:
In iOS section, at step 5):
- (IBAction)onLogInButtonClicked:(id)sender {
NSArray *requestScopes = [NSArray arrayWithObjects:#"alexa:all", nil];
NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
NSString* scopeData = [NSString stringWithFormat:#"{\"alexa:all\":{\"productID\":\"%#\","
"\"productInstanceAttributes\":{\"deviceSerialNumber\":\"%#\"}}}",
productId, deviceSerialNumber];
options[kAIOptionScopeData] = scopeData;
options[kAIOptionReturnAuthCode] = #YES;
options[kAIOptionCodeChallenge] = #"CODE_CHALLENGE_GOES_HERE";
options[kAIOptionCodeChallengeMethod] = #"S256";
[AIMobileLib authorizeUserForScopes:requestScopes delegate:delegate options:options];
}
what should productId in scopeData be? As I have read some other posts, it is said that productId is taken from ID column of an app created in AVS Developer Portal, which is not linked with the document as it mentions to App Console in Getting Started guide for iOS. So I am confused at how/where to take the productId.
deviceSerialNumber can be any unique string?
I implemented a code-challenge method in ObjC based on item 1) and 2) described in "Transferring an Authorization Code from a Mobile App to an Alexa-enabled Product" section. Is it correct? (since I have no reference example)
- (NSString *)codeChallenge {
verifier = [NSString randomStringWithLength:128]; // generate 128-char string containing [A-Z], [a-z], [0-9], "-", "_", ".", "~"
NSData *sha256 = [[verifier dataUsingEncoding:NSUTF8StringEncoding] SHA256]; // SHA256 that string
NSString *base64Enc = [sha256 base64EncodedStringWithOptions:0]; // base64 encode SHA256 result
NSLog(#"base64Enc: %#", base64Enc);
NSMutableString *ret = [NSMutableString string];
for (NSInteger i=0; i<base64Enc.length; i++) { // remove "="; replace "+" with "-"; replace "/" with "_" as referenced from: http://tools.ietf.org/html/draft-ietf-oauth-spop-10#appendix-A
unichar c = [base64Enc characterAtIndex:i];
if (c == '=') {
continue;
}
else if (c == '+') {
[ret appendString:#"-"];
}
else if (c == '/') {
[ret appendString:#"_"];
}
else {
[ret appendFormat:#"%C", c];
}
}
return ret;
}
Regards,

So, it turned out that the App ID is the productID. You should able able find yours on Developer Console under Application Type Info tab. Your code challenge seems fine to me, but I'm not sure why you'd want to strip off =+-_. And yeah, deviceSerailNumber could be anything unique, I'm assuming that it should also be unique per installation.
The following in the Swift example for the same,
#IBAction func loginWithAmazon() {
let scopes = ["alexa:all"];
let codeChallenge = sha256("CODE_CHALLENGE_GOES_HERE".dataUsingEncoding(NSUTF8StringEncoding)!).base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)
var options: [String: AnyObject] = [:]
let scopeData = String(format: "{\"alexa:all\":{\"productID\":\"%#\",\"productInstanceAttributes\":{\"deviceSerialNumber\":\"%#\"}}}", "yourAppIDHere", "anyUniqueID")
options[kAIOptionScopeData] = scopeData;
options[kAIOptionReturnAuthCode] = true;
options[kAIOptionCodeChallenge] = codeChallenge;
options[kAIOptionCodeChallengeMethod] = "S256";
AIMobileLib.authorizeUserForScopes(scopes, delegate: self, options: options);
}
func requestDidSucceed(apiResult: APIResult!) {
accessToken = apiResult.result
print(apiResult.result)
}
func requestDidFail(errorResponse: APIError!) {
print(errorResponse.error)
}
=================================================
func sha256(data : NSData) -> NSData {
var hash = [UInt8](count: Int(CC_SHA256_DIGEST_LENGTH), repeatedValue: 0)
CC_SHA256(data.bytes, CC_LONG(data.length), &hash)
let res = NSData(bytes: hash, length: Int(CC_SHA256_DIGEST_LENGTH))
return res
}

Related

encoding NSURL in ISO-8859-1

I have a ViewController containing TextFields and I would need to send those values to a dedicated HTTP service.
My main concern comes from the encoding type, as this app is in French and may contain some accents ('é', 'è', etc,...) but also I need to format correctly my fields as it may contain spaces as well....
I tried to use different ways but I still have a wrong encoding on the server side.
here is a sample of my code:
let url_to_request = "http://11.22.33.44:8080/SRV/addRepertoire"
var params = "owner=\(User.sharedInstance.email)&adresse=\(adresse.text!)&nom=\(nom.text!)&telephone=\(telephone.text!)&commentaires=\(commentaires.text!)"
//trying to encode in ISO-8859-1
let dt = NSString(CString: params, encoding: NSISOLatin1StringEncoding)
//preparing string to be used in a NSURL
let final_url = dt!.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
print("URL loadRepertoire: \(url_to_request+"?"+final_url!)")
for instance, field "nom" contains "bébé" which is encoded "b%C4%82%C5%A0b%C4%82%C5%A0" whereas my server is expecting "b%E9b%E9"
EDIT2:
I tried to use the following:
let url_to_request = "http://11.22.33.44:8080/SRV/addRepertoire"
let params = "owner=\(User.sharedInstance.email)&adresse=\(adresse.text!)&nom=\(nom.text!)&telephone=\(telephone.text!)&commentaires=\(commentaires.text!)"
let tmp_url = url_to_request + "?" + params
let final_url = tmp_url.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
print("URL addRepertoire: \(final_url)")
but the result remains:
b%C3%83%C2%A9b%C3%83%C2%A9, diplayed bébé instead of bébé
stringByAddingPercentEncodingWithAllowedCharacters always uses UTF-8 representation, so I'm afraid you may need to do it yourself.
extension String {
func stringByAddingPercentEncodingForISOLatin1() -> String? {
let allowedCharacterSet = NSCharacterSet(charactersInString:
"0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "_-.~"
+ "=&" //You'd better remove this and encode each param.
)
if let data = self.dataUsingEncoding(NSISOLatin1StringEncoding) {
var result = ""
for i in 0..<data.length {
let ch = UnsafePointer<UInt8>(data.bytes)[i]
if ch >= 0x80 || !allowedCharacterSet.characterIsMember(unichar(ch)) {
result += String(format: "%%%02X", ch)
} else {
result.append(UnicodeScalar(ch))
}
}
return result
} else {
return nil
}
}
}
"bébé".stringByAddingPercentEncodingForISOLatin1()! //->"b%E9b%E9"
Here is a solution in Swift 4:
extension String {
// Url percent encoding according to RFC3986 specifications
// https://tools.ietf.org/html/rfc3986#section-2.1
func urlPercentEncoded(withAllowedCharacters allowedCharacters:
CharacterSet, encoding: String.Encoding) -> String {
var returnStr = ""
// Compute each char seperatly
for char in self {
let charStr = String(char)
let charScalar = charStr.unicodeScalars[charStr.unicodeScalars.startIndex]
if allowedCharacters.contains(charScalar) == false,
let bytesOfChar = charStr.data(using: encoding) {
// Get the hexStr of every notAllowed-char-byte and put a % infront of it, append the result to the returnString
for byte in bytesOfChar {
returnStr += "%" + String(format: "%02hhX", byte as CVarArg)
}
} else {
returnStr += charStr
}
}
return returnStr
}
}
Usage:
"aouäöü!".urlPercentEncoded(withAllowedCharacters: .urlQueryAllowed,
encoding: .isoLatin1)
// Results in -> "aou%E4%F6%FC!"
For Objective-C :
- (NSString *)stringByAddingPercentEncodingForISOLatin1 {
NSCharacterSet *allowedCharacterSet = [NSCharacterSet characterSetWithCharactersInString:#"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-.~=&"];
NSData *data = [self dataUsingEncoding:NSISOLatin1StringEncoding];
if (data) {
NSMutableString *result = [#"" mutableCopy];
const char *bytes = [data bytes];
for (NSUInteger i = 0; i < [data length]; i++)
{
unsigned char ch = (unsigned char)bytes[i];
if (ch >= 0x80 || ![allowedCharacterSet characterIsMember:ch]) {
[result appendFormat:#"%%%02X", ch];
} else {
[result appendFormat:#"%c", ch];
}
}
return [result copy];
}
return nil;}

How to translate push tokens between NSData and NSString representations in Swift?

I have used the following Objective C routines for years, to convert a NSData push token into a NSString (for use by a web side push service), and the inverse, to take a known NSString version of the token and recreate the NSData representation. Now, I find the need for the exact same capabilities, but in Swift.
The dataToHex Objective C code essentially uses printf formatting:
- (NSString *)dataToHex:(NSData *)data
{
NSMutableString *str = [NSMutableString stringWithCapacity:100];
const unsigned char *p = [data bytes];
NSUInteger len = [data length];
for(int i=0; i<len; ++i) {
[str appendFormat:#"%02.2X", p[i]];
}
return str;
}
The inverse translation is:
- (NSData *)hexToData:(NSString *)str
{
const char *ptr = [str cStringUsingEncoding:NSASCIIStringEncoding];
NSUInteger len = [str length]/2;
NSMutableData *data = [NSMutableData dataWithCapacity:len];
while(len--) {
char num[5] = (char[]){ '0', 'x', 0, 0, 0 };
num[2] = *ptr++;
num[3] = *ptr++;
uint8_t n = (uint8_t)strtol(num, NULL, 0);
[data appendBytes:&n length:1];
}
return data;
}
By "cleverly" overwriting a two byes in an ASCII array, the "0xXX" string is converted into a byte, which is then appended to the mutable data object.
Now that I'm coding in Swift, I need the same capabilities but have not found any posts with anything like the code above in Swift.
Converting from the NSData representation as supplied by iOS matches the Objective C code almost line for line:
func dataToHex(data: NSData) -> String
{
var str: String = String()
let p = UnsafePointer<UInt8>(data.bytes)
let len = data.length
for var i=0; i<len; ++i {
str += String(format: "%02.2X", p[i])
}
return str
}
However, given an NSString object, the conversion back to a NSData object is a bit harder. You might need to do this if you are testing in the Simulator, have a string token from a real device, and need it to say register with a service.
The first approach I took tried to replicate code I used before, by creating a string with character pairs, and calling strtol:
func hexToData0(str: NSString) -> NSData {
let len = str.length/2
var data = NSMutableData(capacity:len)!
var num: [Int8] = [ 0, 0, 0 ]
let ptr = str.cStringUsingEncoding(NSUTF8StringEncoding)
for var i=0; i<len; ++i {
num[0] = ptr[i*2+0]
num[1] = ptr[i*2+1]
var n = UInt8 ( strtol(&num, nil, 16) )
data.appendBytes(&n, length:1)
}
return data;
}
I just felt the strtol was a bit of a hack, so I did the same using NSScanner that about the same code size, while most likely less efficient:
func hexToData1(str: NSString) -> NSData {
var data = NSMutableData(capacity: str.length/2)!
for var i = 0; i<str.length; i+=2 {
let r = NSRange(location: i, length: 2)
let s = str.substringWithRange(r)
let sc = NSScanner(string: s)
var val: UInt32 = 0
let ret = sc.scanHexInt(&val)
if ret {
var b = UInt8(val)
data.appendBytes(&b, length: 1)
} else {
assert(false, "Yikes!")
}
}
return data
}
Then, it occurred to me that I could do it all in Swift, no Darwin or Foundation needed, at the expense of a few more lines of code:
// Swift 4
func hexToData(str: String) -> Data {
let len = str.count/2
var data = Data(capacity:len)
let ptr = str.cString(using: String.Encoding.utf8)!
for i in 0..<len {
var num: UInt8 = 0
var multi: UInt8 = 16;
for j in 0..<2 {
let c: UInt8 = UInt8(ptr[i*2+j])
var offset: UInt8 = 0
switch c {
case 48...57: // '0'-'9'
offset = 48
case 65...70: // 'A'-'F'
offset = 65 - 10 // 10 since 'A' is 10, not 0
case 97...102: // 'a'-'f'
offset = 97 - 10 // 10 since 'a' is 10, not 0
default:
assert(false)
}
num += (c - offset)*multi
multi = 1
}
data.append(num)
}
return data;
}
I'm using the final hexToData in my code.

Access email in EKParticipant/EKAttendee

I want to get the email address of the attendee of an event in the EKEventKit.
I have the following code:
if ( event.attendees.count > 0)
{
NSArray *people = event.attendees;
for(EKParticipant *person in people)
{
if ( person.participantType == EKParticipantTypePerson && person.URL.resourceSpecifier.length > 0)
{
NSString *dataString = [NSString stringWithFormat:#"event_id=%ld&name=%#&is_me=%d&email=%#&role=%#",event_id,person.name, person.isCurrentUser,person.URL.resourceSpecifier, #"attendee"];
//<DO SOMETHING USEFUL WITH dataString>;
}
}
}
When I run the code person populates with the following data:
EKAttendee <0x17809acc0> {UUID = 4F657EA4-452A-412B-A9AA-FEC5551DC096; name = A. Real Person; email = realperson#therightdomain.com; status = 0; role = 0; type = 1}
How to I access the email field?
I tried (as above) to use URL.resourceSpecifier, but that frequently is some strange string that is definitely NOT an email address.
The "Description" of the EKParticipant object is a property list of sorts. I tried several different methods of parsing that list into something containing key:value pairs unsuccessfully. So I wrote the following:
// This is re-useable code that converts any class description field into a dictionary that can be parsed for info
NSMutableDictionary *descriptionData = [NSMutableDictionary dictionary];
for (NSString *pairString in [person.description componentsSeparatedByString:#";"])
{
NSArray *pair = [pairString componentsSeparatedByString:#"="];
if ( [pair count] != 2)
continue;
[descriptionData setObject:[[pair objectAtIndex:1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] forKey:[[pair objectAtIndex:0]stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
}
With this I simply get the email address with
[descriptionData valueForKey:#"email"]
I tried to answer this same question in "how to get ekevent EKparticipant email?" thread:
What you need to do is use EKPrincipal:ABRecordWithAddressBook and then extract email from there. Like this:
NSString *email = nil;
ABAddressBookRef book = ABAddressBookCreateWithOptions(nil, nil);
ABRecordRef record = [self.appleParticipant ABRecordWithAddressBook:book];
if (record) {
ABMultiValueRef value = ABRecordCopyValue(record, kABPersonEmailProperty);
if (value
&& ABMultiValueGetCount(value) > 0) {
email = (__bridge id)ABMultiValueCopyValueAtIndex(value, 0);
}
}
Note that calling ABAddressBookCreateWithOptions is expensive so you might want to do that only once per session.
If you can't access the record, then fall back on URL.resourceSpecifier.
In Swift 4:
private static func getParticipantDescriptionData(_ participant: EKParticipant) -> [String:String] {
var descriptionData = [String: String]()
for pairString in participant.description.components(separatedBy: ";") {
let pair = pairString.components(separatedBy: "=")
if pair.count != 2 {
continue
}
descriptionData[pair[0].trimmingCharacters(in: .whitespacesAndNewlines)] =
pair[1].trimmingCharacters(in: .whitespacesAndNewlines)
}
return descriptionData
}

Adding NSDictionary keys with null values efficiently in ios5

I'm trying to add some values with keys in my dictionary for posting request to server. I have no issues on ios7 but having issue while creating dictionary in ios5, when a null value enters it doesnt insert that key or thereafter in the dictionary. This is a sample of my code
NSLog(#"name:%#, address:%#,city:%#, state:%#, country:%#, postal:%# ,phone:%#,age:%#, gender:%#",self.pop.firstNameTextField.text,self.pop.addressTextField.text, self.pop.cityTextField.text,[NSNumber numberWithInt:stateId],[NSNumber numberWithInt:countryId],self.pop.postalCodeTextField.text,self.pop.phoneNumberTextField.text,self.pop.ageTextField.text,self.pop.genderTextField.text);
NSDictionary *details = [NSDictionary dictionaryWithObjectsAndKeys:
self.pop.firstNameTextField.text,#"name",
self.pop.addressTextField.text,#"address",
self.pop.cityTextField.text,#"city",
[NSNumber numberWithInt:stateId],#"state",
[NSNumber numberWithInt:countryId],#"country",
self.pop.postalCodeTextField.text,#"postalCode",
self.pop.phoneNumberTextField.text,#"phoneNo",
self.pop.ageTextField.text,#"age",
self.pop.genderTextField.text,#"gender",
nil];
NSLog(#"MyDetails:%#",details);
Log result in ios 7:
name: GamerLegend,address:india,city:, state:1, country:1, postal: ,phone:, age:, gender:
MyDetails:
{
address = India;
age = "";
city = "";
country = 1;
gender = "";
name = "GamerLegend";
phoneNo = "";
postalCode = "";
state = 1;
}
Same Log result in ios5:
name: GamerLegend,address:haripad,city:(null), state:1, country:1, postal:(null) ,phone:(null), age:(null), gender:(null)
MyDetails:
{
address = India;
name = "GamerLegend";
}
I want the log of ios5 same as that of ios7. I did some search and I know I can compare the value beforehand to check if its null and then add to dictionary with empty string. But that will be so tedious. I was wondering if there is any other simple workaround for this. Is there any better solution?
This is my current solution
-(void) checkForNull
{
if([self.pop.firstNameTextField.text length] == 0)
self.pop.firstNameTextField.text=#"";
if([self.pop.addressTextField.text length] == 0)
self.pop.addressTextField.text=#"";
if([self.pop.cityTextField.text length] == 0)
self.pop.cityTextField.text=#"";
if([self.pop.postalCodeTextField.text length] == 0)
self.pop.postalCodeTextField.text=#"";
if([self.pop.phoneNumberTextField.text length] == 0)
self.pop.phoneNumberTextField.text=#"";
if([self.pop.ageTextField.text length] == 0)
self.pop.ageTextField.text=#"18";
if([self.pop.genderTextField.text length] == 0)
self.pop.genderTextField.text=#"";
}
This should work if it fits your requirements:
Create an array of EXPECTED keys. Then you loop through this and compare this with the output of the allKeys method in your dictionary. If a key is missing there you could add the missing key with an empty string, [NSNull null], or whatever floats your boat. (You would need to make the dictionary mutable of course).
Something like this:
for (NSString *expectedKey in allExpectedKeys) {
BOOL keyMissing = YES;
for (NSString *existingKey in [dictionary allKeys]) {
if ([expectedKey isEqualToString:existingKey]) {
keyMissing = NO;
break;
}
}
if (keyMissing) {
[dictionary setObject:[NSNull null] forKey:expectedKey];
}
}

How can I convert my device token (NSData) into an NSString?

I am implementing push notifications. I'd like to save my APNS Token as a String.
- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)newDeviceToken
{
NSString *tokenString = [NSString stringWithUTF8String:[newDeviceToken bytes]]; //[[NSString alloc]initWithData:newDeviceToken encoding:NSUTF8StringEncoding];
NSLog(#"%#", tokenString);
NSLog(#"%#", newDeviceToken);
}
The first line of code prints null. the second prints the token. How can I get my newDeviceToken as an NSString?
If anyone is looking for a way to do this in Swift:
Swift 3 introduces the Data type, with value semantics. To convert the deviceToken to a String, you can do as follows:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
print(token)
}
Old answer using NSData:
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
let tokenChars = UnsafePointer<CChar>(deviceToken.bytes)
var tokenString = ""
for i in 0..<deviceToken.length {
tokenString += String(format: "%02.2hhx", arguments: [tokenChars[i]])
}
print("tokenString: \(tokenString)")
}
Someone Helped me with this.I am just passing along
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
const unsigned *tokenBytes = [deviceToken bytes];
NSString *hexToken = [NSString stringWithFormat:#"%08x%08x%08x%08x%08x%08x%08x%08x",
ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];
[[MyModel sharedModel] setApnsToken:hexToken];
}
You could use this
- (NSString *)stringWithDeviceToken:(NSData *)deviceToken {
const char *data = [deviceToken bytes];
NSMutableString *token = [NSMutableString string];
for (NSUInteger i = 0; i < [deviceToken length]; i++) {
[token appendFormat:#"%02.2hhX", data[i]];
}
return [token copy];
}
For those who want in Swift 3 and most easier method
func extractTokenFromData(deviceToken:Data) -> String {
let token = deviceToken.reduce("", {$0 + String(format: "%02X", $1)})
return token.uppercased();
}
Note - This will not work when compiling with the iOS 13 or later SDK
use this :
NSString * deviceTokenString = [[[[deviceToken description]
stringByReplacingOccurrencesOfString: #"<" withString: #""]
stringByReplacingOccurrencesOfString: #">" withString: #""]
stringByReplacingOccurrencesOfString: #" " withString: #""];
NSLog(#"The generated device token string is : %#",deviceTokenString);
Explanation of %02.2hhx in the high vote answer:
%: Introduces the x conversion specifier.
02: The minimum width of the converted value is 2. If the converted value has fewer bytes than the field width, it shall be padded with 0 on the left.
.2: Gives the minimum number of digits to appear for the x conversion specifier.
hh: Specifies that the x conversion specifier applies to a signed char or unsigned char argument (the argument will have been promoted according to the integer promotions, but its value shall be converted to signed char or unsigned char before printing).
x: The unsigned argument shall be converted to unsigned hexadecimal format in the style "dddd"; the letters "abcdef" are used. The precision specifies the minimum number of digits to appear; if the value being converted can be represented in fewer digits, it shall be expanded with leading zeros. The default precision is 1. The result of converting zero with an explicit precision of zero shall be no characters.
For more details, see the IEEE printf specification.
Based on the above explanation, I think it is better to change %02.2hhx to %02x or %.2x.
For Swift 5, the following methods are all feasible:
deviceToken.map({String(format: "%02x", $0)}).joined()
deviceToken.map({String(format: "%.2x", $0)}).joined()
deviceToken.reduce("", {$0 + String(format: "%02x", $1)})
deviceToken.reduce("", {$0 + String(format: "%.2x", $1)})
The test is as follows:
let deviceToken = (0..<32).reduce(Data(), {$0 + [$1]})
print(deviceToken.reduce("", {$0 + String(format: "%.2x", $1)}))
// Print content:
// 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
In iOS 13 the description will be in different format. Kindly use below code to fetch the device token.
- (NSString *)fetchDeviceToken:(NSData *)deviceToken {
NSUInteger len = deviceToken.length;
if (len == 0) {
return nil;
}
const unsigned char *buffer = deviceToken.bytes;
NSMutableString *hexString = [NSMutableString stringWithCapacity:(len * 2)];
for (int i = 0; i < len; ++i) {
[hexString appendFormat:#"%02x", buffer[i]];
}
return [hexString copy];
}
It's my solution and It works well in my app:
NSString* newToken = [[[NSString stringWithFormat:#"%#",deviceToken]
stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#"<>"]] stringByReplacingOccurrencesOfString:#" " withString:#""];
convert NSData to NSString with stringWithFormat
trim the "<>"
remove the spaces
I think converting deviceToken to hex byte string has no sense. Why? You will send it to your backend, where it will be transformed back to bytes to be pushed to APNS. So, use NSData's method base64EncodedStringWithOptions, push it to server, and then use reverse base64decoded data :) That is so much easier :)
NSString *tokenString = [tokenData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
In iOS 13 description will break so use this
let deviceTokenString = deviceToken.map { String(format: "%02x", $0) }.joined()
For clarity, let’s break this down and explain each part:
The map method operates on each element of a sequence. Because Data is a sequence of bytes in Swift, the passed closure is evaluated for each byte in deviceToken.
The String(format:) initializer evaluates each byte in the data (represented by the anonymous parameter $0) using the %02x format specifier, to produce a zero-padded, 2-digit hexadecimal representation of the byte / 8-bit integer.
After collecting each byte representation created by the map method, joined() concatenates each element into a single string.
P.S don't use description gives different string in iOS 12 and iOS 13 and not safe as per future scope. Developers shouldn’t have relied on a specific format for an object’s description.
// iOS 12
(deviceToken as NSData).description // "<965b251c 6cb1926d e3cb366f dfb16ddd e6b9086a 8a3cac9e 5f857679 376eab7C>"
// iOS 13
(deviceToken as NSData).description // "{length = 32, bytes = 0x965b251c 6cb1926d e3cb366f dfb16ddd ... 5f857679 376eab7c }"
For more information read This.
2020
token as text...
let tat = deviceToken.map{ data in String(format: "%02.2hhx", data) }.joined()
or if you prefer
let tat2 = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
(result is the same)
This is a little bit shorter solution:
NSData *token = // ...
const uint64_t *tokenBytes = token.bytes;
NSString *hex = [NSString stringWithFormat:#"%016llx%016llx%016llx%016llx",
ntohll(tokenBytes[0]), ntohll(tokenBytes[1]),
ntohll(tokenBytes[2]), ntohll(tokenBytes[3])];
Functional Swift version
One liner:
let hexString = UnsafeBufferPointer<UInt8>(start: UnsafePointer(data.bytes),
count: data.length).map { String(format: "%02x", $0) }.joinWithSeparator("")
Here's in a reusable and self documenting extension form:
extension NSData {
func base16EncodedString(uppercase uppercase: Bool = false) -> String {
let buffer = UnsafeBufferPointer<UInt8>(start: UnsafePointer(self.bytes),
count: self.length)
let hexFormat = uppercase ? "X" : "x"
let formatString = "%02\(hexFormat)"
let bytesAsHexStrings = buffer.map {
String(format: formatString, $0)
}
return bytesAsHexStrings.joinWithSeparator("")
}
}
Alternatively, use reduce("", combine: +) instead of joinWithSeparator("") to be seen as a functional master by your peers.
Edit: I changed String($0, radix: 16) to String(format: "%02x", $0), because one digit numbers needed to having a padding zero
(I don't know yet how to mark a question as a duplicate of this other one, so I just posted my answer again)
Throwing my answer on the pile. Avoid using string parsing; It's not guaranteed by the docs that NSData.description will always work that way.
Swift 3 Implementation:
extension Data {
func hexString() -> String {
var bytesPointer: UnsafeBufferPointer<UInt8> = UnsafeBufferPointer(start: nil, count: 0)
self.withUnsafeBytes { (bytes) in
bytesPointer = UnsafeBufferPointer<UInt8>(start: UnsafePointer(bytes), count:self.count)
}
let hexBytes = bytesPointer.map { return String(format: "%02hhx", $0) }
return hexBytes.joined()
}
}
This will work for you,
NSUInteger dataLength = deviceToken.length;
const unsigned char *dataBuffer = (const unsigned char *)deviceToken.bytes;
NSMutableString *deviceTokenString = [NSMutableString stringWithCapacity:(dataLength * 2)];
for (int i = 0; i < dataLength; ++i) {
[deviceTokenString appendFormat:#"%02x", dataBuffer[i]];
}
NSLog(#"The generated device token string is : %#",deviceTokenString);
I've tried to test two different methods with format "%02.2hhx" and "%02x"
var i :Int = 0
var j: Int = 0
let e: Int = Int(1e4)
let time = NSDate.timeIntervalSinceReferenceDate
while i < e {
_ = deviceToken.map { String(format: "%02x", $0) }.joined()
i += 1
}
let time2 = NSDate.timeIntervalSinceReferenceDate
let delta = time2-time
print(delta)
let time3 = NSDate.timeIntervalSinceReferenceDate
while j < e {
_ = deviceToken.reduce("", {$0 + String(format: "%02x", $1)})
j += 1
}
let time4 = NSDate.timeIntervalSinceReferenceDate
let delta2 = time4-time3
print(delta2)
and the result is that the fastest is "%02x" at average 2.0 vs 2.6 for the reduced version:
deviceToken.reduce("", {$0 + String(format: "%02x", $1)})
Here's how you do it in Xamarin.iOS
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
var tokenStringBase64 = deviceToken.GetBase64EncodedString(NSDataBase64EncodingOptions.None);
//now you can store it for later use in local storage
}
Using updateAccumulatingResult is more efficient than the various other approaches found here, so here's the Swiftiest way to stringify your Data bytes:
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let token = deviceToken.reduce(into: "") { $0 += String(format: "%.2x", $1) }
print(token)
}
For Swift :
var characterSet: NSCharacterSet = NSCharacterSet( charactersInString: "<>" )
var deviceTokenString: String = ( deviceToken.description as NSString )
.stringByTrimmingCharactersInSet( characterSet )
.stringByReplacingOccurrencesOfString( " ", withString: "" ) as String
println( deviceTokenString )
NSString *tokenString = [[newDeviceToken description] stringByReplacingOccurrencesOfString:#"[<> ]" withString:#"" options:NSRegularExpressionSearch range:NSMakeRange(0, [[newDeviceToken description] length])];
Swift:
let tokenString = deviceToken.description.stringByReplacingOccurrencesOfString("[ <>]", withString: "", options: .RegularExpressionSearch, range: nil)
What about one line solution?
Objective C
NSString *token = [[data.description componentsSeparatedByCharactersInSet:[[NSCharacterSet alphanumericCharacterSet]invertedSet]]componentsJoinedByString:#""];
Swift
let token = data.description.components(separatedBy: CharacterSet.alphanumerics.inverted).joined()
-(NSString *)deviceTokenWithData:(NSData *)data
{
NSString *deviceToken = [[data description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#"<>"]];
deviceToken = [deviceToken stringByReplacingOccurrencesOfString:#" " withString:#""];
return deviceToken;
}
Swift
// make sure that we have token for the devie on the App
func application(application: UIApplication
, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
var tokenStr = deviceToken.description
tokenStr = tokenStr.stringByReplacingOccurrencesOfString("<", withString: "", options: [], range: nil)
tokenStr = tokenStr.stringByReplacingOccurrencesOfString(">", withString: "", options: [], range: nil)
tokenStr = tokenStr.stringByReplacingOccurrencesOfString(" ", withString: "", options: [], range: nil)
print("my token is: \(tokenStr)")
}
Use excellent category!
// .h file
#interface NSData (DeviceToken)
- (NSString *)stringDeviceToken;
#end
// .m file
#import "NSData+DeviceToken.h"
#implementation NSData (DeviceToken)
- (NSString *)stringDeviceToken {
const unsigned *deviceTokenBytes = [deviceToken bytes];
NSString *deviceToken = [NSString stringWithFormat:#"%08x%08x%08x%08x%08x%08x%08x%08x",
ntohl(deviceTokenBytes[0]), ntohl(deviceTokenBytes[1]), ntohl(deviceTokenBytes[2]),
ntohl(deviceTokenBytes[3]), ntohl(deviceTokenBytes[4]), ntohl(deviceTokenBytes[5]),
ntohl(deviceTokenBytes[6]), ntohl(deviceTokenBytes[7])];
return deviceToken;
}
#end
// AppDelegate.m
#import "NSData+DeviceToken.h"
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
NSString *token = deviceToken.stringDeviceToken;
}
Works fine!
Swift 3:
If any one is looking for a way to get device token in Swift 3. Use the below modified snippet.
let characterSet: CharacterSet = CharacterSet( charactersIn: "<>" )
let deviceTokenString: String = (deviceToken.description as NSString)
.trimmingCharacters(in: characterSet as CharacterSet)
.replacingOccurrences(of: " ", with: "")
.uppercased()
print(deviceTokenString)
var token: String = ""
for i in 0..<deviceToken.count {
token += String(format: "%02.2hhx", deviceToken[i] as CVarArg)
}
print(token)
The solution #kulss posted here, while lacking in elegance but having the virtue of simplicity no longer works in iOS 13, since description will work differently for NSData. You can still use debugDescription though.
NSString * deviceTokenString = [[[[deviceToken debugDescription]
stringByReplacingOccurrencesOfString: #"<" withString: #""]
stringByReplacingOccurrencesOfString: #">" withString: #""]
stringByReplacingOccurrencesOfString: #" " withString: #""];
Try this one unless the data is null-terminated.
NSString* newStr = [[NSString alloc] initWithData:newDeviceToken
encoding:NSUTF8StringEncoding];
NSString *tokenstring = [[NSString alloc] initWithData:token encoding:NSUTF8StringEncoding];

Resources