App freezes when requesting access to addressbook - swift

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.

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

Why does the CKFetchRecordsOperation not work?

This operation does not retrieve anything. The CkFetchRecordsCOmpletion block does not get called? Thanks for your time!
public func getRecord(recordID: CKRecord.ID, completion: #escaping (CKRecord?, CKError?) -> Void) {
let operation = CKFetchRecordsOperation(recordIDs: [recordID])
operation.fetchRecordsCompletionBlock = { (records, error) in
// Checking for potential errors
if let error = error {
completion(nil, error as? CKError)
print(error)
}
if let record = records?[recordID] {
completion(record, nil)
}
}
operation.qualityOfService = .utility
let privateDatabase = CKContainer(identifier: "something").privateCloudDatabase
privateDatabase.add(operation)
}

Update two fields at once with updateData

I am changing my online status with this code:
static func online(for uid: String, status: Bool, success: #escaping (Bool) -> Void) {
//True == Online, False == Offline
let db = Firestore.firestore()
let lastTime = Date().timeIntervalSince1970
let onlineStatus = ["onlineStatus" : status]
let lastTimeOnline = ["lastTimeOnline" : lastTime]
let ref = db.collection("users").document(uid)
ref.updateData(lastTimeOnline) {(error) in
if let error = error {
assertionFailure(error.localizedDescription)
success(false)
}
success(true)
}
ref.updateData(onlineStatus) {(error) in
if let error = error {
assertionFailure(error.localizedDescription)
success(false)
}
success(true)
}
}
I update the lastTimeOnline and the onlineStatus.
I listen to this updates via:
// Get the user online offline status
func getUserOnlineStatus(completion: #escaping (Dictionary<String, Any>) -> Void) {
let db = Firestore.firestore()
db.collection("users").addSnapshotListener { (querySnapshot, error) in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .modified) {
//GETS CALLED TWICE BUT I ONLY WANT ONCE
print("modified called..")
guard let onlineStatus = diff.document.get("onlineStatus") as? Bool else {return}
guard let userId = diff.document.get("uid") as? String else {return}
var userIsOnline = Dictionary<String, Any>()
userIsOnline[userId] = [onlineStatus, "huhu"]
completion(userIsOnline)
}
}
}
}
The problem is now, since I use ref.updateData twice, my SnapshotListener .modified returns the desired data twice.
How can I update two fields in a single call, so my .modified just return one snapshot?
You can try to combine them
let all:[String:Any] = ["onlineStatus" : status ,"lastTimeOnline" : lastTime]
let ref = db.collection("users").document(uid)
ref.updateData(all) {(error) in
if let error = error {
assertionFailure(error.localizedDescription)
success(false)
}
success(true)
}

Async issue in call using ObjectiveDropboxOfficial SDK

I'm experiencing an issue with a function that should return an encrypted file from dropbox, but is instead returning the empty dictionary I initialized to receive the data.
I'm almost certain it's a race condition issue since an async call has to be made to the Dropbox API, but so far I have been unable to resolve the issue using GCD. Any help would be most appreciated:
func loadDropboxAccounts() {
let retreiveDataGroup = dispatch_group_create()
var dictionary = [String:String]()
dispatch_group_enter(retreiveDataGroup)
if DropboxClientsManager.authorizedClient() == nil {
DropboxClientsManager.authorizeFromController(UIApplication.sharedApplication(), controller: self, openURL: {(url: NSURL) -> Void in
UIApplication.sharedApplication().openURL(url)
}, browserAuth: true)
}
if let client = DropboxClientsManager.authorizedClient() {
client.filesRoutes.downloadData("/example/example.txt").response({(result: AnyObject?, routeError: AnyObject?, error: DBError?, fileContents: NSData) -> Void in
if (fileContents.length != 0) {
let cipherTextData = fileContents
let plainTextByteArray = CryptoHelper.accountDecrypt(cipherTextData, fileName: "vault")
let plainTextString = plainTextByteArray.reduce("") { $0 + String(UnicodeScalar($1)) }
let plainTextData = dataFromByteArray(plainTextByteArray)
do {
try dictionary = NSJSONSerialization.JSONObjectWithData(plainTextData, options: []) as! [String:String]
for (key, value) in dictionary {
let t = dictionary[key]
print(t)
}
} catch let error as NSError{
print("loadAccountInfo:", error)
}
} else {
print("\(routeError)\n\(error)\n")
}
}).progress({(bytesDownloaded: Int64, totalBytesDownloaded: Int64, totalBytesExpectedToDownload: Int64) -> Void in
print("\(bytesDownloaded)\n\(totalBytesDownloaded)\n\(totalBytesExpectedToDownload)\n")
})
}
dispatch_group_notify(retreiveDataGroup, dispatch_get_main_queue()) {
return dictionary
}
}
Just for reference, this the pod that I am using in the project:
https://github.com/dropbox/dropbox-sdk-obj-c

Facebook login using RxSwift

I'm trying to implement the following RxSwift example:
Login in with facebook in my application -> retrieve the user information -> retrieve user's profile photo.
I have these three functions and they must be executed in this order: requestAccess() -> fetchUserInformation() -> fetchUserPhoto()
func requestAccess() -> Observable<(ACAccount)> {
return create { observer -> Disposable in
let accountStore = ACAccountStore()
let accountType = accountStore.accountTypeWithAccountTypeIdentifier(ACAccountTypeIdentifierFacebook)
let dictionary: [NSObject : AnyObject] = [ACFacebookAppIdKey:"***APPID***", ACFacebookPermissionsKey:["public_profile", "email", "user_friends"]]
accountStore.requestAccessToAccountsWithType(accountType, options: dictionary) { granted, error in
if granted == false || error != nil {
sendError(observer, error ?? UnknownError)
} else {
let accounts = accountStore.accountsWithAccountType(accountType)
let account = accounts.last as! ACAccount
sendNext(observer, account)
sendCompleted(observer)
}
}
return AnonymousDisposable({})
}
}
func fetchUserInformation(account: ACAccount) -> Observable<User> {
return create { observer -> Disposable in
let url = NSURL(string: "https://graph.facebook.com/me")
let request = SLRequest(forServiceType: SLServiceTypeFacebook, requestMethod: .GET, URL: url, parameters: nil)
request.account = account
request.performRequestWithHandler { (data, response, error) -> Void in
if data == nil || response == nil {
sendError(observer, error ?? UnknownError)
} else {
let result: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil)
let user = User()
user.updateInformationWithJSON(result! as! JSONObject)
sendNext(observer, user)
sendCompleted(observer)
}
}
return AnonymousDisposable({})
}
}
func fetchUserPhoto(user: User) -> Observable<AnyObject> {
return create { observer -> Disposable in
let url = NSURL(string: "https://graph.facebook.com/***myid***/picture")
let params = ["redirect":"false", "height":"200", "width":"200"]
let request = SLRequest(forServiceType: SLServiceTypeFacebook, requestMethod: .GET, URL: url, parameters: params)
request.account = SocialController.account
request.performRequestWithHandler { (data, response, error) -> Void in
if data == nil || response == nil {
sendError(observer, error ?? UnknownError)
} else {
let result: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil)
sendNext(observer, result!)
sendCompleted(observer)
}
}
return AnonymousDisposable({})
}
}
I already tried to implement this flow but it doesn't feel right. What is the best way to solve this problem?
requestAccess()
>- subscribeNext { account in
fetchUserInformation(account)
>- map { user in return UserViewModel(model: user) }
>- subscribeNext { viewModel in self.viewModel = viewModel }
}
Have you tried using flatMap?
It's an equivalent to then in the JavaScript bluebird or Q world. The difference between map and flatMap is that flatMap must return an Observable<T> which will then be unwrapped in the following block/closure.
requestAccess()
>- flatMap{ account in
return fetchUserInformation(account)
}
>- map { user in
return UserViewModel(model:user)
}
>- subscribeNext { viewModel in
self.viewModel = viewModel
}
Tidbit #1: Consider using unowned self when referencing self to avoid a retain cycle.
Tidbit #2: These two are pretty much the same thing.
flatMap { return just("hello") }
>- subscribeNext{ greeting in println(greeting) }
map { return "hello" }
>- subscribeNext{ greeting in println(greeting) }
private
func requestFacebookAccess(_ viewController: UIViewController) -> Observable<LoginManagerLoginResult?> {
return Observable.create { observer -> Disposable in
let loginManager = LoginManager()
loginManager.logIn(permissions: ["public_profile", "email"], from: viewController, handler: { result, error in
observer.onNext(result)
observer.onCompleted()
})
return Disposables.create()
}
}