From 9739d83df8b64beab574c5bf39bd71ffc09af2bb Mon Sep 17 00:00:00 2001 From: Jett Farmer Date: Wed, 19 Oct 2016 12:35:29 -0700 Subject: [PATCH 1/5] Added `shouldAddContact` closure. --- Pods/EPContactsPicker.swift | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Pods/EPContactsPicker.swift b/Pods/EPContactsPicker.swift index d72678c..f0e7f0c 100644 --- a/Pods/EPContactsPicker.swift +++ b/Pods/EPContactsPicker.swift @@ -49,6 +49,9 @@ 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 shouldAddContact: ((CNContact) -> Bool)? + // MARK: - Lifecycle Methods override open func viewDidLoad() { @@ -186,8 +189,19 @@ open class EPContactsPicker: UITableViewController, UISearchResultsUpdating, UIS 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 shouldAddContactClosure = self.shouldAddContact { + if shouldAddContactClosure(contact) { + contactsArray.append(contact) + } + + } else { + contactsArray.append(contact) + } + //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() { From 21e106bab7447bc7bad73b96b520d0b0de1a6928 Mon Sep 17 00:00:00 2001 From: Jett Farmer Date: Wed, 19 Oct 2016 12:37:21 -0700 Subject: [PATCH 2/5] `reloadContacts()` is called when `shouldAddContact` closure is set. --- Pods/EPContactsPicker.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Pods/EPContactsPicker.swift b/Pods/EPContactsPicker.swift index f0e7f0c..33332a6 100644 --- a/Pods/EPContactsPicker.swift +++ b/Pods/EPContactsPicker.swift @@ -50,7 +50,11 @@ open class EPContactsPicker: UITableViewController, UISearchResultsUpdating, UIS var multiSelectEnabled: Bool = false //Default is single selection contact //Enables custom filtering of contacts. - public var shouldAddContact: ((CNContact) -> Bool)? + public var shouldAddContact: ((CNContact) -> Bool)? { + didSet { + self.reloadContacts() + } + } // MARK: - Lifecycle Methods From 524decffe240bd37da606614c44cd31a6bf1ea8d Mon Sep 17 00:00:00 2001 From: Anthony Miller Date: Mon, 24 Oct 2016 12:27:44 -0700 Subject: [PATCH 3/5] Fixed bug causing duplicate contacts to be loaded when using 'shouldAddContacts' closure --- Pods/EPContactsPicker.swift | 223 ++++++++++++++++++------------------ 1 file changed, 112 insertions(+), 111 deletions(-) diff --git a/Pods/EPContactsPicker.swift b/Pods/EPContactsPicker.swift index 33332a6..ba6f62e 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 @@ -61,7 +61,7 @@ open class EPContactsPicker: UITableViewController, UISearchResultsUpdating, UIS override open func viewDidLoad() { super.viewDidLoad() self.title = EPGlobalConstants.Strings.contactsTitle - + registerContactCell() inititlizeBarButtons() initializeSearchBar() @@ -111,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) } @@ -128,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 @@ -138,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: { @@ -147,8 +147,8 @@ open class EPContactsPicker: UITableViewController, UISearchResultsUpdating, UIS }) } }) - } - + } + func getContacts(_ completion: @escaping ContactsHandler) { if contactsStore == nil { //ContactStore is control for accessing the Contacts @@ -157,80 +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 shouldAddContactClosure = self.shouldAddContact, !shouldAddContactClosure(contact) { + return + } + + 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() } - else{ - self.getContacts(completion) + 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 - - //Adds the `contact` to the `contactsArray` if the closure returns true. - //If the closure doesn't exist, then the contact is added. - if let shouldAddContactClosure = self.shouldAddContact { - if shouldAddContactClosure(contact) { - contactsArray.append(contact) - } - - } else { - 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 = 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) + } } } @@ -238,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, ] } @@ -264,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 } @@ -311,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) + } + }) } } @@ -326,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)! } @@ -334,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] @@ -368,7 +369,7 @@ open class EPContactsPicker: UITableViewController, UISearchResultsUpdating, UIS let store = CNContactStore() do { filteredContacts = try store.unifiedContacts(matching: predicate, - keysToFetch: allowedContactKeys()) + keysToFetch: allowedContactKeys()) //print("\(filteredContacts.count) count") self.tableView.reloadData() From ab77922fdaf3f7ccd135718d1b768e9705af78ba Mon Sep 17 00:00:00 2001 From: Anthony Miller Date: Mon, 24 Oct 2016 12:28:49 -0700 Subject: [PATCH 4/5] Changed closure name from 'shouldAddContact' to 'shouldIncludeContact' --- Pods/EPContactsPicker.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Pods/EPContactsPicker.swift b/Pods/EPContactsPicker.swift index ba6f62e..1dd6cb4 100644 --- a/Pods/EPContactsPicker.swift +++ b/Pods/EPContactsPicker.swift @@ -50,7 +50,7 @@ open class EPContactsPicker: UITableViewController, UISearchResultsUpdating, UIS var multiSelectEnabled: Bool = false //Default is single selection contact //Enables custom filtering of contacts. - public var shouldAddContact: ((CNContact) -> Bool)? { + public var shouldIncludeContact: ((CNContact) -> Bool)? { didSet { self.reloadContacts() } @@ -198,7 +198,7 @@ open class EPContactsPicker: UITableViewController, UISearchResultsUpdating, UIS //Adds the `contact` to the `contactsArray` if the closure returns true. //If the closure doesn't exist, then the contact is added. - if let shouldAddContactClosure = self.shouldAddContact, !shouldAddContactClosure(contact) { + if let shouldIncludeContactClosure = self.shouldIncludeContact, !shouldIncludeContactClosure(contact) { return } From c07f8456ab31029938f95253c49488323af33624 Mon Sep 17 00:00:00 2001 From: Anthony Miller Date: Tue, 25 Oct 2016 12:15:31 -0700 Subject: [PATCH 5/5] Filtered contacts now respect 'shouldIncludeContact' closure --- Pods/EPContactsPicker.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Pods/EPContactsPicker.swift b/Pods/EPContactsPicker.swift index 1dd6cb4..df5102b 100644 --- a/Pods/EPContactsPicker.swift +++ b/Pods/EPContactsPicker.swift @@ -370,6 +370,10 @@ open class EPContactsPicker: UITableViewController, UISearchResultsUpdating, UIS do { filteredContacts = try store.unifiedContacts(matching: predicate, keysToFetch: allowedContactKeys()) + if let shouldIncludeContact = shouldIncludeContact { + filteredContacts = filteredContacts.filter(shouldIncludeContact) + } + //print("\(filteredContacts.count) count") self.tableView.reloadData()