How do I convert apns data to string in Swift 3? - apple-push-notifications

let tokenChars = UnsafePointer<CChar>(deviceToken.bytes)
var tokenString = ""
for i in 0..<deviceToken.length {
tokenString += String(format: "%02.2hhx", arguments: [tokenChars[i]])
}
DDLogDebug("Device Token: \(tokenString)")
^ This no longer works. Mostly because there is no deviceToken.bytes anymore.
I tried to figure out how to use .withUnsafeBytes, but am failing.
Any help would be fantastic, I just need the string version of the device token, thanks!

There is a one-line solution, deviceToken is Data
let tokenString = deviceToken.map{ String(format: "%02x", $0) }.joined()

Related

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

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?
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
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.
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) }
}
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:
First we have to grab all the bytes in the device token by calling
the ToArray() method on it.
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[].
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("-", "");
}
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
}
func getStringFrom(deviceToken: Data) -> String {
var token = ""
for i in 0..<deviceToken.count {
token += String(format: "%02.2hhx", arguments: [deviceToken[i]])
}
return token
}
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];
}
use deviceToken.debugDescription

field has value confirmed but still get unwrap error?

This block of code fails when trying to set appURL, even though the if test succeeds and that the managed object contact has all fields set to non nil values and for certain contact.facebook has a value so I cant see why I am getting found nil while trying to unwrap?
func openFacebook() {
if (contact.facebook) != nil && (contact.facebook) != "" {
// build url to users facebook page
let appURL = NSURL(string: String(format: "fb://profile=%#", contact.facebook!))!
// build url to user page on facebook web site
let webURL = NSURL(string: String(format: "http://www.facebook.com/%#", contact.facebook!))!
openURL(appURL, webURL: webURL)
}
}
I suggest to do it in a better way like :
func openFacebook() {
if let contact = contact.facebook where contact != "" {
// build url to users facebook page
let appURL = NSURL(string: String(format: "fb://profile=%#", contact))!
// build url to user page on facebook web site
let webURL = NSURL(string: String(format: "http://www.facebook.com/%#", contact))!
openURL(appURL, webURL: webURL)
}
}
I think this will a better cleaner way to do nil checks and not force unwraping values later on.

Authentication keeps failing with Alamofire

I recently migrated my project to Swift 2.0 and got lots of errors.
I fixed most of them, but this one keeps making an error.
I am calling Bing Search API using Alamofire as below and I get an error saying "Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.}"
I understand this means that the authentication fails.
Could anybody advise me on how to fix this?
let percentedKeyword = searchKey.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
Let ulrStr: String = "https://api.datamarket.azure.com/Bing/Search/v1/News" + "? Query=" + percentedKeyword! + "&$top=10&$format=JSON"
let credentials = ":\(bingApiKey)"
let plainText = credentials.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
let base64 = plainText!.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
let headers = ["Authorization": "Basic \(base64)"]
Alamofire.request(.GET, urlStr, headers: headers)
.responseJSON { request, response, data in
switch data {
case Result.Success(let receivedValue):
self.bingJson = JSON(receivedValue)
case Result.Failure(_, let error as NSError):
print(error)
default:
print("do nothing")
}
}
Xcode version 7.0
Alamofire version 2.0.2
[Update]
I tried urlStr("https://api.datamarket.azure.com/Bing/Search/News?Query=%E4%B8%AD%E5%9B%BD&$top=10&$format=JSON") the web browser.
It asked me to type in user name and password, so I typed apiKey as password and kept the user name blank following the instruction by Microsoft doc
I got this error:Parameter: Query is not of type String
According to stackoverflow, this is because keyword is not percented, but I am adding percent to keyword string...
It turns out that I needed single quotation marks for search key words.
let percentedKeyword = searchKey.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
let urlStr: String = "https://api.datamarket.azure.com/Bing/Search/News" + "?Query=" + "'" + percentedKeyword! + "'" + "&$top=10&$format=JSON"
let credentials = ":\(bingApiKey)"
let plainText = credentials.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
let base64 = plainText!.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
let headers = ["Authorization": "Basic \(base64)"]
Alamofire.request(.GET, urlStr, headers: headers)
.responseJSON { request, response, data in
switch data {
case Result.Success(let receivedValue):
self.bingJson = JSON(receivedValue)
case Result.Failure(_, let error as NSError):
print(error)
default:
print("do nothing")
}
}
}

Setting the Desktop background on OSX using Swift 2

After an introduction to Javascript, I'm trying to tackle OSX/iOS programming by creating some simple tools to scratch my own itches.
However, right from the jump I hit a roadblock.
I found two examples that should work.
https://github.com/hinderberg/ios-swift-kurs/blob/master/swift-intro/wallpaper.swift
https://www.snip2code.com/Snippet/196825/Swift-shell-script-to-randomize-wallpape
Here's the second:
#!/usr/bin/env xcrun swift
import Foundation
import AppKit
let imagesDir = "/Users/david/Dropbox/Graphics/Wallpaper-HD/"
var err: NSError?
let fs = NSFileManager.defaultManager()
let filenames = fs.contentsOfDirectoryAtPath(imagesDir, error: &err) as [String]?
if let error = err {
NSLog(error.localizedDescription)
} else {
let imagenames = filenames!.filter { $0.hasSuffix(".jpg") || $0.hasSuffix("png") }
let ir = Int(arc4random_uniform(UInt32(imagenames.count)))
let imgurl = NSURL.fileURLWithPath(imagesDir + imagenames[ir])
let workspace = NSWorkspace.sharedWorkspace()
let screen = NSScreen.mainScreen()
let ok : Bool = workspace.setDesktopImageURL( imgurl!, forScreen: screen!, options: nil, error: nil )
if ok {
println( "New wallpaper: " + imagenames[ir] )
} else {
println("Oops!")
}
}
This didn't work in XCode 7 beta 3.
Hoping to reduce to the essentials, I arrived at:
#!/usr/bin/env xcrun swift
import Foundation
import AppKit
let imagesDir = "/Users/josh/Downloads/"
let singleImage = "/Users/josh/Downloads/xlarge.png"
let imgurl = NSURL.fileURLWithPath(singleImage)
let workspace = NSWorkspace.sharedWorkspace()
let screen = NSScreen.mainScreen()
let ok : Bool = workspace.setDesktopImageURL( imgurl, forScreen: screen!, options: nil, error: nil )
if ok {
print( "New wallpaper set!" )
} else {
print("Oops!")
}
And saved as the file wallpaper.swift.
On execution, the error is:
./wallpaper.swift:17:49: error: extra argument 'error' in call
let ok : Bool = workspace.setDesktopImageURL( imgurl, forScreen: screen!, options: nil, error: nil )
And now I'm completely stuck...
I've tried referring to NSWorkspace and NSScreen documentation as well as running through playground, but it's beyond my current skills.
Removing the extra argument it complains about (error: nil) simply gives a different error:
./wallpaper.swift:13:31: error: cannot invoke 'setDesktopImageURL' with an argument list of type '(NSURL, forScreen: NSScreen?, options: nil)'
let ok : Bool = workspace.setDesktopImageURL( imgurl, forScreen: screen, options: nil )
Where is the code failing, and how can I understand how to make it work properly?
In your example you're passing nil as options to the method.
I guess it worked before but now in the comments you showed the current method signature:
(url: NSURL, forScreen screen: NSScreen, options: [String : AnyObject]) throws
We see that options should be a non-Optional Dictionary.
It means you can't use nil anymore for the options parameter: if you don't have options, just pass an empty Dictionary.
Also, now in Swift 2, this method doesn't return a Bool anymore, it throws.
Meaning you have to use it with do try catch:
do {
let imgurl = NSURL.fileURLWithPath(singleImage)
let workspace = NSWorkspace.sharedWorkspace()
if let screen = NSScreen.mainScreen() {
try workspace.setDesktopImageURL(imgurl, forScreen: screen, options: [:])
}
} catch {
print(error)
}
Updated example for Swift 3:
do {
let imgurl = NSURL.fileURL(withPath: singleImage)
let workspace = NSWorkspace.shared()
if let screen = NSScreen.main() {
try workspace.setDesktopImageURL(imgurl, for: screen, options: [:])
}
} catch {
print(error)
}
For those of us using Xamarin Mac, this can be achieved like so:
string filepath = "/Users/you/Desktop/sweet-wallpaper.jpg";
var workspace = NSWorkspace.SharedWorkspace;
var screen = NSScreen.MainScreen;
NSUrl url = NSUrl.FromFilename(filepath);
NSDictionary options = new NSDictionary();
NSError errorContainer = new NSError();
workspace.SetDesktopImageUrl(url, NSScreen.MainScreen, options, errorContainer);
Updated version for Swift 5.x:
do {
let imageURL = URL(fileURLWithPath: "/path/to/image")
if let screen = NSScreen.main {
try NSWorkspace.shared.setDesktopImageURL(imageURL, for: screen, options: [:])
}
} catch {
print(error)
}

Displaying the username of current user (Swift)

Is it possible to display the username of the current user in a macOS app?
You can use the NSUserName() function:
var username = NSUserName()!
Swift 4.2
let userName = NSUserName()
let fullUserName = NSFullUserName()
The following did work for me. It was copied from this post.
CKContainer.default().requestApplicationPermission(.userDiscoverability) { (status, error) in
CKContainer.default().fetchUserRecordID { (record, error) in
CKContainer.default().discoverUserIdentity(withUserRecordID: record!, completionHandler: { (userID, error) in
let fullName = (userID?.nameComponents?.givenName)! + " " + (userID?.nameComponents?.familyName)!
})
}
}

Resources