CNContact fetching should be faster - swift

I am fetching contact from CNContact and it took .30 seconds to show thousands of contact in UI. My client want me to reduce time upto 0.01 second. He gave me example of Telegram app. Can any one help
Ex Code :
typealias ContactsHandler = (_ contacts : [CNContact] , _ error : NSError?) -> Void
extension CDContactsPickerVC {
func getContacts(_ completion: #escaping ContactsHandler) {
if contactsStore == nil {
//ContactStore is control for accessing the Contacts
contactsStore = CNContactStore()
}
let error = NSError(domain: "CDContactPickerErrorDomain", code: 1, userInfo: [NSLocalizedDescriptionKey: "No Contacts Access"])
switch CNContactStore.authorizationStatus(for: CNEntityType.contacts) {
case CNAuthorizationStatus.denied, CNAuthorizationStatus.restricted:
//User has denied the current app to access the contacts.
let productName = Bundle.main.infoDictionary!["CFBundleName"]!
let alert = UIAlertController(title: "Unable to access contacts", message: "\(productName) does not have access to contacts. Kindly enable it in privacy settings ", preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: { action in
completion([], error)
})
alert.addAction(okAction)
self.present(alert, animated: true, completion: nil)
case CNAuthorizationStatus.notDetermined:
//This case means the user is prompted for the first time for allowing contacts
contactsStore?.requestAccess(for: CNEntityType.contacts, completionHandler: { (granted, error) -> Void in
//At this point an alert is provided to the user to provide access to contacts. This will get invoked if a user responds to the alert
if (!granted ){
DispatchQueue.main.async(execute: { () -> Void in
completion([], error! as NSError?)
})
}
else{
self.getContacts(completion)
}
})
case CNAuthorizationStatus.authorized:
//Authorization granted by user for this app.
var contactsArray = [CNContact]()
let contactFetchRequest = CNContactFetchRequest(keysToFetch: allowedContactKeys())
do {
try contactsStore?.enumerateContacts(with: contactFetchRequest, usingBlock: { (contact, stop) -> Void in
//Ordering contacts based on alphabets in firstname
contactsArray.append(contact)
var key: String = "#"
//If ordering has to be happening via family name change it here.
if let firstLetter = contact.givenName[0..<1] , firstLetter.containsAlphabets() {
key = firstLetter.uppercased()
}
var contacts = [CNContact]()
if let segregatedContact = self.orderedContacts[key] {
contacts = segregatedContact
}
var identifierArray = [String]()
for contactDict in self.orderedContacts {
let contactArray = contactDict.value as [CNContact]
for cnObj in contactArray {
identifierArray.append(cnObj.identifier)
}
}
if identifierArray.contains(contact.identifier) {
// do nothing
}else {
contacts.append(contact)
self.orderedContacts[key] = contacts
}
})
self.sortedContactKeys = Array(self.orderedContacts.keys).sorted(by: <)
if self.sortedContactKeys.first == "#" {
self.sortedContactKeys.removeFirst()
self.sortedContactKeys.append("#")
}
completion(contactsArray, nil)
}
//Catching exception as enumerateContactsWithFetchRequest can throw errors
catch let error as NSError {
print(error.localizedDescription)
}
}
}
func allowedContactKeys() -> [CNKeyDescriptor]{
//We have to provide only the keys which we have to access. We should avoid unnecessary keys when fetching the contact. Reducing the keys means faster the access.
return [CNContactNamePrefixKey as CNKeyDescriptor,
CNContactGivenNameKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor,
CNContactPhoneNumbersKey as CNKeyDescriptor,
CNContactEmailAddressesKey as CNKeyDescriptor,
]
}
// MARK: - Contact Operations
open func reloadContacts() {
DispatchQueue.global(qos: .background).async {
self.getContacts( {(contacts, error) in
if (error == nil) {
DispatchQueue.main.async(execute: {
self.emptyContactLabel.isHidden = contacts.count > 0 ? true : false
self.tableView.reloadData()
})
self.updateContactsInLocalDB(contacts: contacts)
}
})
}
}

Related

Cannot share with UICloudSharingController; vanishes with "uploading" message

while presenting the UICloudSharingController on top of a view, it presents the screen and when I select the messages option to send a message to a person whom I want to share with, it gives a spinning wheel with "uploading" message and vanishes - attached.
However when I go to cloudkit dashboard the root record has been shared. But I cannot share it with specific person. Is it because it has shared global? How can I fix it?
self.shareInfraRecord(zoneID: appDelegate.privateContactZoneID, completion: { (status) in
if ( status == false) {
return
}
})
func shareInfraRecord(zoneID: CKRecordZone.ID, completion: #escaping(Bool) -> Void) {
if let rootRecord = self.rootRecord {
if self.rootRecord?.share == nil {
let sharingController = UICloudSharingController { (controller, preparationHandler: #escaping (CKShare?, CKContainer?, Error?) -> Void) in
let shareID = CKRecord.ID(recordName: UUID().uuidString, zoneID: zoneID)
var share = CKShare(rootRecord: rootRecord, shareID: shareID)
share[CKShare.SystemFieldKey.title] = Cloud.ShareInfrastructure.ContactShareTitleKey as CKRecordValue?
share[CKShare.SystemFieldKey.shareType] = Cloud.ShareInfrastructure.ContactShareTypeKey as CKRecordValue?
let modifyRecZoneOp = CKModifyRecordsOperation(recordsToSave:[rootRecord, share], recordIDsToDelete: nil)
modifyRecZoneOp.modifyRecordsCompletionBlock = { (records, recordID, error) in
if error != nil {
if let ckerror = error as? CKError {
if let serverVersion = ckerror.serverRecord as? CKShare {
share = serverVersion
}
completion(false)
}
}
preparationHandler(share, self.defaultContainer, error)
}
self.privateDB?.add(modifyRecZoneOp)
}
sharingController.availablePermissions = [.allowReadOnly, .allowPrivate]
sharingController.delegate = self
sharingController.popoverPresentationController?.sourceView = self.view
self.present(sharingController, animated:true, completion:nil)
} else {
let shareRecordID = rootRecord.share!.recordID
let fetchRecordsOp = CKFetchRecordsOperation(recordIDs: [shareRecordID])
fetchRecordsOp.fetchRecordsCompletionBlock = { recordsByRecordID, error in
guard error == nil, let share = recordsByRecordID?[shareRecordID] as? CKShare else {
if let ckerror = error as? CKError {
self.aErrorHandler.handleCkError(ckerror: ckerror)
//self.saveToCloudKitStatus(recordName: myRecordName, success: false)
}
completion(false)
return
}
DispatchQueue.main.async {
let sharingController = UICloudSharingController(share: share, container: self.defaultContainer!)
completion(true)
//completionHandler(sharingController)
}
}
self.privateDB?.add(fetchRecordsOp)
}
}
}
This might be a bit late but I was running into this issue too, while using NSPersistentCloudKitContainer and it seems the issue was just making sure that my iCloud container name in the Capabilities section of the settings matched my app bundle name ie iCloud.com.goddamnyouryan.MyApp

Issue with Firebase Storage & Database

I'm having an app, with UpdateProfileViewController (iOS app, written in Swift). I'm using Firebase storage and Database as backend. The problem that I'm facing is that I have 2 images that need to be uploaded to the Firebase storage, then get the download URL and enter it in the database for that particular user. Now, I'm facing 3 problems.
The downloadURL method should return a URL pointing to the uploaded image in the storage and assign it to the variable storageHeaderDownloadedURL and the other one is headerImgDownloadedURL (those should be the header and the profile images).
I want to update all of the fields despite of the fact if the user changed something or not, just update the entire user profile.
I would like to be able to compress the the images and make them smaller in terms of size, not quality, so the users don't upload very big images.
Here's my entire code, I hope someone could help me out with that because I spent 2 days and I still cannot figure those things out:
#IBAction func savePressed(_ sender: UIBarButtonItem)
{
updateUserProfile()
}
func updateUserProfile ()
{
if let userID = FIRAuth.auth()?.currentUser?.uid
{
// Note: Storage references to profile images & profile headers folder
let storageUserProfileID = Storage.storage.profile_images.child(userID)
let storageUserHeaderID = Storage.storage.profile_headers.child(userID)
guard let imageProfile = profileImage.image else { return }
guard let headerImage = headerImage.image else { return }
var storageProfileDownloadedURL: String = ""
var storageHeaderDownloadedURL: String = ""
if let newProfileImage = UIImagePNGRepresentation(imageProfile), let newHeaderImage = UIImagePNGRepresentation(headerImage)
{
storageUserProfileID.put(newProfileImage, metadata: nil, completion: { (metadata, error) in
if error != nil
{
showAlert(title: "Oops!", msg: (error?.localizedDescription)!, actionButton: "OK", viewController: self)
return
}
// Get the URL from the storage
storageUserProfileID.downloadURL(completion: { (url, error) in
if error != nil
{
showAlert(title: "Oops!!!", msg: (error?.localizedDescription)!, actionButton: "OK", viewController: self)
return
}
if let profileImgDownloadedURL = url?.absoluteString
{
storageProfileDownloadedURL = profileImgDownloadedURL
}
})
})
storageUserHeaderID.put(newHeaderImage, metadata: nil, completion: { (metadata, error) in
if error != nil
{
showAlert(title: "Oops!", msg: (error?.localizedDescription)!, actionButton: "OK", viewController: self)
return
}
// Get the URL from the storage
storageUserHeaderID.downloadURL(completion: { (url, error) in
if error != nil
{
showAlert(title: "Oops!!!", msg: (error?.localizedDescription)!, actionButton: "OK", viewController: self)
return
}
else
{
if let headerImgDownloadedURL = url?.absoluteString
{
storageHeaderDownloadedURL = headerImgDownloadedURL
}
}
})
})
//Note: Update the info for that user in Database
}
guard let newDisplayName = self.displayNameTextField.text else { return }
guard let newLocation = self.locationTextField.text else { return }
guard let newDescription = self.bioTextField.text else { return }
guard let newWebsite = self.websiteTextField.text else { return }
guard let newBirthday = self.birthdayTextField.text else { return }
let newUpdatedUserDictionary = ["imageProfile": storageProfileDownloadedURL,
"imageHeader" : storageHeaderDownloadedURL,
"description" : newDescription,
"location": newLocation,
"displayName": newDisplayName,
"website": newWebsite,
"birthday": newBirthday,
]
Database.dataService.updateUserProfile(uid: userID, user: newUpdatedUserDictionary)
print("Successfully updated!")
showAlert(title: "Profile updated", msg: "YAASS", actionButton: "OK", viewController: self)
}
}

App freezes when requesting access to addressbook

func getContacts() {
let store = CNContactStore()
if CNContactStore.authorizationStatus(for: .contacts) == .notDetermined {
store.requestAccess(for: .contacts, completionHandler: { (authorized: Bool, error: NSError?) -> Void in
if authorized {
self.retrieveContactsWithStore(store: store)
}
} as! (Bool, Error?) -> Void)
} else if CNContactStore.authorizationStatus(for: .contacts) == .authorized {
self.retrieveContactsWithStore(store: store)
}
}
func retrieveContactsWithStore(store: CNContactStore) {
do {
let groups = try store.groups(matching: nil)
let predicate = CNContact.predicateForContactsInGroup(withIdentifier: groups[0].identifier)
//let predicate = CNContact.predicateForContactsMatchingName("John")
let keysToFetch = [CNContactFormatter.descriptorForRequiredKeys(for: .fullName), CNContactEmailAddressesKey] as [Any]
let contacts = try store.unifiedContacts(matching: predicate, keysToFetch: keysToFetch as! [CNKeyDescriptor])
self.objects = contacts
DispatchQueue.main.async(execute: { () -> Void in
self.myTableView.reloadData()
})
} catch {
print(error)
}
}
I was trying to retrieve contacts from address book, but whenever I go to the view calling getContacts(), the app freezes. It wouldn't proceed anymore, but it didn't crash either. I wonder what went wrong here?
Your code for the call to requestAccess isn't correct. The syntax for the completion handler isn't valid. You need this:
func getContacts() {
let store = CNContactStore()
let status = CNContactStore.authorizationStatus(for: .contacts)
if status == .notDetermined {
store.requestAccess(for: .contacts, completionHandler: { (authorized: Bool, error: Error?) in
if authorized {
self.retrieveContactsWithStore(store: store)
}
})
} else if status == .authorized {
self.retrieveContactsWithStore(store: store)
}
}
Also note the change to use the status variable. This is cleaner and easier to read than calling authorizationStatus over and over. Call it once and then check the value over and over as needed.

Swift Firebase Facebook login dialog box pop up 2 times

I have a facebook login which using firebase to authenticate the process.
However, after I input my login detail and press confirm. It will back to the login page and pop up the facebook login page again. Then I press confirm again. It will display "User Cancel Login".
I am not sure why does it happen 2 times also when i click the confirm button it will display "User Cancel Login"
func loginButton(FbLoginBtn: FBSDKLoginButton!, didCompleteWithResult result: FBSDKLoginManagerLoginResult!, error: NSError!) {
let FbloginManager = FBSDKLoginManager()
FbloginManager.logInWithReadPermissions(["email","public_profile", "user_location", "user_hometown","user_friends"],fromViewController: self, handler: { (result, error) in
if let error = error {
print(error.localizedDescription)
return
}else if(result.isCancelled) {
print("User Cancel Login")
}else{
let credential = FIRFacebookAuthProvider.credentialWithAccessToken(FBSDKAccessToken.currentAccessToken().tokenString)
print("User\(self.user?.displayName) login successful")
AppState.instance.signedIn = true
if AppState.instance.signedIn == false{
self.firebaseLogin(credential)
//self.createFirebaseUser()
self.performSegueWithIdentifier(SEGUE_LOGIN, sender: nil)
}
}
})
}
For me this code work :
#IBAction func btnFBLoginPressed(sender: AnyObject) {
self.comeFromFB = true
let fbLoginManager : FBSDKLoginManager = FBSDKLoginManager()
var id:String = ""
var urlPhoto:String = ""
fbLoginManager.logInWithReadPermissions(["email"], fromViewController: self){ (result, error) -> Void in
if let error = error
{
print(error)
return
}
else
{
let fbloginresult : FBSDKLoginManagerLoginResult = result
if (!result.isCancelled)
{
if(fbloginresult.grantedPermissions.contains("email"))
{
if((FBSDKAccessToken.currentAccessToken()) != nil){
FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id, name, first_name, last_name, picture.type(large), email"]).startWithCompletionHandler({ (connection, result, error) -> Void in
let bUserFacebookDict = result as! NSDictionary
id = bUserFacebookDict["id"]! as! String
urlPhoto = "https://graph.facebook.com/"+id+"/picture?width=500&height=500"
let credential = FIRFacebookAuthProvider.credentialWithAccessToken(FBSDKAccessToken.currentAccessToken().tokenString)
FIRAuth.auth()?.signInWithCredential(credential) { (user, error) in
self.currentUser.setCurrentUserState(user!.uid, _firstName: bUserFacebookDict["first_name"]! as! String, _name: bUserFacebookDict["last_name"]! as! String, _urlPhoto: urlPhoto, _email:bUserFacebookDict["email"]! as! String, _connected:true)
}
})
}
}
}
}
}
}
Then I add a listener in the ViewDidAppear method with perform segue after "connected" state.

Retrieving texts Records in CloudKit with RecordID Using Swift

Purpose of the program
User need to enter the email and password in order to get logged in.
CloudKit is used to retrieve user credentials.
Hi everyone,
I need your help.
Fetching is not working. Also, this error is unresolved
Use of unresolved identifier 'errorHandler'
I have two texts that I want to fetch in my MasterViewController
The texts are:
#IBOutlet weak var userEmailAddressTextField: UITextField!
#IBOutlet weak var userPasswordTextField: UITextField!
MasterViewController Code:
Please go to fetching section
// signIn btn
#IBAction func btnSignInTapped(sender: UIButton)
{
let userEmailAddress = userEmailAddressTextField.text
let userPassword = userPasswordTextField.text
if(userEmailAddress!.isEmpty || userPassword!.isEmpty)
{
// Display an alert message
let myAlert = UIAlertController(title: "Alert", message:"All fields are required to fill in", preferredStyle: UIAlertControllerStyle.Alert);
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler:nil)
myAlert.addAction(okAction);
self.presentViewController(myAlert, animated: true, completion: nil)
return
}
//************************Fetching Section
//loading system indicator
let accountID = CKRecordID!.self
let database = CKContainer.defaultContainer().privateCloudDatabase
var query = CKQuery(recordType:"RegisteredAccounts" , predicate: NSPredicate(value: true))
var operation = CKQueryOperation(query: query)
let spinningActivity = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
spinningActivity.labelText = "SigningIn"
spinningActivity.detailsLabelText = "Please wait"
operation.recordFetchedBlock = { record in /*is this your record...*/ }
operation.queryCompletionBlock =
{ cursor, error in
self.handleCallback(error, errorHandler: {errorHandler(error: error)}, completionHandler:
{
// ready fetching records
if(userEmailAddress! == accountID || userPassword! == accountID)
{
//AlertMessage"You are loggedIn"
}
else{
userEmailAddress! != accountID || userPassword! != accountID
//AlertMessage"Your Credentials do not match"
}
})
}
operation.resultsLimit = CKQueryOperationMaximumResults;
database.addOperation(operation)
spinningActivity.hidden = true
}
Click here please for ScreenShot of the code
.......................
Changes After feedback
//loading system indicator
let database = CKContainer.defaultContainer().privateCloudDatabase
var query = CKQuery(recordType:"RegisteredAccounts" , predicate: NSPredicate(value: true))
var operation = CKQueryOperation(query: query)
//changes
//****default_Login
CKContainer.defaultContainer().fetchUserRecordIDWithCompletionHandler { (CKRecordID, error) in
if error != nil
{
if(userEmailAddress! == CKRecordID || userPassword! == CKRecordID)
{
//self.spinningIndicator("Loggedin")
self.alertMessage("LoggedIn")
}
}
else{
userEmailAddress! != CKRecordID || userPassword! != CKRecordID
//self.spinningIndicator("Credentials don't match.")
self.alertMessage("not matched")
}
}
operation.recordFetchedBlock = { record in /*is this your record...*/ }
operation.queryCompletionBlock =
{ cursor, error in
if error != nil
{
print(error)
}
else{
print(cursor)
}
}
operation.resultsLimit = CKQueryOperationMaximumResults;
database.addOperation(operation)
}
func spinningIndicator(userIndicator:String)
{
let spinningActivity = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
spinningActivity.labelText=userIndicator
spinningActivity.detailsLabelText = "Please wait"
spinningActivity.hidden = true
}
func alertMessage(userAlert: String)
{
// Display an alert message
let myAlert = UIAlertController(title: "Alert", message:userAlert, preferredStyle: UIAlertControllerStyle.Alert);
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler:nil)
myAlert.addAction(okAction);
self.presentViewController(myAlert, animated: true, completion: nil)
return
}
I am only prompted by this message self.alertMessage("not matched")
Why am I not prompted by this? self.alertMessage("LoggedIn")
The answer source for this question
is in techotopia
look for >Searching for Cloud Database Records
The answer is in performQuery method
#IBAction func performQuery(sender: AnyObject) {
let predicate = NSPredicate(format: "address = %#", addressField.text)
let query = CKQuery(recordType: "Houses", predicate: predicate)
publicDatabase?.performQuery(query, inZoneWithID: nil,
completionHandler: ({results, error in
if (error != nil) {
dispatch_async(dispatch_get_main_queue()) {
self.notifyUser("Cloud Access Error",
message: error.localizedDescription)
}
} else {
if results.count > 0 {
var record = results[0] as! CKRecord
self.currentRecord = record
dispatch_async(dispatch_get_main_queue()) {
self.commentsField.text =
record.objectForKey("comment") as! String
let photo =
record.objectForKey("photo") as! CKAsset
let image = UIImage(contentsOfFile:
photo.fileURL.path!)
self.imageView.image = image
self.photoURL = self.saveImageToFile(image!)
}
} else {
dispatch_async(dispatch_get_main_queue()) {
self.notifyUser("No Match Found",
message: "No record matching the address was found")
}
}
}
}))
}
The user will already be logged in into iCloud. You do not need an extra login.You can just get the id of the user and use that as the username.
If you do want to use this code, then you probably did not define the errorHandler function (its not in your sample code). Maybe you should first try this without the self.handleCallback. You an just remove that line (well, you should replace it for an if for the error) Did you use this structure for the .handleCallback? That structure works better if you keep your CloudKit code in a separate class and call those methods with a completion and error handler.
replace the line with the self.handleCallback with:
if error != nil {
Besides that you should also change your predicate. You are now querying all records. You want to limit it with a filter on the username.
And if you want to use the iCloud ID instead of your own login, then you could call this:
CKContainer.defaultContainer().fetchUserRecordIDWithCompletionHandler({recordID, error in