How to get a unique device ID in Swift? - ios

How can I get a device's unique ID in Swift?
I need an ID to use in the database and as the API-key for my web service in my social app. Something to keep track of this devices daily use and limit its queries to the database.

You can use this (Swift 3):
UIDevice.current.identifierForVendor!.uuidString
For older versions:
UIDevice.currentDevice().identifierForVendor
or if you want a string:
UIDevice.currentDevice().identifierForVendor!.UUIDString
There is no longer a way to uniquely identify a device after the user uninstalled the app(s). The documentation says:
The value in this property remains the same while the app (or another app from the same vendor) is installed on the iOS device. The value changes when the user deletes all of that vendor’s apps from the device and subsequently reinstalls one or more of them.
You may also want to read this article by Mattt Thompson for more details:
http://nshipster.com/uuid-udid-unique-identifier/
Update for Swift 4.1, you will need to use:
UIDevice.current.identifierForVendor?.uuidString

You can use devicecheck (in Swift 4)
Apple documentation
func sendEphemeralToken() {
//check if DCDevice is available (iOS 11)
//get the **ephemeral** token
DCDevice.current.generateToken {
(data, error) in
guard let data = data else {
return
}
//send **ephemeral** token to server to
let token = data.base64EncodedString()
//Alamofire.request("https://myServer/deviceToken" ...
}
}
Typical usage:
Typically, you use the DeviceCheck APIs to ensure that a new user has not already redeemed an offer under a different user name on the same device.
Server action needs:
See WWDC 2017 — Session 702 (24:06)
more from Santosh Botre article - Unique Identifier for the iOS Devices
Your associated server combines this token with an authentication key that you receive from Apple and uses the result to request access to the per-device bits.

For Swift 3.X Latest Working Code, Easily usage;
let deviceID = UIDevice.current.identifierForVendor!.uuidString
print(deviceID)

You can use identifierForVendor public property present in UIDevice class
let UUIDValue = UIDevice.currentDevice().identifierForVendor!.UUIDString
print("UUID: \(UUIDValue)")
EDIT
Swift 3:
UIDevice.current.identifierForVendor!.uuidString
END EDIT

In swift 4,5
You can get UUID using below code.
print(UIDevice.current.identifierForVendor!.uuidString)
Output
Apart from that you can get multiple properties from connected Device.
UIDevice.current.name // e.g. "My iPhone"
UIDevice.current.model // e.g. #"iPhone", #"iPod touch"
UIDevice.current.localizedModel // localized version of model
UIDevice.current.systemName // e.g. #"iOS"
UIDevice.current.systemVersion // e.g. #"15.5"

Swift 2.2
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let userDefaults = NSUserDefaults.standardUserDefaults()
if userDefaults.objectForKey("ApplicationIdentifier") == nil {
let UUID = NSUUID().UUIDString
userDefaults.setObject(UUID, forKey: "ApplicationIdentifier")
userDefaults.synchronize()
}
return true
}
//Retrieve
print(NSUserDefaults.standardUserDefaults().valueForKey("ApplicationIdentifier")!)

if (UIDevice.current.identifierForVendor?.uuidString) != nil
{
self.lblDeviceIdValue.text = UIDevice.current.identifierForVendor?.uuidString
}

From iOS 11 onwards you can use Apple's api: DeviceCheck

class func uuid(completionHandler: #escaping (String) -> ()) {
if let uuid = UIDevice.current.identifierForVendor?.uuidString {
completionHandler(uuid)
}
else {
// If the value is nil, wait and get the value again later. This happens, for example, after the device has been restarted but before the user has unlocked the device.
// https://developer.apple.com/documentation/uikit/uidevice/1620059-identifierforvendor?language=objc
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
uuid(completionHandler: completionHandler)
}
}
}

I've tried with
let UUID = UIDevice.currentDevice().identifierForVendor?.UUIDString
instead
let UUID = NSUUID().UUIDString
and it works.

Related

iOS swift how to know if any contact is updated even when app is killed

I have seen many SO question curious about this case but still I am posting this as many of developers out there may also want to know this another reason is that no solution is working for me .
I have used following code but it only works when My app is in background. but I am not notified when my app is killed and meanwhile user has updated the info of any contact. So in this case I am not sure how to do it.
What I am doing: here is a code snippet what I am trying to do
From iOS 9 you can register your class to observe CNContactStoreDidChangeNotification
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: #selector(addressBookDidChange),
name: NSNotification.Name.CNContactStoreDidChange,
object: nil)
And then:
#objc func addressBookDidChange(notification: NSNotification){
//Handle event here...
}
I found this solution over here:
Whats Happening: Through this way I am able to get my app notified once the user has updated his contact while app is in background.
What I want: I just want to know that if the user has updated any contact even though my app was killed then How to get my app notified with updated contacts?
Please let me know if you have solution of this issue in advance.
UPDATE: I have seen Whatsapp doing this. Is there anyone who can tell me how Whatsapp is doing this?
To check if a contact has changed you can use a custom hash function because the native one only checks for the identifier:
extension CNContact {
var customHash : Int {
var hasher = Hasher()
hasher.combine(identifier)
hasher.combine(contactType)
hasher.combine(namePrefix)
hasher.combine(givenName)
hasher.combine(middleName)
hasher.combine(familyName)
hasher.combine(previousFamilyName)
hasher.combine(nameSuffix)
hasher.combine(nickname)
hasher.combine(organizationName)
hasher.combine(departmentName)
hasher.combine(jobTitle)
hasher.combine(phoneticGivenName)
hasher.combine(phoneticMiddleName)
hasher.combine(phoneticFamilyName)
if #available(iOS 10.0, *) {
hasher.combine(phoneticOrganizationName)
}
hasher.combine(note)
hasher.combine(imageData)
hasher.combine(thumbnailImageData)
if #available(iOS 9.0, *) {
hasher.combine(imageDataAvailable)
}
hasher.combine(phoneNumbers)
hasher.combine(emailAddresses)
hasher.combine(postalAddresses)
hasher.combine(urlAddresses)
hasher.combine(contactRelations)
hasher.combine(socialProfiles)
hasher.combine(instantMessageAddresses)
hasher.combine(birthday)
hasher.combine(nonGregorianBirthday)
hasher.combine(dates)
return hasher.finalize()
}
}
(You can remove fields you don't care)
Then you have to keep a dictionary inside your app to store the hash values of all the contacts, to build it just do:
let hashedContacts = [String:Int]()
for contact in allContacts {
hashedContacts[contact.identifier] = contact.customHash
}
You have to store it on the file system.
Whenever a contact is updated, you update it:
hashedContacts[updatedContact.identifier] = updatedContact.customHash
Then at every launch, you load the saved dictionary, and you check for differences:
for contact in allContacts {
if contact.customHash != savedHashedValues[contact.identifier] {
// This contact has changed since last launch
...
}
}
And voilà!
EDIT:
How to save the hash map on disk...
var hashedContacts = ...
guard let name = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("hashedContacts")
else { return }
try? (hashedContacts as NSDictionary).write(to: name)
How to load the hash map from disk...
guard
let name = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("hashedContacts"),
let loadedContacts = (try? NSDictionary(contentsOf: name, error: ())) as? [String:Int]
else { return }
// Do whatever you want with loaded contacts...
Whenever you open your app you need to get all the contacts from the contact list and can compare to previous one which is saved inside of your app. After that you can push your contact list to server.
What you can do is send an update notification to your application on launch screen. This might have an illusion to your user that you have done the changes while in background.

uuid changes everytime in iOS [duplicate]

How can I get a device's unique ID in Swift?
I need an ID to use in the database and as the API-key for my web service in my social app. Something to keep track of this devices daily use and limit its queries to the database.
You can use this (Swift 3):
UIDevice.current.identifierForVendor!.uuidString
For older versions:
UIDevice.currentDevice().identifierForVendor
or if you want a string:
UIDevice.currentDevice().identifierForVendor!.UUIDString
There is no longer a way to uniquely identify a device after the user uninstalled the app(s). The documentation says:
The value in this property remains the same while the app (or another app from the same vendor) is installed on the iOS device. The value changes when the user deletes all of that vendor’s apps from the device and subsequently reinstalls one or more of them.
You may also want to read this article by Mattt Thompson for more details:
http://nshipster.com/uuid-udid-unique-identifier/
Update for Swift 4.1, you will need to use:
UIDevice.current.identifierForVendor?.uuidString
You can use devicecheck (in Swift 4)
Apple documentation
func sendEphemeralToken() {
//check if DCDevice is available (iOS 11)
//get the **ephemeral** token
DCDevice.current.generateToken {
(data, error) in
guard let data = data else {
return
}
//send **ephemeral** token to server to
let token = data.base64EncodedString()
//Alamofire.request("https://myServer/deviceToken" ...
}
}
Typical usage:
Typically, you use the DeviceCheck APIs to ensure that a new user has not already redeemed an offer under a different user name on the same device.
Server action needs:
See WWDC 2017 — Session 702 (24:06)
more from Santosh Botre article - Unique Identifier for the iOS Devices
Your associated server combines this token with an authentication key that you receive from Apple and uses the result to request access to the per-device bits.
For Swift 3.X Latest Working Code, Easily usage;
let deviceID = UIDevice.current.identifierForVendor!.uuidString
print(deviceID)
You can use identifierForVendor public property present in UIDevice class
let UUIDValue = UIDevice.currentDevice().identifierForVendor!.UUIDString
print("UUID: \(UUIDValue)")
EDIT
Swift 3:
UIDevice.current.identifierForVendor!.uuidString
END EDIT
In swift 4,5
You can get UUID using below code.
print(UIDevice.current.identifierForVendor!.uuidString)
Output
Apart from that you can get multiple properties from connected Device.
UIDevice.current.name // e.g. "My iPhone"
UIDevice.current.model // e.g. #"iPhone", #"iPod touch"
UIDevice.current.localizedModel // localized version of model
UIDevice.current.systemName // e.g. #"iOS"
UIDevice.current.systemVersion // e.g. #"15.5"
Swift 2.2
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let userDefaults = NSUserDefaults.standardUserDefaults()
if userDefaults.objectForKey("ApplicationIdentifier") == nil {
let UUID = NSUUID().UUIDString
userDefaults.setObject(UUID, forKey: "ApplicationIdentifier")
userDefaults.synchronize()
}
return true
}
//Retrieve
print(NSUserDefaults.standardUserDefaults().valueForKey("ApplicationIdentifier")!)
if (UIDevice.current.identifierForVendor?.uuidString) != nil
{
self.lblDeviceIdValue.text = UIDevice.current.identifierForVendor?.uuidString
}
From iOS 11 onwards you can use Apple's api: DeviceCheck
class func uuid(completionHandler: #escaping (String) -> ()) {
if let uuid = UIDevice.current.identifierForVendor?.uuidString {
completionHandler(uuid)
}
else {
// If the value is nil, wait and get the value again later. This happens, for example, after the device has been restarted but before the user has unlocked the device.
// https://developer.apple.com/documentation/uikit/uidevice/1620059-identifierforvendor?language=objc
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
uuid(completionHandler: completionHandler)
}
}
}
I've tried with
let UUID = UIDevice.currentDevice().identifierForVendor?.UUIDString
instead
let UUID = NSUUID().UUIDString
and it works.

Transfer data from project to widget in swift

At my project i need to send user id's to widget in iOS. But for do that, my user needs to open application once. Without opening, information stays only 1 day, after that it vanishes and widget stops showing information and await for opening application.
For do that i used appGroup.
What is the correct way to use transfer data from my project to widget?
Swift 5
Follow these steps to pass data from the host app to extensions.
Select project target > Capabilities > add new app group (if you have enabled permissions for your developer account otherwise enable that first)
Select the extension target and repeat the same.
if let userDefaults = UserDefaults(suiteName: "group.com.yourAppgroup") {
createEventDic.removeAll()
let eventDic = NSMutableDictionary()
eventDic.setValue("YourString", forKey: "timeFontName")
createEventDic.append(eventDic)
let resultDic = try? NSKeyedArchiver.archivedData(withRootObject: createEventDic, requiringSecureCoding: false)
userDefaults.set(resultDic, forKey: "setWidget")
userDefaults.synchronize()
} else {
}
Now go to your app extension and do these steps to get the passed data.
if let userDefaults = UserDefaults(suiteName: "group.com.yourAppGroup") {
guard let testcreateEvent = userDefaults.object(forKey: "testcreateEvent") as? NSData else {
print("Data not found in UserDefaults")
return
}
do {
guard let eventsDicArray = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(testcreateEvent as Data) as? [NSMutableDictionary] else {
fatalError("loadWidgetDataArray - Can't get Array")
}
for eventDic in eventsDicArray {
let timeFontName = eventDic.object(forKey: "timeFontName") as? String ?? ""
}
} catch {
fatalError("loadWidgetDataArray - Can't encode data: \(error)")
}
}
Hopefully, it will help. Cheers!
For do that i used appGroup.
What is the correct way to use transfer data from my project to
widget?
What you did so far (App Grouping) is one of the steps that you should follow. Next, as mentioned in App Extension Programming Guide - Sharing Data with Your Containing App:
After you enable app groups, an app extension and its containing app
can both use the NSUserDefaults API to share access to user
preferences. To enable this sharing, use the initWithSuiteName: method
to instantiate a new NSUserDefaults object, passing in the identifier
of the shared group.
So, what you have to do so far is to let the data to be transferred by the UserDefautls. For instance:
if let userDefaults = UserDefaults(suiteName: "group.com.example.myapp") {
userDefaults.set(true, forKey: "myFlag")
}
thus you could pass it to the widget:
if let userDefaults = UserDefaults(suiteName: "group.com.example.myapp") {
let myFlag = userDefaults.bool(forKey: "myFlag")
}
And you follow the same approach for passing the data vise-versa (from the widget to the project).
In Xamarin Forms, we need to use DI to pass the data to the ios project then we can put it into NSUserDefaults
Info: Grouping application is mandatory
Xamarin iOS project - Putting Data into NSUserDefaults
var plist = new NSUserDefaults("group.com.test.poc", NSUserDefaultsType.SuiteName);
plist.SetBool(true, "isEnabled");
plist.Synchronize();
Today Extension - Getting data from NSUserDefaults
var plist = new NSUserDefaults("group.com.test.poc", NSUserDefaultsType.SuiteName);
var result = plist.BoolForKey("isEnabled");
Console.WriteLine($"The result of NSUserdefaults: logesh {result}");

Will deviceid saved in iOS keychain be synced across all devices using same iCloud account?

I wanted to have same device id for the device even after re-installing the app. I was planning to save it to keychain and retrieve it every time.
I want to know if keychain data will get synced across all the devices using the same iCloud email id.
I am using the keychainwrapper in this downloadable_link.
This is what I ended up doing.
I read that the same UUID will be shared across all devices with same iCloud account.(Not sure about this.)
I had to restrict app login for upto 3 devices for each email id. If keychain data will get synced, then I cannot do this . If I have 3 different iPhones, it will be considered as a single device only if the user uses same icloud account.
After some googleing I came across SAMKeychain.
It has a property called synchronizationmode which I think will do the job of preventing the keychain key /value from syncing it to iCloud.
I use the below code to generate UUID.
func UUID() -> String {
let bundleName = Bundle.main.infoDictionary!["CFBundleName"] as! String
let accountName = Bundle.main.bundleIdentifier! + String(Bundle.main.bundleIdentifier!.characters.reversed()) // just one extra layer of protection.. :p
var applicationUUID = SAMKeychain.password(forService: bundleName, account: accountName)
if applicationUUID == nil {
applicationUUID = (UIDevice.current.identifierForVendor?.uuidString)!
// Save applicationUUID in keychain without synchronization
let query = SAMKeychainQuery()
query.service = bundleName
query.account = accountName
query.password = applicationUUID
query.synchronizationMode = SAMKeychainQuerySynchronizationMode.no
do {
try query.save()
} catch let error as NSError {
print("SAMKeychainQuery Exception: \(error)")
}
}
return applicationUUID!
}
You can check the sample app in swift 3 here in GITHUB.If someone has a better answer , Please let me know.
NOTE: SAMKeychain from their official github page was not working properly for me. Please use the one I am using in the UniqueUUIDAPP. I had to make some code commenting to make it work.

Creating Architecture for Sharing Photos with Apple Watch OS2

I'm trying to figure out the proper approach for sharing 10+ photos from an iOS app to an Apple Watch app using watchOS 2.
I want to transfer these images in the background so that the user doesn't have to open the iOS app in order to view the photos.
I've tried querying photos from Facebook and sending them to the watch via transferUserInfo() but the payload is too large:
FBSDKGraphRequest(graphPath: "me/photos?limit=2", parameters:["fields": "name, source"]).startWithCompletionHandler({ (connection, result, error) -> Void in
if (error != nil){
print(error.description)
}
else {
var arr = [NSData]()
for res in result["data"] as! NSArray {
if let string = res["source"] as? String {
if let url = NSURL(string: string) {
if let data = NSData(contentsOfURL: url){
arr.append(data)
}
}
}
}
print(arr)
if arr.count > 0 {
self.session.transferUserInfo(["image" : arr])
}
}
})
Any ideas how I should go about doing this?
The proper method is mentioned in the WCSession documentation:
Use the transferFile:metadata: method to transfer files in the background. Use this method in cases where you want to send more than a dictionary of values. For example, use this method to send images or file-based documents.
The images will be asynchronously delivered to the watch on a background thread. session:didReceiveFile: will be called when the watch successfully receives an image.
Make sure to include (date) metadata with the image, and remove any existing images from the watch which are no longer a part of the ten most recent Facebook uploads.

Resources