diff --git a/Pods/EPContactsPicker.swift b/Pods/EPContactsPicker.swift index d72678c..df5102b 100644 --- a/Pods/EPContactsPicker.swift +++ b/Pods/EPContactsPicker.swift @@ -11,17 +11,17 @@ import Contacts public protocol EPPickerDelegate { - func epContactPicker(_: EPContactsPicker, didContactFetchFailed error: NSError) + func epContactPicker(_: EPContactsPicker, didContactFetchFailed error: NSError) func epContactPicker(_: EPContactsPicker, didCancel error: NSError) func epContactPicker(_: EPContactsPicker, didSelectContact contact: EPContact) - func epContactPicker(_: EPContactsPicker, didSelectMultipleContacts contacts: [EPContact]) + func epContactPicker(_: EPContactsPicker, didSelectMultipleContacts contacts: [EPContact]) } public extension EPPickerDelegate { - func epContactPicker(_: EPContactsPicker, didContactFetchFailed error: NSError) { } - func epContactPicker(_: EPContactsPicker, didCancel error: NSError) { } - func epContactPicker(_: EPContactsPicker, didSelectContact contact: EPContact) { } - func epContactPicker(_: EPContactsPicker, didSelectMultipleContacts contacts: [EPContact]) { } + func epContactPicker(_: EPContactsPicker, didContactFetchFailed error: NSError) { } + func epContactPicker(_: EPContactsPicker, didCancel error: NSError) { } + func epContactPicker(_: EPContactsPicker, didSelectContact contact: EPContact) { } + func epContactPicker(_: EPContactsPicker, didSelectMultipleContacts contacts: [EPContact]) { } } typealias ContactsHandler = (_ contacts : [CNContact] , _ error : NSError?) -> Void @@ -49,12 +49,19 @@ open class EPContactsPicker: UITableViewController, UISearchResultsUpdating, UIS var subtitleCellValue = SubtitleCellValue.phoneNumber var multiSelectEnabled: Bool = false //Default is single selection contact + //Enables custom filtering of contacts. + public var shouldIncludeContact: ((CNContact) -> Bool)? { + didSet { + self.reloadContacts() + } + } + // MARK: - Lifecycle Methods override open func viewDidLoad() { super.viewDidLoad() self.title = EPGlobalConstants.Strings.contactsTitle - + registerContactCell() inititlizeBarButtons() initializeSearchBar() @@ -104,14 +111,14 @@ open class EPContactsPicker: UITableViewController, UISearchResultsUpdating, UIS tableView.register(cellNib, forCellReuseIdentifier: "Cell") } } - + override open func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } - + // MARK: - Initializers - + convenience public init(delegate: EPPickerDelegate?) { self.init(delegate: delegate, multiSelection: false) } @@ -121,7 +128,7 @@ open class EPContactsPicker: UITableViewController, UISearchResultsUpdating, UIS self.multiSelectEnabled = multiSelection contactDelegate = delegate } - + convenience public init(delegate: EPPickerDelegate?, multiSelection : Bool, subtitleCellType: SubtitleCellValue) { self.init(style: .plain) self.multiSelectEnabled = multiSelection @@ -131,8 +138,8 @@ open class EPContactsPicker: UITableViewController, UISearchResultsUpdating, UIS // MARK: - Contact Operations - - open func reloadContacts() { + + open func reloadContacts() { getContacts( {(contacts, error) in if (error == nil) { DispatchQueue.main.async(execute: { @@ -140,8 +147,8 @@ open class EPContactsPicker: UITableViewController, UISearchResultsUpdating, UIS }) } }) - } - + } + func getContacts(_ completion: @escaping ContactsHandler) { if contactsStore == nil { //ContactStore is control for accessing the Contacts @@ -150,69 +157,81 @@ open class EPContactsPicker: UITableViewController, UISearchResultsUpdating, UIS let error = NSError(domain: "EPContactPickerErrorDomain", 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 - self.contactDelegate?.epContactPicker(self, didContactFetchFailed: error) - completion([], error) - self.dismiss(animated: true, completion: nil) - }) - alert.addAction(okAction) - self.present(alert, animated: true, completion: nil) + 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 + self.contactDelegate?.epContactPicker(self, didContactFetchFailed: error) + completion([], error) + self.dismiss(animated: true, completion: nil) + }) + 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]() + + var orderedContacts = [String : [CNContact]]() - 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?) - }) + let contactFetchRequest = CNContactFetchRequest(keysToFetch: allowedContactKeys()) + + do { + try contactsStore?.enumerateContacts(with: contactFetchRequest, usingBlock: { (contact, stop) -> Void in + + //Adds the `contact` to the `contactsArray` if the closure returns true. + //If the closure doesn't exist, then the contact is added. + if let shouldIncludeContactClosure = self.shouldIncludeContact, !shouldIncludeContactClosure(contact) { + return } - else{ - self.getContacts(completion) + + contactsArray.append(contact) + + //Ordering contacts based on alphabets in firstname + 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 = orderedContacts[key] { + contacts = segregatedContact + } + contacts.append(contact) + orderedContacts[key] = contacts + }) - - 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 - } - 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) + self.orderedContacts = orderedContacts + 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) - } + catch let error as NSError { + print(error.localizedDescription) + } } } @@ -220,15 +239,15 @@ open class EPContactsPicker: UITableViewController, UISearchResultsUpdating, UIS 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, - CNContactOrganizationNameKey as CNKeyDescriptor, - CNContactBirthdayKey as CNKeyDescriptor, - CNContactImageDataKey as CNKeyDescriptor, - CNContactThumbnailImageDataKey as CNKeyDescriptor, - CNContactImageDataAvailableKey as CNKeyDescriptor, - CNContactPhoneNumbersKey as CNKeyDescriptor, - CNContactEmailAddressesKey as CNKeyDescriptor, + CNContactGivenNameKey as CNKeyDescriptor, + CNContactFamilyNameKey as CNKeyDescriptor, + CNContactOrganizationNameKey as CNKeyDescriptor, + CNContactBirthdayKey as CNKeyDescriptor, + CNContactImageDataKey as CNKeyDescriptor, + CNContactThumbnailImageDataKey as CNKeyDescriptor, + CNContactImageDataAvailableKey as CNKeyDescriptor, + CNContactPhoneNumbersKey as CNKeyDescriptor, + CNContactEmailAddressesKey as CNKeyDescriptor, ] } @@ -246,30 +265,30 @@ open class EPContactsPicker: UITableViewController, UISearchResultsUpdating, UIS } return 0 } - + // MARK: - Table View Delegates - + override open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! EPContactCell cell.accessoryType = UITableViewCellAccessoryType.none //Convert CNContact to EPContact - let contact: EPContact + let contact: EPContact if resultSearchController.isActive { contact = EPContact(contact: filteredContacts[(indexPath as NSIndexPath).row]) } else { - guard let contactsForSection = orderedContacts[sortedContactKeys[(indexPath as NSIndexPath).section]] else { - assertionFailure() - return UITableViewCell() - } - - contact = EPContact(contact: contactsForSection[(indexPath as NSIndexPath).row]) + guard let contactsForSection = orderedContacts[sortedContactKeys[(indexPath as NSIndexPath).section]] else { + assertionFailure() + return UITableViewCell() + } + + contact = EPContact(contact: contactsForSection[(indexPath as NSIndexPath).row]) } - + if multiSelectEnabled && selectedContacts.contains(where: { $0.contactId == contact.contactId }) { cell.accessoryType = UITableViewCellAccessoryType.checkmark } - + cell.updateContactsinUI(contact, indexPath: indexPath, subtitleType: subtitleCellValue) return cell } @@ -293,12 +312,12 @@ open class EPContactsPicker: UITableViewController, UISearchResultsUpdating, UIS } else { //Single selection code - resultSearchController.isActive = false - self.dismiss(animated: true, completion: { - DispatchQueue.main.async { - self.contactDelegate?.epContactPicker(self, didSelectContact: selectedContact) - } - }) + resultSearchController.isActive = false + self.dismiss(animated: true, completion: { + DispatchQueue.main.async { + self.contactDelegate?.epContactPicker(self, didSelectContact: selectedContact) + } + }) } } @@ -308,7 +327,7 @@ open class EPContactsPicker: UITableViewController, UISearchResultsUpdating, UIS override open func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int { if resultSearchController.isActive { return 0 } - tableView.scrollToRow(at: IndexPath(row: 0, section: index), at: UITableViewScrollPosition.top , animated: false) + tableView.scrollToRow(at: IndexPath(row: 0, section: index), at: UITableViewScrollPosition.top , animated: false) return sortedContactKeys.index(of: title)! } @@ -316,7 +335,7 @@ open class EPContactsPicker: UITableViewController, UISearchResultsUpdating, UIS if resultSearchController.isActive { return nil } return sortedContactKeys } - + override open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { if resultSearchController.isActive { return nil } return sortedContactKeys[section] @@ -350,7 +369,11 @@ open class EPContactsPicker: UITableViewController, UISearchResultsUpdating, UIS let store = CNContactStore() do { filteredContacts = try store.unifiedContacts(matching: predicate, - keysToFetch: allowedContactKeys()) + keysToFetch: allowedContactKeys()) + if let shouldIncludeContact = shouldIncludeContact { + filteredContacts = filteredContacts.filter(shouldIncludeContact) + } + //print("\(filteredContacts.count) count") self.tableView.reloadData()