How do I "Look Me Up By Email" - ios

I am making an iOS application and I would like to use the iCloud feature "Look Me Up By Email" found under iCloud in the Settings of iOS.
I would like to simplify the user experience by identifying users by their iCloud so they don't have to remember a login for my app.

As I understand this, this is a CloudKit feature related to CKDiscoverAllContactsOperation. You can discover people from your contacts with the same app and can be discovered by them. You need to request permission for that ability first, like this:
CKContainer.defaultContainer().requestApplicationPermission(CKApplicationPermissions.UserDiscoverability) { [unowned self] (status, error) -> Void in
//Your code handling error or success...
}
Note
Release notes for iOS 10 mention, that this operation and related functions will be changed. In iOS 10+ you need to use CKDiscoverAllUserIdentitiesOperation
Example of usage
init a container first, note that your container accountStatus must be correct
let container = CKContainer(identifier: "iCloud.com.YourContainerID")
Later, ask a permission
container.requestApplicationPermission(CKApplicationPermissions.userDiscoverability) { [unowned self] (status, error) -> Void in
if let err = error {
print(err)
}
else if status == CKApplicationPermissionStatus.granted{
//success
}
else{
print("Permission not granted")
print(status)
}
}
Later, you can get your current user record id and create a subscription for example (note that you don't need a permission and user record ID to create a subscription, but in my case it was needed to create a predicate):
container.fetchUserRecordID { [weak self] (recordID, error) in
let predicate = NSPredicate(format: "user = %#",userRecordID.recordName)
let subscription = CKSubscription(recordType: "myRecordType", predicate: predicate, options: CKSubscriptionOptions.firesOnRecordCreation)
container.publicCloudDatabase.save(subscription, completionHandler: { (subscription, error) in
//completion stuff here
}
}
Friends discoverability example
let discoverOperation = CKDiscoverAllUserIdentitiesOperation()
var users = [CKUserIdentity]()
discoverOperation.discoverAllUserIdentitiesCompletionBlock = { [weak self] (error: Error?) -> Void in
if let err = error as? NSError{
print("Discover Friends Error: \(err)")
} else {
//do whatever you want with discovered contacts
}
}
discoverOperation.userIdentityDiscoveredBlock = { (userIdentity: CKUserIdentity) -> Void in
users.append(userIdentity)
}
discoverOperation.queuePriority = Operation.QueuePriority.high //this option is up to your tasks
container.add(discoverOperation)

Related

CKQueryOperation queryCompletionBlock not called

I'm using a CKQueryOperation that apparently works fine for most of my users. However, for some of them it is not working. The issue is that queryCompletionBlock is not being called.
Analysing user logs I can see that it works fine for most of the users, but it doesn't work for a few of them. It fails in all kind of iPhone models. But iOS is always iOS 14.2 on the failing devices. Unfortunately, I can not reproduce the issue on my device and this makes impossible to debug it.
I've already checked that the issue is not related with the internet connection type (wifi or data)
Any idea?
This is the code
func fetchTeams(_ success: #escaping (_ result: [CKRecord]?) -> Void,
failure: #escaping (_ error: NSError) -> Void) {
bfprint("fetchTeams starts")
let type = RecordType.Team
let predicate = NSPredicate(format: "TRUEPREDICATE")
let query = CKQuery(recordType: type.rawValue, predicate: predicate)
let operation = CKQueryOperation(query: query)
allTeamsRecords = []
executeQueryOperation(operation,
success: success,
failure: failure)
}
private func executeQueryOperation(_ queryOperation: CKQueryOperation,
success: #escaping (_ result: [CKRecord]?) -> Void,
failure: #escaping (_ error: NSError) -> Void) {
bfprint("executeQueryOperation starts")
let configuration = CKOperation.Configuration()
configuration.qualityOfService = .userInitiated
queryOperation.configuration = configuration
queryOperation.queuePriority = .veryHigh
queryOperation.recordFetchedBlock = { [weak self] (record) in
guard let strongSelf = self else {
bfprint("CloudKitDataProvider was deallocated before we got all team records")
return
}
strongSelf.allTeamsRecords.append(record)
}
queryOperation.queryCompletionBlock = { [weak self] (cursor, error) in
bfprint("fetch teams operation completion block called")
if let cursor = cursor {
bfprint("We got a new cursor fetching teams")
let newOperation = CKQueryOperation(cursor: cursor)
guard let strongSelf = self else {
bfprint("CloudKitDataProvider was deallocated before we got all team records")
return
}
strongSelf.executeQueryOperation(newOperation,
success: success,
failure: failure)
}
else if let error = error {
DispatchQueue.main.async(execute: {
failure(error as NSError)
bfprint("Cloud Query Error - Fetching Teams): \(error)")
})
}
else {
DispatchQueue.main.async(execute: {
bfprint("Get teams finished successfully")
guard let strongSelf = self else {
bfprint("CloudKitDataProvider was deallocated before we execute success closure")
return
}
success(strongSelf.allTeamsRecords)
})
}
}
Self.publicDB.add(queryOperation)
bfprint("query added to database")
}
I don't know what's specifically wrong in your situation, but I might offer some general guidance with CloudKit as I've worked with it over the years. CloudKit is really reliable, but it's also a little unreliable. :)
Here are some tips:
Build in mechanisms to repeatedly check that you have the latest data.
Background notifications don't always come. Have a way to get data that may have been missed.
Development and production behave a little differently in that dev seems to be a bit less reliable overall.
The CloudKit Dashboard needs to be refreshed (like the whole page in your web browser) from time-to-time because the state shown can get stale even when using the reload and query buttons in the interface.
So in your case, you might have a way to repeatedly try the CKQueryOperation so that it gets reattempted if something is haywire on CloudKit. Maintaining a local cache that syncs with CloudKit is the best way I've found to make sure your data is accurate.
I'm 99% sure it was an iOS issue. After users updated to iOS 14.3 the problem disappeared

HKObserverQuery only runs when the application is reopened

So I've been following the instructions in this answer...
Healthkit background delivery when app is not running
The code runs fine and works whilst the application is open and says that background delivery is successful, however when I test the application by walking around and changing the clock on the device to an hour forward I do not receive any logs to let me know it has run. However, if I open the application again the observer query runs.
private func checkAuthorization(){
let healthDataToRead = Set(arrayLiteral: self.distanceQuantityType!)
healthKitStore.requestAuthorization(toShare: nil, read: healthDataToRead) { (success, error) in
if error != nil {
print(error?.localizedDescription)
print("There was an error requesting Authorization to use Health App")
}
if success {
print("success")
}
}
}
public func enableBackgroundDelivery() {
self.checkAuthorization()
self.healthKitStore.enableBackgroundDelivery(for: self.distanceQuantityType!, frequency: .hourly) { (success, error) in
if success{
print("Background delivery of steps. Success = \(success)")
}
if let error = error {
print("Background delivery of steps failed = \(error.localizedDescription)")
}
}
}
func observeDistance(_ handler:#escaping (_ distance: Double) -> Void) {
let updateHandler: (HKObserverQuery?, HKObserverQueryCompletionHandler?, Error?) -> Void = { query, completion, error in
if !(error != nil) {
print("got an update")
completion!()
} else {
print("observer query returned error: \(error)")
}
}
let query = HKObserverQuery(sampleType: self.distanceQuantityType!, predicate: nil, updateHandler: updateHandler)
self.healthKitStore.execute(query)
}
The query is initialised in the appDelegate method didFinishLaunching
This particular HealthKitQuery is asynchronous. You should wait until it finishes processing.
However, this case is not possible in didFinishLaunching. The application just ended execution and there is not enough time to process the query.
I would seriously suggest to rethink the logic behind the operation of your code. A good way to solve this would be to put the request elsewhere, preferrably after the needed operations were completed.

iOS app's home page not showing at first building time

I am creating an iOS contact app using swift. I have imported ContactsUI. I am trying to parse CNContact items using CNContactFetchRequest. when I try to build, it shows a white screen instate of home page (contact list in table view).
After bringing the app in background a alert shown for importing contacts. But this alert should show first time.
Why?
My code:
if CNContactStore.authorizationStatusForEntityType(.Contacts) == .NotDetermined {
store.requestAccessForEntityType(.Contacts, completionHandler: { (authorized: Bool, error: NSError?) -> Void in if authorized { self.ContactArr = self.createContactArr(contactCN) } })
}
else if CNContactStore.authorizationStatusForEntityType(.Contacts) == .Authorized {
self.ContactArr = self.createContactArr(contactCN) }
let keysToFetch = [CNContactFormatter.descriptorForRequiredKeysForStyle(.FullN‌​ame), CNContactImageDataKey,CNContactEmailAddressesKey,CNContactUr‌​lAddressesKey,CNCont‌​actNoteKey, CNContactPhoneNumbersKey,CNContactPostalAddressesKey]
let fetchRequest = CNContactFetchRequest(keysToFetch: keysToFetch) var contacts = [CNContact]() do { try store.enumerateContactsWithFetchRequest(fetchRequest, usingBlock: { (let contact, let stop) -> Void in contacts.append(contact) }) }
I need the solution so that I need not to go to the background mode for going to the home page.

Firebase Queries and Completion

I'm having some trouble writing a process to allow a user to delete their Firebase account which deletes their account and all the posts that they had in their account including images. I am querying the Users posts in Firebase Database, looping through and deleting them as they come out. In the middle of this loop, I call a function passing in the image URL to delete the image from Firebase storage. The problem I am having is that I can't get an ordered result because of asynchronous stuff. I thought having completion handlers would give it some order. Should I be looking into dispatch groups? Any help is greatly appreciated.
The start of my process with function call:
ref.deleteFirebaseDBUsersPosts(deleter) { (success) -> Void in
if success {
print("The user and their info is completely gone!")
}
}
Function that queries Firebase and loops through users posts and deletes:
func deleteFirebaseDBUsersPosts(uid: String, completion:(success: Bool) -> Void) {
let usersCurrentId = uid
ref.child(“posts”).queryOrderedByChild("uid").queryEqualToValue("\(uid)").observeEventType(.Value, withBlock: {snapshot in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot{
print("SNAP: \(snap)")
if let postedImg = snap.childSnapshotForPath("imageUrl").value {
let postImgUrl = postedImg as! String
self.deletingOldUsersImgsFromStorage(postImgUrl) { (success) -> Void in
}
}
let key = snap.key
ref.child(“posts”).child(key).removeValue()
ref.child(“users”).child(usersCurrentId).removeValue()
}
print("COMPLETING")
completion(success: true)
}
})
}
Deleting the image from Firebase Storage:
func deletingOldUsersImgsFromStorage(postImgUrl: String!, completion:(success: Bool) -> Void) {
let deleteImgRef = ref.child(“images”).child("[imageNAME]")
deleteImgRef.deleteWithCompletion({ (error) in
if (error != nil) {
print("DEVELOPER: There was an error when trying to delete the image from Firebase Storage")
} else {
print("IMAGE SUCCESSFULLY DELETED FROM FIREBASE STORAGE")
}
completion(success: true)
})
}
I dealt with a similar challenge when deleting a user and their data from Firebase. I handled it using dispatch groups to delete the user after my methods that deleted their data had completed.
func deleteUsersData(completion: (success: Bool) -> Void) {}
func deleteUsersPhotoUrl(completion: (success: Bool) -> Void) {}
func deleteUserFromFirebase(completion: (success: Bool) -> Void) {}
let group = dispatch_group_create()
dispatch_group_enter(group)
deleteUsersData { (success) in
if success {
dispatch_group_leave(group)
}
}
dispatch_group_enter(group)
deleteUsersPhotoUrl { (success) in
if success {
dispatch_group_leave(group)
}
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
deleteUserFromFirebase({ (success) in
print("Completely deleted user from Firebase")
})
}
Dispatch groups are great for handling situations like this where you only want to perform a task after the other tasks you specified have completed. Here is more info on how to use dispatch groups.

Detect when user tapped Don't Allow when making changes to photo library

When you want to make a change to a PHAsset, you wrap it up in a performChanges block. You get a success Bool and an error NSError in the completion block. Now I would like to show an alert to the user in the case the request failed. This does the trick:
PHPhotoLibrary.sharedPhotoLibrary().performChanges({ () -> Void in
let request = PHAssetChangeRequest(forAsset: asset)
request.creationDate = date
}, completionHandler: { (success: Bool, error: NSError?) -> Void in
dispatch_async(dispatch_get_main_queue()) {
if let error = error {
//present alert
}
}
})
The problem is when the user taps Don't Allow it also presents the alert. I don't want to do that, the user intentionally canceled it so there's no need to inform them it failed. But how can I detect that's what has occurred? The error userInfo is nil, it doesn't seem it provides any useful info to detect that case. Am I missing something?
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
NSLog(#"%ld",(long)status);
switch (status) {
case PHAuthorizationStatusAuthorized:
// code for display photos
NSLog(#"ffefwfwfwef");
case PHAuthorizationStatusRestricted:
break;
case PHAuthorizationStatusDenied:
//code for Dont Allow code
break;
default:
break;
}
}];
This is now possible by checking if the error is a PHPhotosError and if so checking its code to see if it's .userCancelled.
PHPhotoLibrary.shared().performChanges({
let request = PHAssetChangeRequest(forAsset: asset)
request.creationDate = date
}) { success, error in
guard let error = error else { return }
guard (error as? PHPhotosError)?.code != .userCancelled else { return }
DispatchQueue.main.async {
//present alert
}
}

Resources