diff --git a/src/ios/CDVContacts.swift b/src/ios/CDVContacts.swift index 25e5eda2..4cc612c9 100644 --- a/src/ios/CDVContacts.swift +++ b/src/ios/CDVContacts.swift @@ -57,7 +57,7 @@ class CDVNewContactsController: CNContactViewController { // NSLog(@"Contacts::onAppTerminate"); } - func checkContactPermission() { + func checkContactPermission(command: CDVInvokedUrlCommand, callback: @escaping (CDVInvokedUrlCommand)->()) { // if no permissions granted try to request them first let status = CNContactStore.authorizationStatus(for: .contacts) if status == .notDetermined { @@ -65,29 +65,32 @@ class CDVNewContactsController: CNContactViewController { if granted { print("Access granted.") } + callback(command) }) + } else { + callback(command) } } // iPhone only method to create a new contact through the GUI func newContact(_ command: CDVInvokedUrlCommand) { - checkContactPermission() - let callbackId: String = command.callbackId - let weakSelf: CDVContacts? = self - - if CNContactStore.authorizationStatus(for: .contacts) == .authorized { - let npController = CDVNewContactsController() - npController.contactStore = CNContactStore() + checkContactPermission(command: command, callback: { (command) in + let callbackId: String = command.callbackId + let weakSelf: CDVContacts? = self - npController.delegate = self - npController.callbackId = callbackId - let navController = UINavigationController(rootViewController: npController as UIViewController) - weakSelf?.viewController.present(navController, animated: true) {() -> Void in } - } + if CNContactStore.authorizationStatus(for: .contacts) == .authorized { + let npController = CDVNewContactsController() + npController.contactStore = CNContactStore() + + npController.delegate = self + npController.callbackId = callbackId + let navController = UINavigationController(rootViewController: npController as UIViewController) + weakSelf?.viewController.present(navController, animated: true) {() -> Void in } + } + }) } func existsValue(_ dict: [AnyHashable: Any], val expectedValue: String, forKey key: String) -> Bool { - checkContactPermission() let val = dict[key] var exists = false if val != nil { @@ -97,122 +100,127 @@ class CDVNewContactsController: CNContactViewController { } func displayContact(_ command: CDVInvokedUrlCommand) { - checkContactPermission() - let callbackId: String = command.callbackId - let weakSelf: CDVContacts? = self - - if CNContactStore.authorizationStatus(for: .contacts) == .authorized { - let recordID = command.argument(at: 0) as? String - var lookupError: Bool = true + checkContactPermission(command: command, callback: { (command) in + let callbackId: String = command.callbackId + let weakSelf: CDVContacts? = self - if let id = recordID { - if let rec: CNContact = try? CNContactStore().unifiedContact(withIdentifier: id, keysToFetch: CDVContacts.allContactKeys) { - let options = command.argument(at: 1, withDefault: NSNull()) as! [AnyHashable: Any] - let bEdit: Bool = (options.count > 0) ? false : existsValue(options, val: "true", forKey: "allowsEditing") - - lookupError = false - let personController = CDVDisplayContactViewController(for: rec) - personController.delegate = self - personController.allowsEditing = false - // create this so DisplayContactViewController will have a "back" button. - let parentController = UIViewController() - let navController = UINavigationController(rootViewController: parentController) - navController.pushViewController(personController, animated: true) - self.viewController.present(navController, animated: true) {() -> Void in } - if bEdit { - // create the editing controller and push it onto the stack - let editPersonController = CNContactViewController(for: rec) - editPersonController.delegate = self - editPersonController.allowsEditing = true - navController.pushViewController(editPersonController, animated: true) + if CNContactStore.authorizationStatus(for: .contacts) == .authorized { + let recordID = command.argument(at: 0) as? String + var lookupError: Bool = true + + if let id = recordID { + if let rec: CNContact = try? CNContactStore().unifiedContact(withIdentifier: id, keysToFetch: CDVContacts.allContactKeys) { + let options = command.argument(at: 1, withDefault: NSNull()) as! [AnyHashable: Any] + let bEdit: Bool = (options.count > 0) ? false : (weakSelf?.existsValue(options, val: "true", forKey: "allowsEditing"))! + + lookupError = false + let personController = CDVDisplayContactViewController(for: rec) + personController.delegate = self + personController.allowsEditing = false + // create this so DisplayContactViewController will have a "back" button. + let parentController = UIViewController() + let navController = UINavigationController(rootViewController: parentController) + navController.pushViewController(personController, animated: true) + self.viewController.present(navController, animated: true) {() -> Void in } + if bEdit { + // create the editing controller and push it onto the stack + let editPersonController = CNContactViewController(for: rec) + editPersonController.delegate = self + editPersonController.allowsEditing = true + navController.pushViewController(editPersonController, animated: true) + } } } - } - if lookupError { - // no record, return error - let result = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: CDVContactError.UNKNOWN_ERROR.rawValue) + if lookupError { + // no record, return error + let result = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: CDVContactError.UNKNOWN_ERROR.rawValue) + weakSelf?.commandDelegate.send(result, callbackId: callbackId) + } + } else { + // permission was denied or other error - return error + let result = CDVPluginResult(status: CDVCommandStatus_ERROR, messageToErrorObject: CDVContactError.UNKNOWN_ERROR.rawValue) weakSelf?.commandDelegate.send(result, callbackId: callbackId) + return } - } else { - // permission was denied or other error - return error - let result = CDVPluginResult(status: CDVCommandStatus_ERROR, messageToErrorObject: CDVContactError.UNKNOWN_ERROR.rawValue) - weakSelf?.commandDelegate.send(result, callbackId: callbackId) - return - } + }) } - func contactViewController(_ viewController: CNContactViewController, shouldPerformDefaultActionFor property: CNContactProperty) -> Bool { - checkContactPermission() - return true - } +// func contactViewController(_ viewController: CNContactViewController, shouldPerformDefaultActionFor property: CNContactProperty) -> Bool { +// checkContactPermission() +// return true +// } func chooseContact(_ command: CDVInvokedUrlCommand) { - checkContactPermission() - let callbackId: String = command.callbackId - let commandFields = command.argument(at: 0, withDefault: [Any]()) as? [Any] - let commandOptions = command.argument(at: 1, withDefault: [AnyHashable: Any]()) as? [AnyHashable: Any] - - let pickerController = CDVContactsPicker() - pickerController.delegate = self - pickerController.callbackId = callbackId - pickerController.pickedContactDictionary = [ - kW3ContactId : "" - ] - - // pickerController.predicateForSelectionOfContact = NSPredicate(value: true) - - if let fields = commandFields { - pickerController.fields = fields - for field in fields { - if let string = field as? String { - if string == "emails" { - pickerController.displayedPropertyKeys = [CNContactEmailAddressesKey]; - pickerController.predicateForEnablingContact = NSPredicate(format: "emailAddresses.@count > 0 || phoneNumbers.@count > 0") - pickerController.predicateForSelectionOfContact = NSPredicate(format: "emailAddresses.@count == 1") - pickerController.predicateForSelectionOfProperty = NSPredicate(format: "key == 'emailAddresses'") + checkContactPermission(command: command, callback: { (command) in + let weakSelf: CDVContacts? = self + let callbackId: String = command.callbackId + let commandFields = command.argument(at: 0, withDefault: [Any]()) as? [Any] + let commandOptions = command.argument(at: 1, withDefault: [AnyHashable: Any]()) as? [AnyHashable: Any] + + let pickerController = CDVContactsPicker() + pickerController.delegate = self + pickerController.callbackId = callbackId + pickerController.pickedContactDictionary = [ + kW3ContactId : "" + ] + + // pickerController.predicateForSelectionOfContact = NSPredicate(value: true) + + if let fields = commandFields { + pickerController.fields = fields + for field in fields { + if let string = field as? String { + if string == "emails" { + pickerController.displayedPropertyKeys = [CNContactEmailAddressesKey]; + pickerController.predicateForEnablingContact = NSPredicate(format: "emailAddresses.@count > 0 || phoneNumbers.@count > 0") + pickerController.predicateForSelectionOfContact = NSPredicate(format: "emailAddresses.@count == 1") + pickerController.predicateForSelectionOfProperty = NSPredicate(format: "key == 'emailAddresses'") + } } } } - } - if let options = commandOptions { - pickerController.allowsEditing = (options.count > 0) ? false : existsValue(options, val: "true", forKey: "allowsEditing") - } else { - pickerController.allowsEditing = false - } - - UISearchBar.appearance().isHidden = true - viewController.present(pickerController, animated: true) {() -> Void in } + if let options = commandOptions { + pickerController.allowsEditing = (options.count > 0) ? false : weakSelf?.existsValue(options, val: "true", forKey: "allowsEditing") + } else { + pickerController.allowsEditing = false + } + + UISearchBar.appearance().isHidden = true + weakSelf?.viewController.present(pickerController, animated: true) {() -> Void in } + }) } func pickContact(_ command: CDVInvokedUrlCommand) { - checkContactPermission() - let newCommand = CDVInvokedUrlCommand(arguments: command.arguments, callbackId: command.callbackId, className: command.className, methodName: command.methodName) - // First check for Address book permissions - let status = CNContactStore.authorizationStatus(for: .contacts) - if status == .authorized { - if let cmd = newCommand { - chooseContact(cmd) + checkContactPermission(command: command, callback: { (command) in + let weakSelf: CDVContacts? = self + let newCommand = CDVInvokedUrlCommand(arguments: command.arguments, callbackId: command.callbackId, className: command.className, methodName: command.methodName) + // First check for Address book permissions + let status = CNContactStore.authorizationStatus(for: .contacts) + if status == .authorized { + if let cmd = newCommand { + weakSelf?.chooseContact(cmd) + } + return } - return - } - let errorResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: CDVContactError.PERMISSION_DENIED_ERROR.rawValue) - // if the access is already restricted/denied the only way is to fail - if status == .restricted || status == .denied { - commandDelegate.send(errorResult, callbackId: command.callbackId) - return - } - // if no permissions granted try to request them first - if status == .notDetermined { - CNContactStore().requestAccess(for: .contacts, completionHandler: {(_ granted: Bool, _ error: Error?) -> Void in - if granted { - if let cmd = newCommand { - self.chooseContact(cmd) + let errorResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: CDVContactError.PERMISSION_DENIED_ERROR.rawValue) + // if the access is already restricted/denied the only way is to fail + if status == .restricted || status == .denied { + weakSelf?.commandDelegate.send(errorResult, callbackId: command.callbackId) + return + } + // if no permissions granted try to request them first + if status == .notDetermined { + CNContactStore().requestAccess(for: .contacts, completionHandler: {(_ granted: Bool, _ error: Error?) -> Void in + if granted { + if let cmd = newCommand { + weakSelf?.chooseContact(cmd) + } + return } - return - } - self.commandDelegate.send(errorResult, callbackId: command.callbackId) - }) - } + weakSelf?.commandDelegate.send(errorResult, callbackId: command.callbackId) + }) + } + }) } func contactPickerDidCancel(_ picker: CNContactPickerViewController) { @@ -322,234 +330,236 @@ class CDVNewContactsController: CNContactViewController { } @objc func search(_ command: CDVInvokedUrlCommand) { - checkContactPermission() - let callbackId: String = command.callbackId - let commandFields = command.argument(at: 0) as? [Any] - let findOptions = command.argument(at: 1, withDefault: [AnyHashable: Any]()) as? [AnyHashable: Any] - if let fields = commandFields { - commandDelegate.run(inBackground: {() -> Void in + checkContactPermission(command: command, callback: { (command) in + let callbackId: String = command.callbackId + let commandFields = command.argument(at: 0) as? [Any] + let findOptions = command.argument(at: 1, withDefault: [AnyHashable: Any]()) as? [AnyHashable: Any] + if let fields = commandFields { let weakSelf: CDVContacts? = self - if CNContactStore.authorizationStatus(for: .contacts) == .authorized { - // get the findOptions values - var multiple = false - // default is false - var filter: String = "" - var desiredFields: [Any] = ["*"] - if let opts = findOptions { - if (opts.count > 0) { - var value: Any? = nil - let filterValue = opts["filter"] - if let f = filterValue as? String { - filter = f - } - value = opts["multiple"] - if (value is NSNumber) { - // multiple is a boolean that will come through as an NSNumber - multiple = (value as? NSNumber) != 0 - // NSLog(@"multiple is: %d", multiple); - } - if let dFields = opts["desiredFields"] as? [Any] { - // return all fields if desired fields are not explicitly defined - if dFields.count > 0 { - desiredFields = dFields + weakSelf?.commandDelegate.run(inBackground: {() -> Void in + if CNContactStore.authorizationStatus(for: .contacts) == .authorized { + // get the findOptions values + var multiple = false + // default is false + var filter: String = "" + var desiredFields: [Any] = ["*"] + if let opts = findOptions { + if (opts.count > 0) { + var value: Any? = nil + let filterValue = opts["filter"] + if let f = filterValue as? String { + filter = f + } + value = opts["multiple"] + if (value is NSNumber) { + // multiple is a boolean that will come through as an NSNumber + multiple = (value as? NSNumber) != 0 + // NSLog(@"multiple is: %d", multiple); + } + if let dFields = opts["desiredFields"] as? [Any] { + // return all fields if desired fields are not explicitly defined + if dFields.count > 0 { + desiredFields = dFields + } } } - } - } - let searchFields = CDVContact.self.calcReturnFields(fields) - let returnFields = CDVContact.self.calcReturnFields(desiredFields) - var matches: [CDVContact] = [CDVContact]() - if (filter == "") { - // get all records - let fetchRequest = CNContactFetchRequest.init(keysToFetch: CDVContacts.allContactKeys) - fetchRequest.predicate = nil - try? CNContactStore().enumerateContacts(with: fetchRequest, usingBlock: {(contact: CNContact, stop: UnsafeMutablePointer) -> Void in - // create Contacts and put into matches array - // doesn't make sense to ask for all records when multiple == NO but better check - if !multiple { - if matches.count == 1 { - return + let searchFields = CDVContact.self.calcReturnFields(fields) + let returnFields = CDVContact.self.calcReturnFields(desiredFields) + var matches: [CDVContact] = [CDVContact]() + if (filter == "") { + // get all records + let fetchRequest = CNContactFetchRequest.init(keysToFetch: CDVContacts.allContactKeys) + fetchRequest.predicate = nil + try? CNContactStore().enumerateContacts(with: fetchRequest, usingBlock: {(contact: CNContact, stop: UnsafeMutablePointer) -> Void in + // create Contacts and put into matches array + // doesn't make sense to ask for all records when multiple == NO but better check + if !multiple { + if matches.count == 1 { + return + } } - } - matches.append(CDVContact(fromCNContact: contact)) - }) - } else { - let fetchRequest = CNContactFetchRequest.init(keysToFetch: CDVContacts.allContactKeys) - fetchRequest.predicate = nil - try? CNContactStore().enumerateContacts(with: fetchRequest, usingBlock: {(contact: CNContact, stop: UnsafeMutablePointer) -> Void in - let testContact = CDVContact(fromCNContact: contact) - let match = testContact.foundValue(filter, inFields: searchFields) - if match { - matches.append(testContact) - } - }) - } - var returnContacts = [[AnyHashable: Any]]() - if (matches.count > 0) { - // convert to JS Contacts format and return in callback - // - returnFields determines what properties to return - autoreleasepool { - let count = multiple == true ? Int(matches.count) : 1 - for i in 0..) -> Void in + let testContact = CDVContact(fromCNContact: contact) + let match = testContact.foundValue(filter, inFields: searchFields) + if match { + matches.append(testContact) + } + }) + } + var returnContacts = [[AnyHashable: Any]]() + if (matches.count > 0) { + // convert to JS Contacts format and return in callback + // - returnFields determines what properties to return + autoreleasepool { + let count = multiple == true ? Int(matches.count) : 1 + for i in 0.. Void in - let weakSelf: CDVContacts? = self - var result: CDVPluginResult? = nil - - if CNContactStore.authorizationStatus(for: .contacts) == .authorized { - var bIsError = false - var bSuccess = false - var bUpdate = false - var errCode: CDVContactError = CDVContactError.UNKNOWN_ERROR - if let cId = contactDict[kW3ContactId] as? String { - var aContact: CDVContact? = nil - if let rec = try? CNContactStore().unifiedContact(withIdentifier: cId, keysToFetch: CDVContacts.allContactKeys) { - aContact = CDVContact(fromCNContact: rec) - bUpdate = true - } - if aContact == nil { - aContact = CDVContact() - } - bSuccess = aContact?.setFromContactDict(contactDict, asUpdate: bUpdate) ?? false - if bSuccess { - if !bUpdate { - if let record = aContact?.mutableRecord { - let saveRequest = CNSaveRequest() - saveRequest.add(record, toContainerWithIdentifier: nil) - do { - try CNContactStore().execute(saveRequest) - bSuccess = true - } catch { + checkContactPermission(command: command, callback: { (command) in + let callbackId: String = command.callbackId + let commandContactDict = command.argument(at: 0) as? [AnyHashable: Any] + + let weakSelf: CDVContacts? = self + if let contactDict = commandContactDict { + weakSelf?.commandDelegate.run(inBackground: {() -> Void in + var result: CDVPluginResult? = nil + + if CNContactStore.authorizationStatus(for: .contacts) == .authorized { + var bIsError = false + var bSuccess = false + var bUpdate = false + var errCode: CDVContactError = CDVContactError.UNKNOWN_ERROR + if let cId = contactDict[kW3ContactId] as? String { + var aContact: CDVContact? = nil + if let rec = try? CNContactStore().unifiedContact(withIdentifier: cId, keysToFetch: CDVContacts.allContactKeys) { + aContact = CDVContact(fromCNContact: rec) + bUpdate = true + } + if aContact == nil { + aContact = CDVContact() + } + bSuccess = aContact?.setFromContactDict(contactDict, asUpdate: bUpdate) ?? false + if bSuccess { + if !bUpdate { + if let record = aContact?.mutableRecord { + let saveRequest = CNSaveRequest() + saveRequest.add(record, toContainerWithIdentifier: nil) + do { + try CNContactStore().execute(saveRequest) + bSuccess = true + } catch { + bSuccess = false + } + } else { bSuccess = false } - } else { - bSuccess = false } - } - if bSuccess && bUpdate { - if let record = aContact?.mutableRecord { - let saveRequest = CNSaveRequest() - saveRequest.update(record) - do { - try CNContactStore().execute(saveRequest) - bSuccess = true - } catch { + if bSuccess && bUpdate { + if let record = aContact?.mutableRecord { + let saveRequest = CNSaveRequest() + saveRequest.update(record) + do { + try CNContactStore().execute(saveRequest) + bSuccess = true + } catch { + bSuccess = false + } + } else { bSuccess = false } - } else { - bSuccess = false } - } - if !bSuccess { - // need to provide error codes + if !bSuccess { + // need to provide error codes + bIsError = true + errCode = CDVContactError.IO_ERROR + } + else { + // give original dictionary back? If generate dictionary from saved contact, have no returnFields specified + // so would give back all fields (which W3C spec. indicates is not desired) + // for now (while testing) give back saved, full contact + let newContact = aContact?.toDictionary(CDVContact.defaultFields()) + // NSString* contactStr = [newContact JSONRepresentation]; + result = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: newContact) + } + } else { bIsError = true errCode = CDVContactError.IO_ERROR } - else { - // give original dictionary back? If generate dictionary from saved contact, have no returnFields specified - // so would give back all fields (which W3C spec. indicates is not desired) - // for now (while testing) give back saved, full contact - let newContact = aContact?.toDictionary(CDVContact.defaultFields()) - // NSString* contactStr = [newContact JSONRepresentation]; - result = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: newContact) + if bIsError { + result = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: (errCode).rawValue) + } + if result != nil { + weakSelf?.commandDelegate.send(result, callbackId: callbackId) } - } else { - bIsError = true - errCode = CDVContactError.IO_ERROR - } - if bIsError { - result = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: (errCode).rawValue) - } - if result != nil { - weakSelf?.commandDelegate.send(result, callbackId: callbackId) } + } else { + // permission was denied or other error - return error + result = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: CDVContactError.UNKNOWN_ERROR.rawValue) + weakSelf?.commandDelegate.send(result, callbackId: callbackId) + return } - } else { - // permission was denied or other error - return error - result = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: CDVContactError.UNKNOWN_ERROR.rawValue) - weakSelf?.commandDelegate.send(result, callbackId: callbackId) - return - } - }) // end of queue - } + }) // end of queue + } + }) } func remove(_ command: CDVInvokedUrlCommand) { - checkContactPermission() - let callbackId: String = command.callbackId - let commandcId = command.argument(at: 0) as? String - let weakSelf: CDVContacts? = self - var result: CDVPluginResult? = nil - - if CNContactStore.authorizationStatus(for: .contacts) == .authorized { - var bIsError = false - var errCode: CDVContactError = CDVContactError.UNKNOWN_ERROR - if let cId = commandcId { //TODO: Check for invalid record id - if let rec = try? CNContactStore().unifiedContact(withIdentifier: cId, keysToFetch: CDVContacts.allContactKeys).mutableCopy() as! CNMutableContact { - let saveRequest = CNSaveRequest() - saveRequest.delete(rec) - do { - try CNContactStore().execute(saveRequest) - // set id to null - // [contactDict setObject:[NSNull null] forKey:kW3ContactId]; - // result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAs: contactDict]; - result = CDVPluginResult(status: CDVCommandStatus_OK) - // NSString* contactStr = [contactDict JSONRepresentation]; - } catch { + checkContactPermission(command: command, callback: { (command) in + let callbackId: String = command.callbackId + let commandcId = command.argument(at: 0) as? String + let weakSelf: CDVContacts? = self + var result: CDVPluginResult? = nil + + if CNContactStore.authorizationStatus(for: .contacts) == .authorized { + var bIsError = false + var errCode: CDVContactError = CDVContactError.UNKNOWN_ERROR + if let cId = commandcId { //TODO: Check for invalid record id + if let rec = try? CNContactStore().unifiedContact(withIdentifier: cId, keysToFetch: CDVContacts.allContactKeys).mutableCopy() as! CNMutableContact { + let saveRequest = CNSaveRequest() + saveRequest.delete(rec) + do { + try CNContactStore().execute(saveRequest) + // set id to null + // [contactDict setObject:[NSNull null] forKey:kW3ContactId]; + // result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAs: contactDict]; + result = CDVPluginResult(status: CDVCommandStatus_OK) + // NSString* contactStr = [contactDict JSONRepresentation]; + } catch { + bIsError = true + errCode = CDVContactError.IO_ERROR + } + } else { + // no record found return error bIsError = true - errCode = CDVContactError.IO_ERROR + errCode = CDVContactError.UNKNOWN_ERROR } } else { - // no record found return error + // invalid contact id provided bIsError = true - errCode = CDVContactError.UNKNOWN_ERROR + errCode = CDVContactError.INVALID_ARGUMENT_ERROR + } + if bIsError { + result = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: errCode.rawValue) + } + if result != nil { + weakSelf?.commandDelegate.send(result, callbackId: callbackId) } } else { - // invalid contact id provided - bIsError = true - errCode = CDVContactError.INVALID_ARGUMENT_ERROR - } - if bIsError { - result = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: errCode.rawValue) - } - if result != nil { + // permission was denied or other error - return error + result = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: CDVContactError.UNKNOWN_ERROR.rawValue) weakSelf?.commandDelegate.send(result, callbackId: callbackId) + return } - } else { - // permission was denied or other error - return error - result = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: CDVContactError.UNKNOWN_ERROR.rawValue) - weakSelf?.commandDelegate.send(result, callbackId: callbackId) return - } - return + }) } }