Refactor ContactsPicker to show a clean search bar

* Replace UITableviewController to UIViewController
* Create a custom xib file
This commit is contained in:
Thomas Guillet 2016-12-12 13:45:16 +01:00 committed by Michael Kirk
parent a70d5f88ba
commit d7b27a4021
4 changed files with 97 additions and 58 deletions

View file

@ -485,6 +485,7 @@
E197B62418BBF5BB00F073E5 /* SoundPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B62318BBF5BB00F073E5 /* SoundPlayer.m */; }; E197B62418BBF5BB00F073E5 /* SoundPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B62318BBF5BB00F073E5 /* SoundPlayer.m */; };
E197B62718BBF63B00F073E5 /* SoundBoard.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B62618BBF63B00F073E5 /* SoundBoard.m */; }; E197B62718BBF63B00F073E5 /* SoundBoard.m in Sources */ = {isa = PBXBuildFile; fileRef = E197B62618BBF63B00F073E5 /* SoundBoard.m */; };
E1CD329618BCFF9900B1A496 /* SoundInstance.m in Sources */ = {isa = PBXBuildFile; fileRef = E1CD329518BCFF9900B1A496 /* SoundInstance.m */; }; E1CD329618BCFF9900B1A496 /* SoundInstance.m in Sources */ = {isa = PBXBuildFile; fileRef = E1CD329518BCFF9900B1A496 /* SoundInstance.m */; };
E94066151DFC5B7B00B15392 /* ContactsPicker.xib in Resources */ = {isa = PBXBuildFile; fileRef = E94066141DFC5B7B00B15392 /* ContactsPicker.xib */; };
EF764C351DB67CC5000D9A87 /* UIViewController+CameraPermissions.m in Sources */ = {isa = PBXBuildFile; fileRef = EF764C341DB67CC5000D9A87 /* UIViewController+CameraPermissions.m */; }; EF764C351DB67CC5000D9A87 /* UIViewController+CameraPermissions.m in Sources */ = {isa = PBXBuildFile; fileRef = EF764C341DB67CC5000D9A87 /* UIViewController+CameraPermissions.m */; };
FC31962A1A067D8F0094C78E /* MessageComposeTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FC3196291A067D8F0094C78E /* MessageComposeTableViewController.m */; }; FC31962A1A067D8F0094C78E /* MessageComposeTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FC3196291A067D8F0094C78E /* MessageComposeTableViewController.m */; };
FC31962D1A06A2190094C78E /* FingerprintViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FC31962C1A06A2190094C78E /* FingerprintViewController.m */; }; FC31962D1A06A2190094C78E /* FingerprintViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FC31962C1A06A2190094C78E /* FingerprintViewController.m */; };
@ -1115,6 +1116,7 @@
E1CD329418BCFF9900B1A496 /* SoundInstance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SoundInstance.h; sourceTree = "<group>"; }; E1CD329418BCFF9900B1A496 /* SoundInstance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SoundInstance.h; sourceTree = "<group>"; };
E1CD329518BCFF9900B1A496 /* SoundInstance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SoundInstance.m; sourceTree = "<group>"; }; E1CD329518BCFF9900B1A496 /* SoundInstance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SoundInstance.m; sourceTree = "<group>"; };
E85DB184824BA9DC302EC8B3 /* Pods-SignalTests.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalTests.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SignalTests/Pods-SignalTests.app store release.xcconfig"; sourceTree = "<group>"; }; E85DB184824BA9DC302EC8B3 /* Pods-SignalTests.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalTests.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SignalTests/Pods-SignalTests.app store release.xcconfig"; sourceTree = "<group>"; };
E94066141DFC5B7B00B15392 /* ContactsPicker.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ContactsPicker.xib; sourceTree = "<group>"; };
EF764C331DB67CC5000D9A87 /* UIViewController+CameraPermissions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIViewController+CameraPermissions.h"; path = "util/UIViewController+CameraPermissions.h"; sourceTree = "<group>"; }; EF764C331DB67CC5000D9A87 /* UIViewController+CameraPermissions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIViewController+CameraPermissions.h"; path = "util/UIViewController+CameraPermissions.h"; sourceTree = "<group>"; };
EF764C341DB67CC5000D9A87 /* UIViewController+CameraPermissions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIViewController+CameraPermissions.m"; path = "util/UIViewController+CameraPermissions.m"; sourceTree = "<group>"; }; EF764C341DB67CC5000D9A87 /* UIViewController+CameraPermissions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIViewController+CameraPermissions.m"; path = "util/UIViewController+CameraPermissions.m"; sourceTree = "<group>"; };
FC3196281A067D8F0094C78E /* MessageComposeTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MessageComposeTableViewController.h; sourceTree = "<group>"; }; FC3196281A067D8F0094C78E /* MessageComposeTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MessageComposeTableViewController.h; sourceTree = "<group>"; };
@ -1843,6 +1845,7 @@
458E38301D6682450094BD24 /* OWSQRCodeScanningViewController.m */, 458E38301D6682450094BD24 /* OWSQRCodeScanningViewController.m */,
451764261DE939F300EDB8B9 /* ContactsPicker.swift */, 451764261DE939F300EDB8B9 /* ContactsPicker.swift */,
45514DE11DDFA183003EFF90 /* InviteFlow.swift */, 45514DE11DDFA183003EFF90 /* InviteFlow.swift */,
E94066141DFC5B7B00B15392 /* ContactsPicker.xib */,
); );
name = "View Controllers"; name = "View Controllers";
path = "view controllers"; path = "view controllers";
@ -2649,6 +2652,7 @@
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
E94066151DFC5B7B00B15392 /* ContactsPicker.xib in Resources */,
AD41D7B61A6F6F0600241130 /* play_button@2x.png in Resources */, AD41D7B61A6F6F0600241130 /* play_button@2x.png in Resources */,
AD83FF3F1A73426500B5C81A /* audio_pause_button_blue.png in Resources */, AD83FF3F1A73426500B5C81A /* audio_pause_button_blue.png in Resources */,
45E1F3A31DEF1DF000852CF1 /* NoSignalContactsView.xib in Resources */, 45E1F3A31DEF1DF000852CF1 /* NoSignalContactsView.xib in Resources */,

View file

@ -33,7 +33,10 @@ public enum SubtitleCellValue{
} }
@available(iOS 9.0, *) @available(iOS 9.0, *)
open class ContactsPicker: UITableViewController, UISearchResultsUpdating, UISearchBarDelegate { open class ContactsPicker: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
@IBOutlet var tableView: UITableView!
@IBOutlet var searchBar: UISearchBar!
// MARK: - Properties // MARK: - Properties
@ -42,7 +45,6 @@ open class ContactsPicker: UITableViewController, UISearchResultsUpdating, UISea
let contactsManager: OWSContactsManager let contactsManager: OWSContactsManager
let collation = UILocalizedIndexedCollation.current() let collation = UILocalizedIndexedCollation.current()
let contactStore = CNContactStore() let contactStore = CNContactStore()
lazy var resultSearchController = UISearchController()
// Data Source State // Data Source State
lazy var sections = [[CNContact]]() lazy var sections = [[CNContact]]()
@ -66,8 +68,9 @@ open class ContactsPicker: UITableViewController, UISearchResultsUpdating, UISea
super.viewDidLoad() super.viewDidLoad()
title = NSLocalizedString("INVITE_FRIENDS_PICKER_TITLE", comment: "Navbar title") title = NSLocalizedString("INVITE_FRIENDS_PICKER_TITLE", comment: "Navbar title")
// Don't obscure table header (search bar) with table index searchBar.placeholder = NSLocalizedString("INVITE_FRIENDS_PICKER_SEARCHBAR_PLACEHOLDER", comment: "Search")
tableView.sectionIndexBackgroundColor = UIColor.clear // Prevent content form going under the navigation bar
self.edgesForExtendedLayout = []
// Auto size cells for dynamic type // Auto size cells for dynamic type
tableView.estimatedRowHeight = 60.0 tableView.estimatedRowHeight = 60.0
@ -78,7 +81,7 @@ open class ContactsPicker: UITableViewController, UISearchResultsUpdating, UISea
registerContactCell() registerContactCell()
initializeBarButtons() initializeBarButtons()
reloadContacts() reloadContacts()
initializeSearchBar() updateSearchResults(searchText: "")
NotificationCenter.default.addObserver(self, selector: #selector(self.didChangePreferredContentSize), name: NSNotification.Name.UIContentSizeCategoryDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.didChangePreferredContentSize), name: NSNotification.Name.UIContentSizeCategoryDidChange, object: nil)
} }
@ -86,20 +89,6 @@ open class ContactsPicker: UITableViewController, UISearchResultsUpdating, UISea
func didChangePreferredContentSize() { func didChangePreferredContentSize() {
self.tableView.reloadData() self.tableView.reloadData()
} }
func initializeSearchBar() {
self.resultSearchController = ( {
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
controller.searchBar.delegate = self
controller.hidesNavigationBarDuringPresentation = false
self.tableView.tableHeaderView = controller.searchBar
return controller
})()
}
func initializeBarButtons() { func initializeBarButtons() {
let cancelButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.cancel, target: self, action: #selector(onTouchCancelButton)) let cancelButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.cancel, target: self, action: #selector(onTouchCancelButton))
@ -117,9 +106,9 @@ open class ContactsPicker: UITableViewController, UISearchResultsUpdating, UISea
// MARK: - Initializers // MARK: - Initializers
override init(style: UITableViewStyle) { init() {
contactsManager = Environment.getCurrent().contactsManager contactsManager = Environment.getCurrent().contactsManager
super.init(style: style) super.init(nibName: nil, bundle: nil)
} }
required public init?(coder aDecoder: NSCoder) { required public init?(coder aDecoder: NSCoder) {
@ -132,13 +121,13 @@ open class ContactsPicker: UITableViewController, UISearchResultsUpdating, UISea
} }
convenience public init(delegate: ContactsPickerDelegate?, multiSelection : Bool) { convenience public init(delegate: ContactsPickerDelegate?, multiSelection : Bool) {
self.init(style: .plain) self.init()
multiSelectEnabled = multiSelection multiSelectEnabled = multiSelection
contactsPickerDelegate = delegate contactsPickerDelegate = delegate
} }
convenience public init(delegate: ContactsPickerDelegate?, multiSelection : Bool, subtitleCellType: SubtitleCellValue) { convenience public init(delegate: ContactsPickerDelegate?, multiSelection : Bool, subtitleCellType: SubtitleCellValue) {
self.init(style: .plain) self.init()
multiSelectEnabled = multiSelection multiSelectEnabled = multiSelection
contactsPickerDelegate = delegate contactsPickerDelegate = delegate
subtitleCellValue = subtitleCellType subtitleCellValue = subtitleCellType
@ -192,7 +181,6 @@ open class ContactsPicker: UITableViewController, UISearchResultsUpdating, UISea
contacts.append(contact) contacts.append(contact)
} }
self.sections = collatedContacts(contacts) self.sections = collatedContacts(contacts)
self.tableView.reloadData()
} catch let error as NSError { } catch let error as NSError {
Logger.error("\(self.TAG) Failed to fetch contacts with error:\(error)") Logger.error("\(self.TAG) Failed to fetch contacts with error:\(error)")
} }
@ -213,22 +201,22 @@ open class ContactsPicker: UITableViewController, UISearchResultsUpdating, UISea
// MARK: - Table View DataSource // MARK: - Table View DataSource
override open func numberOfSections(in tableView: UITableView) -> Int { open func numberOfSections(in tableView: UITableView) -> Int {
return self.collation.sectionTitles.count return self.collation.sectionTitles.count
} }
override open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let dataSource = resultSearchController.isActive ? filteredSections : sections let dataSource = filteredSections
return dataSource[section].count return dataSource[section].count
} }
// MARK: - Table View Delegates // MARK: - Table View Delegates
override open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: contactCellReuseIdentifier, for: indexPath) as! ContactCell let cell = tableView.dequeueReusableCell(withIdentifier: contactCellReuseIdentifier, for: indexPath) as! ContactCell
let dataSource = resultSearchController.isActive ? filteredSections : sections let dataSource = filteredSections
let cnContact = dataSource[indexPath.section][indexPath.row] let cnContact = dataSource[indexPath.section][indexPath.row]
let contact = Contact(contact: cnContact) let contact = Contact(contact: cnContact)
@ -247,7 +235,7 @@ open class ContactsPicker: UITableViewController, UISearchResultsUpdating, UISea
return cell return cell
} }
override open func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { open func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! ContactCell let cell = tableView.cellForRow(at: indexPath) as! ContactCell
let deselectedContact = cell.contact! let deselectedContact = cell.contact!
@ -256,7 +244,7 @@ open class ContactsPicker: UITableViewController, UISearchResultsUpdating, UISea
} }
} }
override open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! ContactCell let cell = tableView.cellForRow(at: indexPath) as! ContactCell
let selectedContact = cell.contact! let selectedContact = cell.contact!
@ -269,23 +257,22 @@ open class ContactsPicker: UITableViewController, UISearchResultsUpdating, UISea
if !multiSelectEnabled { if !multiSelectEnabled {
//Single selection code //Single selection code
resultSearchController.isActive = false
self.dismiss(animated: true) { self.dismiss(animated: true) {
self.contactsPickerDelegate?.contactsPicker(self, didSelectContact: selectedContact) self.contactsPickerDelegate?.contactsPicker(self, didSelectContact: selectedContact)
} }
} }
} }
override open func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int { open func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
return collation.section(forSectionIndexTitle: index) return collation.section(forSectionIndexTitle: index)
} }
override open func sectionIndexTitles(for tableView: UITableView) -> [String]? { open func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return collation.sectionIndexTitles return collation.sectionIndexTitles
} }
override open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let dataSource = resultSearchController.isActive ? filteredSections : sections let dataSource = filteredSections
if dataSource[section].count > 0 { if dataSource[section].count > 0 {
return collation.sectionTitles[section] return collation.sectionTitles[section]
@ -307,33 +294,25 @@ open class ContactsPicker: UITableViewController, UISearchResultsUpdating, UISea
} }
// MARK: - Search Actions // MARK: - Search Actions
open func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
updateSearchResults(searchText: searchText)
}
open func updateSearchResults(for searchController: UISearchController) { open func updateSearchResults(searchText: String) {
if let searchText = resultSearchController.searchBar.text , searchController.isActive { let predicate: NSPredicate
if searchText.characters.count == 0 {
let predicate: NSPredicate filteredSections = sections
if searchText.characters.count == 0 { } else {
filteredSections = sections do {
} else { predicate = CNContact.predicateForContacts(matchingName: searchText)
do { let filteredContacts = try contactStore.unifiedContacts(matching: predicate,keysToFetch: allowedContactKeys)
predicate = CNContact.predicateForContacts(matchingName: searchText)
let filteredContacts = try contactStore.unifiedContacts(matching: predicate, keysToFetch: allowedContactKeys)
filteredSections = collatedContacts(filteredContacts) filteredSections = collatedContacts(filteredContacts)
} catch let error as NSError { } catch let error as NSError {
Logger.error("\(self.TAG) updating search results failed with error: \(error)") Logger.error("\(self.TAG) updating search results failed with error: \(error)")
}
} }
self.tableView.reloadData()
} }
self.tableView.reloadData()
} }
open func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
} }
@available(iOS 9.0, *) @available(iOS 9.0, *)

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="11201" systemVersion="15G1004" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11161"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ContactsPicker" customModule="Signal" customModuleProvider="target">
<connections>
<outlet property="searchBar" destination="4gV-1B-8Mf" id="QBY-fF-wiP"/>
<outlet property="tableView" destination="oaw-nZ-Bd3" id="ovH-CY-TEZ"/>
<outlet property="view" destination="iN0-l3-epB" id="SV2-9h-0H0"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="64" width="375" height="603"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="oaw-nZ-Bd3">
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<connections>
<outlet property="dataSource" destination="-1" id="2Ke-sU-HF0"/>
<outlet property="delegate" destination="-1" id="yc6-lh-qbW"/>
</connections>
</tableView>
<searchBar contentMode="redraw" translatesAutoresizingMaskIntoConstraints="NO" id="4gV-1B-8Mf">
<constraints>
<constraint firstAttribute="height" constant="44" id="fOA-ib-HG5"/>
</constraints>
<textInputTraits key="textInputTraits"/>
<connections>
<outlet property="delegate" destination="-1" id="xC3-pC-pNH"/>
</connections>
</searchBar>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="oaw-nZ-Bd3" firstAttribute="top" secondItem="4gV-1B-8Mf" secondAttribute="bottom" id="6xt-kc-7P8"/>
<constraint firstItem="4gV-1B-8Mf" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="EXL-NQ-glZ"/>
<constraint firstItem="4gV-1B-8Mf" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="Yep-bF-mvk"/>
<constraint firstItem="oaw-nZ-Bd3" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="oKu-gT-NC6"/>
<constraint firstAttribute="bottom" secondItem="oaw-nZ-Bd3" secondAttribute="bottom" id="ptG-2s-ieJ"/>
<constraint firstAttribute="trailing" secondItem="oaw-nZ-Bd3" secondAttribute="trailing" id="sB4-4w-vGM"/>
<constraint firstAttribute="trailing" secondItem="4gV-1B-8Mf" secondAttribute="trailing" id="w2q-bS-FII"/>
</constraints>
<simulatedStatusBarMetrics key="simulatedStatusBarMetrics"/>
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" translucent="NO" prompted="NO"/>
<point key="canvasLocation" x="-209.5" y="88.5"/>
</view>
</objects>
</document>

View file

@ -364,6 +364,9 @@
/* Text for button at the top of the contact picker */ /* Text for button at the top of the contact picker */
"INVITE_FRIENDS_CONTACT_TABLE_BUTTON" = "Invite Friends to Signal"; "INVITE_FRIENDS_CONTACT_TABLE_BUTTON" = "Invite Friends to Signal";
/* Search */
"INVITE_FRIENDS_PICKER_SEARCHBAR_PLACEHOLDER" = "Search";
/* Navbar title */ /* Navbar title */
"INVITE_FRIENDS_PICKER_TITLE" = "Invite Friends"; "INVITE_FRIENDS_PICKER_TITLE" = "Invite Friends";