From 089437667aa477a88897648c75441f68efde5780 Mon Sep 17 00:00:00 2001 From: Miroslav Kutak Date: Sat, 13 Jan 2024 01:08:15 +0100 Subject: [PATCH 01/11] Moved the contacts fetching to a background thread. --- .../KNContactsPicker/KNContactsPicker.swift | 74 +++++++++++-------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/Sources/KNContactsPicker/KNContactsPicker.swift b/Sources/KNContactsPicker/KNContactsPicker.swift index 62957ca..284432e 100644 --- a/Sources/KNContactsPicker/KNContactsPicker.swift +++ b/Sources/KNContactsPicker/KNContactsPicker.swift @@ -11,11 +11,11 @@ import UIKit import Contacts open class KNContactsPicker: UINavigationController { - + var settings: KNPickerSettings = KNPickerSettings() weak var contactPickingDelegate: KNContactPickingDelegate! private var contacts: [CNContact] = [] - + var tableViewStyle: UITableView.Style { get { if #available(iOS 13.0, *) { @@ -24,65 +24,75 @@ open class KNContactsPicker: UINavigationController { return UITableView.Style.grouped } } - + private var sortingOutcome: KNSortingOutcome? - + override open func viewDidLoad() { super.viewDidLoad() self.fetchContacts() - + self.navigationBar.prefersLargeTitles = true self.navigationItem.largeTitleDisplayMode = .always - - let contactPickerController = self.getContactsPicker() - - self.presentationController?.delegate = contactPickerController - self.viewControllers.append(contactPickerController) } - + convenience public init(delegate: KNContactPickingDelegate?, settings: KNPickerSettings) { self.init() self.contactPickingDelegate = delegate self.settings = settings } - + func getContactsPicker() -> KNContactsPickerController { let controller = KNContactsPickerController(style: tableViewStyle) - + controller.settings = settings controller.delegate = contactPickingDelegate controller.presentationDelegate = self controller.contacts = sortingOutcome?.sortedContacts ?? [] controller.sortedContacts = sortingOutcome?.contactsSortedInSections ?? [:] controller.sections = sortingOutcome?.sections ?? [] - + return controller } - + func fetchContacts() { - + switch settings.pickerContactsSource { case .userProvided: self.sortingOutcome = KNContactUtils.sortContactsIntoSections(contacts: settings.pickerContactsList, sortingType: settings.displayContactsSortedBy) case .default: - requestAndSortContacts() - } - - } - - private func requestAndSortContacts() { - switch KNContactsAuthorisation.requestAccess(conditionToEnableContact: settings.conditionToDisplayContact) { - case .success(let resultContacts): - self.sortingOutcome = KNContactUtils.sortContactsIntoSections(contacts: resultContacts, sortingType: settings.displayContactsSortedBy) - - case .failure(let failureReason): - if failureReason != .pendingAuthorisation { - self.dismiss(animated: true, completion: { - self.contactPickingDelegate?.contactPicker(didFailPicking: failureReason) - }) + requestAndSortContacts { [weak self] result in + guard let self = self else { return } + let contactPickerController = self.getContactsPicker() + + self.presentationController?.delegate = contactPickerController + self.viewControllers.append(contactPickerController) } } + + } + + private func requestAndSortContacts(completion: @escaping (Result<[CNContact], KNContactFetchingError>) -> Void) { + let conditionToDisplayContact = self.settings.conditionToDisplayContact + DispatchQueue.global().async { + let result = KNContactsAuthorisation.requestAccess(conditionToEnableContact: conditionToDisplayContact) + + DispatchQueue.main.async { + switch result { + case .success(let resultContacts): + self.sortingOutcome = KNContactUtils.sortContactsIntoSections(contacts: resultContacts, sortingType: self.settings.displayContactsSortedBy) + + + case .failure(let failureReason): + if failureReason != .pendingAuthorisation { + self.dismiss(animated: true, completion: { + self.contactPickingDelegate?.contactPicker(didFailPicking: failureReason) + }) + } + } + completion(result) + }//: DispatchQueue.main.async + } //: DispatchQueue.global().async } - + } #endif From 7e1066299cbcd8424b96852ab4749ae6dccc1832 Mon Sep 17 00:00:00 2001 From: Miroslav Kutak Date: Sat, 13 Jan 2024 01:37:01 +0100 Subject: [PATCH 02/11] Fixed the select all button issue (was showing clear button instead). --- Sources/KNContactsPicker/KNContactsPickerController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/KNContactsPicker/KNContactsPickerController.swift b/Sources/KNContactsPicker/KNContactsPickerController.swift index 742ebcd..03c291f 100644 --- a/Sources/KNContactsPicker/KNContactsPickerController.swift +++ b/Sources/KNContactsPicker/KNContactsPickerController.swift @@ -73,7 +73,7 @@ class KNContactsPickerController: UITableViewController { self.navigationItem.leftBarButtonItem = KNPickerElements.clearButton(count, action: #selector(clearSelected), target: self, settings: settings) } else if (settings.showSelectAllContactsButton && settings.selectionMode == .multiple) { - self.navigationItem.leftBarButtonItem = KNPickerElements.clearButton(count, action: #selector(selectAllSelected), target: self, settings: settings) + self.navigationItem.leftBarButtonItem = KNPickerElements.selectAllButton(count, action: #selector(selectAllSelected), target: self, settings: settings) } else { self.navigationItem.leftBarButtonItem = nil } From c67aa721ef03d751c0fb5c6539a0c1e08b23abd9 Mon Sep 17 00:00:00 2001 From: Miroslav Kutak Date: Wed, 24 Jan 2024 12:42:00 +0100 Subject: [PATCH 03/11] fix: fetch contacts and sort made public --- Sources/KNContactsPicker/KNContactsPicker.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/KNContactsPicker/KNContactsPicker.swift b/Sources/KNContactsPicker/KNContactsPicker.swift index 284432e..7885c91 100644 --- a/Sources/KNContactsPicker/KNContactsPicker.swift +++ b/Sources/KNContactsPicker/KNContactsPicker.swift @@ -54,7 +54,11 @@ open class KNContactsPicker: UINavigationController { return controller } - func fetchContacts() { + public func sort() { + self.sortingOutcome = KNContactUtils.sortContactsIntoSections(contacts: settings.pickerContactsList, sortingType: settings.displayContactsSortedBy) + } + + public func fetchContacts() { switch settings.pickerContactsSource { case .userProvided: From be2183264dce7cc4c67d19aee0566e707df2f457 Mon Sep 17 00:00:00 2001 From: Miroslav Kutak Date: Wed, 24 Jan 2024 13:21:37 +0100 Subject: [PATCH 04/11] Updating KNContactsPickerController rather than adding it if it already exists when fetchingContacts. --- .../KNContactsPicker/KNContactsPicker.swift | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Sources/KNContactsPicker/KNContactsPicker.swift b/Sources/KNContactsPicker/KNContactsPicker.swift index 7885c91..4e8c35c 100644 --- a/Sources/KNContactsPicker/KNContactsPicker.swift +++ b/Sources/KNContactsPicker/KNContactsPicker.swift @@ -56,23 +56,33 @@ open class KNContactsPicker: UINavigationController { public func sort() { self.sortingOutcome = KNContactUtils.sortContactsIntoSections(contacts: settings.pickerContactsList, sortingType: settings.displayContactsSortedBy) + if let controller = self.topViewControllers as? KNContactsPickerController { + controller.contacts = sortingOutcome?.sortedContacts ?? [] + controller.sortedContacts = sortingOutcome?.contactsSortedInSections ?? [:] + controller.sections = sortingOutcome?.sections ?? [] + controller.tableView.reloadData() + } } public func fetchContacts() { - switch settings.pickerContactsSource { case .userProvided: self.sortingOutcome = KNContactUtils.sortContactsIntoSections(contacts: settings.pickerContactsList, sortingType: settings.displayContactsSortedBy) case .default: requestAndSortContacts { [weak self] result in guard let self = self else { return } - let contactPickerController = self.getContactsPicker() - - self.presentationController?.delegate = contactPickerController - self.viewControllers.append(contactPickerController) + if let controller = self.topViewControllers as? KNContactsPickerController { + controller.contacts = sortingOutcome?.sortedContacts ?? [] + controller.sortedContacts = sortingOutcome?.contactsSortedInSections ?? [:] + controller.sections = sortingOutcome?.sections ?? [] + controller.tableView.reloadData() + } else { + let contactPickerController = self.getContactsPicker() + self.presentationController?.delegate = contactPickerController + self.viewControllers.append(contactPickerController) + } } } - } private func requestAndSortContacts(completion: @escaping (Result<[CNContact], KNContactFetchingError>) -> Void) { From 29d613d6518d4899258bbfa9a70f0b54a3458f1f Mon Sep 17 00:00:00 2001 From: Miroslav Kutak Date: Wed, 24 Jan 2024 20:00:43 +0100 Subject: [PATCH 05/11] public var settings: KNPickerSettings --- Sources/KNContactsPicker/KNContactsPicker.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/KNContactsPicker/KNContactsPicker.swift b/Sources/KNContactsPicker/KNContactsPicker.swift index 4e8c35c..aae13c9 100644 --- a/Sources/KNContactsPicker/KNContactsPicker.swift +++ b/Sources/KNContactsPicker/KNContactsPicker.swift @@ -12,7 +12,7 @@ import Contacts open class KNContactsPicker: UINavigationController { - var settings: KNPickerSettings = KNPickerSettings() + public var settings: KNPickerSettings = KNPickerSettings() weak var contactPickingDelegate: KNContactPickingDelegate! private var contacts: [CNContact] = [] @@ -56,7 +56,7 @@ open class KNContactsPicker: UINavigationController { public func sort() { self.sortingOutcome = KNContactUtils.sortContactsIntoSections(contacts: settings.pickerContactsList, sortingType: settings.displayContactsSortedBy) - if let controller = self.topViewControllers as? KNContactsPickerController { + if let controller = self.topViewController as? KNContactsPickerController { controller.contacts = sortingOutcome?.sortedContacts ?? [] controller.sortedContacts = sortingOutcome?.contactsSortedInSections ?? [:] controller.sections = sortingOutcome?.sections ?? [] @@ -71,7 +71,7 @@ open class KNContactsPicker: UINavigationController { case .default: requestAndSortContacts { [weak self] result in guard let self = self else { return } - if let controller = self.topViewControllers as? KNContactsPickerController { + if let controller = self.topViewController as? KNContactsPickerController { controller.contacts = sortingOutcome?.sortedContacts ?? [] controller.sortedContacts = sortingOutcome?.contactsSortedInSections ?? [:] controller.sections = sortingOutcome?.sections ?? [] From fd1436752128c71087671eaf2c9ab5fecfef6a72 Mon Sep 17 00:00:00 2001 From: Miroslav Kutak Date: Sat, 25 May 2024 13:30:01 +0200 Subject: [PATCH 06/11] Added toolbarItems to the KNPickerSettings --- Sources/KNContactsPicker/KNContactsPickerController.swift | 1 + Sources/KNContactsPicker/KNPickerSettings.swift | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/KNContactsPicker/KNContactsPickerController.swift b/Sources/KNContactsPicker/KNContactsPickerController.swift index 03c291f..007298c 100644 --- a/Sources/KNContactsPicker/KNContactsPickerController.swift +++ b/Sources/KNContactsPicker/KNContactsPickerController.swift @@ -56,6 +56,7 @@ class KNContactsPickerController: UITableViewController { self.tableView.register(KNContactCell.self, forCellReuseIdentifier: CELL_ID) self.navigationItem.largeTitleDisplayMode = .always self.navigationItem.title = settings.pickerTitle + self.toolbarItems = settings.toolbarItems self.tableView.sectionIndexColor = UIColor.lightGray self.searchResultsController = KNPickerElements.searchResultsController(settings: settings, controller: self) diff --git a/Sources/KNContactsPicker/KNPickerSettings.swift b/Sources/KNContactsPicker/KNPickerSettings.swift index 3d51434..ce19ace 100644 --- a/Sources/KNContactsPicker/KNPickerSettings.swift +++ b/Sources/KNContactsPicker/KNPickerSettings.swift @@ -120,7 +120,10 @@ public struct KNPickerSettings { // The colour or gradient colours to display as background // if contact doesn't have a thumbnail image set. public var contactInitialsBackgroundColor: GradientColors = GradientColors(top: #colorLiteral(red: 0.2199510634, green: 0.2199510634, blue: 0.2199510634, alpha: 1), bottom: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)) - + + // Items on the toolbar + public var toolbarItems = [UIBarButtonItem]() + public init() {} } #endif From b3804b4ad0f4200d635184c64130b3a0cce5b7c0 Mon Sep 17 00:00:00 2001 From: Miroslav Kutak Date: Sat, 25 May 2024 22:27:31 +0200 Subject: [PATCH 07/11] feat: cell configuration block in settings --- Sources/KNContactsPicker/KNContactCell.swift | 68 ++++++++-------- .../KNContactsPicker/KNContactCellModel.swift | 24 +++--- .../KNContactsPickerController.swift | 79 ++++++++++--------- .../KNContactsPicker/KNPickerSettings.swift | 46 +++++------ 4 files changed, 111 insertions(+), 106 deletions(-) diff --git a/Sources/KNContactsPicker/KNContactCell.swift b/Sources/KNContactsPicker/KNContactCell.swift index e19c8e1..50a146a 100644 --- a/Sources/KNContactsPicker/KNContactCell.swift +++ b/Sources/KNContactsPicker/KNContactCell.swift @@ -9,9 +9,10 @@ #if canImport(UIKit) import UIKit -class KNContactCell: UITableViewCell { +public class KNContactCell: UITableViewCell { + public private(set) var contactModel: KNContactCellModel? private var disabled: Bool = false - + private var initialColor: UIColor { get { if #available(iOS 13.0, *) { @@ -22,7 +23,7 @@ class KNContactCell: UITableViewCell { return UIColor.white } } - + private let profileImageView: UIImageView = { let img = UIImageView() let bounds = CGRect(x: 0, y: 0, width: 40, height: 40) @@ -33,14 +34,14 @@ class KNContactCell: UITableViewCell { width: 40, height: img.frame.size.height ) img.contentMode = .scaleAspectFill - + // enable autolayout img.translatesAutoresizingMaskIntoConstraints = false img.layer.cornerRadius = 20 img.clipsToBounds = true return img }() - + private let nameLabel: UILabel = { let label = UILabel() label.font = UIFont.systemFont(ofSize: 16) @@ -51,7 +52,7 @@ class KNContactCell: UITableViewCell { label.translatesAutoresizingMaskIntoConstraints = false return label }() - + private let subtitleLabel: UILabel = { let label = UILabel() label.font = UIFont.systemFont(ofSize: 11) @@ -62,15 +63,15 @@ class KNContactCell: UITableViewCell { label.translatesAutoresizingMaskIntoConstraints = false return label }() - - private let containerView: UIStackView = { + + public let containerView: UIStackView = { let view = UIStackView() view.translatesAutoresizingMaskIntoConstraints = false view.axis = .vertical view.alignment = .fill view.distribution = .fillProportionally view.spacing = 0 - + return view }() @@ -78,50 +79,50 @@ class KNContactCell: UITableViewCell { super.init(style: style, reuseIdentifier: reuseIdentifier) self.contentView.addSubview(profileImageView) self.containerView.addArrangedSubview(nameLabel) - + self.contentView.addSubview(containerView) - + profileImageView.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor).isActive = true profileImageView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 10).isActive = true profileImageView.widthAnchor.constraint(equalToConstant: 40).isActive = true profileImageView.heightAnchor.constraint(equalToConstant: 40).isActive = true - + containerView.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor).isActive = true containerView.leadingAnchor.constraint(equalTo: self.profileImageView.trailingAnchor, constant: 10).isActive = true containerView.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -10).isActive = true - + nameLabel.adjustsFontSizeToFitWidth = true nameLabel.minimumScaleFactor = CGFloat(0.7) nameLabel.font = UIFont.boldSystemFont(ofSize: nameLabel.font.pointSize) nameLabel.leadingAnchor.constraint(equalTo: self.containerView.leadingAnchor).isActive = true nameLabel.trailingAnchor.constraint(equalTo: self.containerView.trailingAnchor).isActive = true - - + + self.selectionStyle = .none } - + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } - - override func prepareForReuse() { + + public override func prepareForReuse() { super.prepareForReuse() self.disabled = false } - override func setSelected(_ selected: Bool, animated: Bool) { + public override func setSelected(_ selected: Bool, animated: Bool) { if (!disabled) { super.setSelected(selected, animated: animated) self.setAppropriateStyle() } } - + func setAppropriateStyle() { self.isSelected ? self.setCellToSelectedStyle() : self.setCellToUnselectedStyle() self.layoutIfNeeded() self.layoutSubviews() } - + func setCellToSelectedStyle() { self.accessoryView?.backgroundColor = UIColor.systemBlue self.contentView.backgroundColor = UIColor.systemBlue @@ -132,27 +133,27 @@ class KNContactCell: UITableViewCell { self.nameLabel.textColor = UIColor.white self.subtitleLabel.textColor = UIColor.lightText } - + func setCellToUnselectedStyle() { self.backgroundColor = initialColor self.accessoryView?.backgroundColor = UIColor.clear self.accessoryType = UITableViewCell.AccessoryType.none self.contentView.backgroundColor = initialColor - + self.nameLabel.textColor = UIColor.black self.subtitleLabel.textColor = UIColor.lightText if #available(iOS 13.0, *) { self.nameLabel.textColor = UIColor.label self.subtitleLabel.textColor = UIColor.secondaryLabel } - + } - + func setDisabled(disabled: Bool) { self.disabled = disabled - + self.isUserInteractionEnabled = !self.disabled - + if (self.disabled) { self.nameLabel.textColor = UIColor.lightGray if #available(iOS 13.0, *) { @@ -166,23 +167,24 @@ class KNContactCell: UITableViewCell { } } } - - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + + public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { guard let previous = previousTraitCollection else { return } - + if traitCollection.userInterfaceStyle != previous.userInterfaceStyle { self.setAppropriateStyle() } } - + public func set(contactModel: KNContactCellModel) { + self.contactModel = contactModel self.nameLabel.text = contactModel.getName() self.subtitleLabel.text = contactModel.getSubtitle() - + let image = contactModel.getImage(with: self.profileImageView.bounds, scaled: self.profileImageView.shouldScale) self.profileImageView.image = image self.profileImageView.highlightedImage = image - + if !self.subtitleLabel.text!.isEmpty { self.containerView.addArrangedSubview(subtitleLabel) subtitleLabel.topAnchor.constraint(equalTo: self.nameLabel.bottomAnchor, constant: 0).isActive = true diff --git a/Sources/KNContactsPicker/KNContactCellModel.swift b/Sources/KNContactsPicker/KNContactCellModel.swift index 6872e00..f147e26 100644 --- a/Sources/KNContactsPicker/KNContactCellModel.swift +++ b/Sources/KNContactsPicker/KNContactCellModel.swift @@ -10,26 +10,26 @@ import Contacts import UIKit -struct KNContactCellModel { - private var contact: CNContact +public struct KNContactCellModel { + public private(set) var contact: CNContact private var settings: KNPickerSettings private var formatter: CNContactFormatter - + init(contact: CNContact, settings: KNPickerSettings, formatter: CNContactFormatter) { self.contact = contact self.settings = settings self.formatter = formatter } - + func getName() -> String { return contact.getFullName(using: formatter) } - + func getImage(with bounds: CGRect, scaled: Bool) -> UIImage? { let initialsBgColors = settings.contactInitialsBackgroundColor let userProvidedImage = settings.contactCellUserProvidedImage let order = settings.contactCellImageOptionOrder - + var image: UIImage? for item in order { if image == nil { @@ -46,11 +46,11 @@ struct KNContactCellModel { break } } - + return image - + } - + func getSubtitle() -> String { let subtitleOption = settings.subtitleDisplayInfo switch subtitleOption { @@ -64,14 +64,14 @@ struct KNContactCellModel { return getFirstEmailAddress() } } - + private func getFirstPhoneNumber() -> String { return self.contact.phoneNumbers.compactMap { String($0.value.stringValue) }.first ?? "" } - + private func getFirstEmailAddress() -> String { return self.contact.emailAddresses.compactMap { String($0.value) }.first ?? "" } - + } #endif diff --git a/Sources/KNContactsPicker/KNContactsPickerController.swift b/Sources/KNContactsPicker/KNContactsPickerController.swift index 007298c..8a13da3 100644 --- a/Sources/KNContactsPicker/KNContactsPickerController.swift +++ b/Sources/KNContactsPicker/KNContactsPickerController.swift @@ -19,16 +19,16 @@ class KNContactsPickerController: UITableViewController { public var settings: KNPickerSettings = KNPickerSettings() public weak var delegate: KNContactPickingDelegate? public weak var presentationDelegate: KNContactsPickerControllerPresentationDelegate? - + private let CELL_ID = "KNContactCell" private let formatter = CNContactFormatter() - + var searchResultsController: UISearchController? var contacts: [CNContact] = [] var filteredContacts: [CNContact] = [] var sortedContacts: [String: [CNContact]] = [:] var sections: [String] = [] - + private var selectedContacts: Set = [] { willSet(newValue) { self.configureButtons(count: newValue.count) @@ -37,39 +37,39 @@ class KNContactsPickerController: UITableViewController { self.tableView.reloadData() } } - + var shouldDisableSelection: Bool { get { return settings.selectionMode == .singleDeselectOthers && selectedContacts.count == 1 } } - + var isSearchBarEmpty: Bool { return searchResultsController?.searchBar.text?.isEmpty ?? true } - + var isFiltering: Bool { return (searchResultsController?.isActive ?? false) && !isSearchBarEmpty } - + override open func viewDidLoad() { super.viewDidLoad() - + self.tableView.register(KNContactCell.self, forCellReuseIdentifier: CELL_ID) self.navigationItem.largeTitleDisplayMode = .always self.navigationItem.title = settings.pickerTitle self.toolbarItems = settings.toolbarItems self.tableView.sectionIndexColor = UIColor.lightGray - + self.searchResultsController = KNPickerElements.searchResultsController(settings: settings, controller: self) self.navigationItem.searchController = searchResultsController self.navigationItem.largeTitleDisplayMode = .always self.navigationItem.hidesSearchBarWhenScrolling = false - + self.configureButtons(count: self.selectedContacts.count) } - + func configureButtons(count: Int) { self.navigationItem.rightBarButtonItem = KNPickerElements.selectButton(count, action: #selector(completeSelection), target: self, settings: settings) - + if count > 0 { self.navigationItem.leftBarButtonItem = KNPickerElements.clearButton(count, action: #selector(clearSelected), target: self, settings: settings) } @@ -79,24 +79,24 @@ class KNContactsPickerController: UITableViewController { self.navigationItem.leftBarButtonItem = nil } } - + public func getSelectedContacts() -> [CNContact] { return Array(selectedContacts) } - + @objc func selectAllSelected() { let contactsToAdd = isFiltering ? filteredContacts : contacts selectedContacts = Set(contactsToAdd) } - + @objc func completeSelection() { self.presentationDelegate?.contactPickerDidSelect(self) } - + @objc func clearSelected() { self.selectedContacts.removeAll() } - + fileprivate func toggleSelected(_ contact: CNContact) { if (settings.selectionMode == .singleReselect) { self.clearSelected() @@ -108,17 +108,17 @@ class KNContactsPickerController: UITableViewController { selectedContacts.insert(contact) } } - + fileprivate func confirmCancel() { let firstContactsName = selectedContacts.first?.getFullName(using: formatter) ?? "" - + let alert = KNPickerElements.pullToDismissAlert(count: selectedContacts.count, contactName: firstContactsName, settings: settings, controller: self) - + alert.popoverPresentationController?.barButtonItem = self.navigationItem.rightBarButtonItem - + self.present(alert, animated: true, completion: nil) } fileprivate func getContact(at indexPath: IndexPath) -> CNContact { @@ -130,59 +130,60 @@ class KNContactsPickerController: UITableViewController { return sectionContact![indexPath.row] } } - + // MARK: Table View Sections override open func numberOfSections(in tableView: UITableView) -> Int { return isFiltering ? 1 : self.sections.count } - + override open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return isFiltering ? settings.searchResultSectionTitle : self.sections[section] } - + // MARK: Table View Rows override open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return isFiltering ? self.filteredContacts.count : self.sortedContacts[self.sections[section]]?.count ?? 0 } - + override open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: CELL_ID, for: indexPath) as! KNContactCell let contact = self.getContact(at: indexPath) let contactModel = KNContactCellModel(contact: contact, settings: settings, formatter: formatter) - + let disabled = ( shouldDisableSelection && !selectedContacts.contains(contact) ) || settings.conditionToDisableContact(contact) - + let selected = selectedContacts.contains(contact) cell.set(contactModel: contactModel) - + settings.cellConfiguration?(cell, indexPath, contactModel) + cell.setDisabled(disabled: disabled) cell.setSelected(selected, animated: false) return cell } - + override open func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 50 } - + override open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let contact = self.getContact(at: indexPath) self.toggleSelected(contact) } - + // MARK: Section Index Title override func sectionIndexTitles(for tableView: UITableView) -> [String]? { return self.sections } - + override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int { return index } - + } // MARK: SEARCH RESULTS UPDATING extension KNContactsPickerController: UISearchResultsUpdating { - + func filterContentForSearchText(_ searchText: String) { let filteredContacts = self.contacts.filter({( currentContact: CNContact) -> Bool in return (currentContact.getFullName(using: formatter).lowercased().contains(searchText.lowercased())) @@ -190,8 +191,8 @@ extension KNContactsPickerController: UISearchResultsUpdating { let outcome = KNContactUtils.sortContactsIntoSections(contacts: filteredContacts, sortingType: .givenName) self.filteredContacts = outcome.sortedContacts } - - + + public func updateSearchResults(for searchController: UISearchController) { self.filterContentForSearchText(searchController.searchBar.text!) self.tableView.reloadData() @@ -200,15 +201,15 @@ extension KNContactsPickerController: UISearchResultsUpdating { // MARK: PRESENTATION DELEGATE extension KNContactsPickerController: UIAdaptivePresentationControllerDelegate { - + func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool { return selectedContacts.count == 0 } - + func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) { self.confirmCancel() } - + } #endif diff --git a/Sources/KNContactsPicker/KNPickerSettings.swift b/Sources/KNContactsPicker/KNPickerSettings.swift index ce19ace..e77b8fa 100644 --- a/Sources/KNContactsPicker/KNPickerSettings.swift +++ b/Sources/KNContactsPicker/KNPickerSettings.swift @@ -36,66 +36,66 @@ public enum KNContactCellImageOptions { } public struct KNPickerSettings { - + // MARK: PICKER TOP SETTINGS public var pickerTitle: String = "Contacts" - + // The source for contacts - `default` uses all contacts on device. // If setting to `userProvided` then make sure to set pickerContactsList with // the list of contacts to display public var pickerContactsSource: KNContactsSource = .default public var pickerContactsList: [CNContact] = [] - - // The + + // The public var displayContactsSortedBy: KNContactSortingOption = .familyName - + // Condition for presenting the contact in the list. // If the condition matches, the contact will be shown. Otherwise it won't. public var conditionToDisplayContact: KNFilteringPredicate = { _ in true } - + // Condition for displaying the contact cell disabled. // If the condition matches, the cell will be shown in a disabled state. // Otherwise it will be active and selectable. public var conditionToDisableContact: KNFilteringPredicate = { _ in false } - + // Enum value whether the contact picker should allow a single or multiple contacts public var selectionMode: KNContactsPickerMode = .singleDeselectOthers - + // Boolean whether picker should immediately return the first contact picked // when the picker selectionMode is .single public var immediatelyReturnAfterSingleSelection: Bool = false - + // The pick button title to display when no contacts have been selected public var defaultPickButtonTitle: String = "Done" - + // The pick button title to display when all contacts selection button is available public var selectAllContactsButtonTitle: String = "Select All" - + // Boolean value whether to show Select All button // Lets user select all contacts at once public var showSelectAllContactsButton: Bool = true - + // The pick button title to display when contacts have been selected. // You may specify a string formatting for one number representing the number of selected contacts. public var selectedContactsPickButtonTitle: String = "Select %d" - + // Boolean value whether to show number of selected contacts public var showSelectionCountInPickButton: Bool = true - - + + // The clear selection button public var clearSelectionButtonTitle: String = " Clear" - - + + // MARK: SEARCH SETTINGS - + // The placeholder text to display in the search bar public var searchBarPlaceholder: String = "Search contacts" // The text to display when search results are shown public var searchResultSectionTitle: String = "Top name matches" - + // MARK: PULL TO DIMISS ALERT - + // The Pull to dimiss alert title public var pullToDismissAlertTitle: String = "Dismiss confirmation" // Message to return selection with the selected contacts when @@ -110,13 +110,13 @@ public struct KNPickerSettings { public var pullToDismissDiscardSelectionButtonTitle: String = "Discard" // Cancel pull to dismiss alert text public var pullToDismissCancelButtonTitle: String = "Cancel" - + // MARK: CONTACT DISPLAY // Enum value for the subtitle information to be displayed public var subtitleDisplayInfo: KNContactSubtitleInfo = .none public var contactCellImageOptionOrder: [KNContactCellImageOptions] = [.contactImage, .userProvidedImage, .initials] public var contactCellUserProvidedImage: UIImage? - + // The colour or gradient colours to display as background // if contact doesn't have a thumbnail image set. public var contactInitialsBackgroundColor: GradientColors = GradientColors(top: #colorLiteral(red: 0.2199510634, green: 0.2199510634, blue: 0.2199510634, alpha: 1), bottom: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)) @@ -124,6 +124,8 @@ public struct KNPickerSettings { // Items on the toolbar public var toolbarItems = [UIBarButtonItem]() + public var cellConfiguration: ((_ cell: KNContactCell, _ indexPath: IndexPath, _ model: KNContactCellModel) -> Void)? + public init() {} } #endif From 366fe5f6ddeb354483dfc4c998e91c8e71d970ce Mon Sep 17 00:00:00 2001 From: Miroslav Kutak Date: Sun, 23 Jun 2024 07:18:36 +0200 Subject: [PATCH 08/11] iOS 12 support --- Example/KNContactsViewer.xcodeproj/project.pbxproj | 10 +++++----- .../CellImageTableViewController.swift | 6 +++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Example/KNContactsViewer.xcodeproj/project.pbxproj b/Example/KNContactsViewer.xcodeproj/project.pbxproj index 89c4993..5248a82 100644 --- a/Example/KNContactsViewer.xcodeproj/project.pbxproj +++ b/Example/KNContactsViewer.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -255,7 +255,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -309,7 +309,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -326,7 +326,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 8UXEK92PP3; INFOPLIST_FILE = KNContactsViewer/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -345,7 +345,7 @@ CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 8UXEK92PP3; INFOPLIST_FILE = KNContactsViewer/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Example/KNContactsViewer/CellImageTableViewController.swift b/Example/KNContactsViewer/CellImageTableViewController.swift index b5bdcef..b44e4bd 100644 --- a/Example/KNContactsViewer/CellImageTableViewController.swift +++ b/Example/KNContactsViewer/CellImageTableViewController.swift @@ -72,7 +72,11 @@ class CellImageTableViewController: UITableViewController { if indexPath.section == 0 { let option = cellImageOptions[indexPath.row] - cell.imageView?.image = UIImage(systemName: imageSource[option]!)! + if #available(iOS 13.0, *) { + cell.imageView?.image = UIImage(systemName: imageSource[option]!)! + } else { + // Fallback on earlier versions + } cell.textLabel?.text = titleSource[option]! } else if indexPath.section == 1 { From 81d0552aeaa6acd719b1bfc132e136c439735ac2 Mon Sep 17 00:00:00 2001 From: Miroslav Kutak Date: Tue, 13 Aug 2024 21:58:54 +0200 Subject: [PATCH 09/11] feat: leading and trailing swipe actions configuration --- .../KNContactsPicker/KNContactsPickerController.swift | 10 ++++++++++ Sources/KNContactsPicker/KNPickerSettings.swift | 3 +++ 2 files changed, 13 insertions(+) diff --git a/Sources/KNContactsPicker/KNContactsPickerController.swift b/Sources/KNContactsPicker/KNContactsPickerController.swift index 8a13da3..99a6c1f 100644 --- a/Sources/KNContactsPicker/KNContactsPickerController.swift +++ b/Sources/KNContactsPicker/KNContactsPickerController.swift @@ -170,6 +170,16 @@ class KNContactsPickerController: UITableViewController { self.toggleSelected(contact) } + func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let contactModel = KNContactCellModel(contact: contact, settings: settings, formatter: formatter) + return settings.leadingCellSwipeActionConfiguration?(cell, indexPath, contactModel) + } + + func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let contactModel = KNContactCellModel(contact: contact, settings: settings, formatter: formatter) + return settings.trailingCellSwipeActionConfiguration?(cell, indexPath, contactModel) + } + // MARK: Section Index Title override func sectionIndexTitles(for tableView: UITableView) -> [String]? { return self.sections diff --git a/Sources/KNContactsPicker/KNPickerSettings.swift b/Sources/KNContactsPicker/KNPickerSettings.swift index e77b8fa..68bf85d 100644 --- a/Sources/KNContactsPicker/KNPickerSettings.swift +++ b/Sources/KNContactsPicker/KNPickerSettings.swift @@ -126,6 +126,9 @@ public struct KNPickerSettings { public var cellConfiguration: ((_ cell: KNContactCell, _ indexPath: IndexPath, _ model: KNContactCellModel) -> Void)? + public var leadingCellSwipeActionConfiguration: ((_ cell: KNContactCell, _ indexPath: IndexPath, _ model: KNContactCellModel) -> UISwipeActionsConfiguration?)? + public var trailingCellSwipeActionConfiguration: ((_ cell: KNContactCell, _ indexPath: IndexPath, _ model: KNContactCellModel) -> UISwipeActionsConfiguration?)? + public init() {} } #endif From 6761b4b66ae08ed0972305ce4ab428e7298e8d11 Mon Sep 17 00:00:00 2001 From: Miroslav Kutak Date: Tue, 13 Aug 2024 22:28:39 +0200 Subject: [PATCH 10/11] fix: leading and trailing swipe actions configuration tableview methods - override and getting the models properly --- .../KNContactsPicker/KNContactsPickerController.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Sources/KNContactsPicker/KNContactsPickerController.swift b/Sources/KNContactsPicker/KNContactsPickerController.swift index 99a6c1f..584568d 100644 --- a/Sources/KNContactsPicker/KNContactsPickerController.swift +++ b/Sources/KNContactsPicker/KNContactsPickerController.swift @@ -170,12 +170,18 @@ class KNContactsPickerController: UITableViewController { self.toggleSelected(contact) } - func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + guard let cell = tableView.cellForRow(at: indexPath) as? KNContactCell, + let contact = cell.contactModel?.contact + else { return nil } let contactModel = KNContactCellModel(contact: contact, settings: settings, formatter: formatter) return settings.leadingCellSwipeActionConfiguration?(cell, indexPath, contactModel) } - func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + guard let cell = tableView.cellForRow(at: indexPath) as? KNContactCell, + let contact = cell.contactModel?.contact + else { return nil } let contactModel = KNContactCellModel(contact: contact, settings: settings, formatter: formatter) return settings.trailingCellSwipeActionConfiguration?(cell, indexPath, contactModel) } From 64f911af3dccfa316ffd3c4c446c15b928d71b62 Mon Sep 17 00:00:00 2001 From: Miroslav Kutak Date: Wed, 14 Aug 2024 08:45:04 +0200 Subject: [PATCH 11/11] fix: userProvided contacts are being shown --- Sources/KNContactsPicker/KNContactsPicker.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Sources/KNContactsPicker/KNContactsPicker.swift b/Sources/KNContactsPicker/KNContactsPicker.swift index aae13c9..4e77cec 100644 --- a/Sources/KNContactsPicker/KNContactsPicker.swift +++ b/Sources/KNContactsPicker/KNContactsPicker.swift @@ -68,6 +68,16 @@ open class KNContactsPicker: UINavigationController { switch settings.pickerContactsSource { case .userProvided: self.sortingOutcome = KNContactUtils.sortContactsIntoSections(contacts: settings.pickerContactsList, sortingType: settings.displayContactsSortedBy) + if let controller = self.topViewController as? KNContactsPickerController { + controller.contacts = sortingOutcome?.sortedContacts ?? [] + controller.sortedContacts = sortingOutcome?.contactsSortedInSections ?? [:] + controller.sections = sortingOutcome?.sections ?? [] + controller.tableView.reloadData() + } else { + let contactPickerController = self.getContactsPicker() + self.presentationController?.delegate = contactPickerController + self.viewControllers.append(contactPickerController) + } case .default: requestAndSortContacts { [weak self] result in guard let self = self else { return }