Last active
November 29, 2017 19:35
-
-
Save irace/fef067935c4d4f73e91a4b7250005204 to your computer and use it in GitHub Desktop.
UISearchController replacement. Relies on a couple of internal categories, helpers, etc.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// SearchController.swift | |
// Prefer | |
// | |
// Created by Bryan Irace on 5/31/17. | |
// Copyright © 2017 Prefer. All rights reserved. | |
// | |
import RxSwift | |
import RxCocoa | |
import SharedUI | |
protocol SearchResultsUpdater: class { | |
func updateSearchResults(for query: String?) | |
func returnedFromSearch(with query: String?) | |
} | |
/** | |
An alternative to `UISearchController`. `UISearchController` is inflexible and seems to have lots of bugs. So, reinvent | |
the wheel we shall. | |
*/ | |
final class SearchController { | |
// MARK: - Subviews | |
private lazy var dimmedView: UIView = DimmedView().usingAutoLayout() | |
// MARK: - Mutable | |
/** | |
Not passed into the initializer since in many cases, this will be the object that owns the search controller, | |
meaning the search controller will be lazily-instantiated. | |
*/ | |
weak var searchResultsUpdater: SearchResultsUpdater? | |
// MARK: - Inputs | |
private let searchResultsViewController: UIViewController | |
private unowned let textField: UITextField | |
private unowned let parentViewController: UIViewController | |
private unowned let containingScrollView: UIScrollView | |
// MARK: - Initialization | |
/// Create a new instance | |
/// | |
/// - Parameters: | |
/// - searchResultsViewController: View controller to display results in | |
/// - textField: Text field used to drive searches | |
/// - parentViewController: View controller to be the parent of the search results view controller | |
/// - containingScrollView: Scroll view that the text field is contained by (e.g. a table that the tet field it is | |
/// the header of). Scrolling will be disabled when the results are being shown. | |
init( | |
searchResultsViewController: UIViewController, | |
textField: UITextField, | |
parentViewController: UIViewController, | |
containingScrollView: UIScrollView | |
) { | |
self.searchResultsViewController = searchResultsViewController | |
self.textField = textField | |
self.parentViewController = parentViewController | |
self.containingScrollView = containingScrollView | |
setUpObservers() | |
} | |
// MARK: - Private | |
private func setUpObservers() { | |
_ = textField | |
.rx | |
.text | |
.distinctUntilChanged({ (lhs, rhs) -> Bool in | |
lhs == rhs | |
}) | |
.subscribe(onNext: { [weak self] query in | |
self?.searchResultsUpdater?.updateSearchResults(for: query) | |
}) | |
_ = Observable | |
.combineLatest( | |
textField.rx.hasText, | |
textField.rx.isEditing | |
) | |
.map({ (hasQuery, isEditing) in | |
hasQuery || isEditing | |
}) | |
.distinctUntilChanged() | |
.subscribe(onNext: { [weak self] showResults in | |
if showResults { | |
self?.showSearchResultsViewController() | |
} | |
else { | |
self?.hideSearchResultsViewController() | |
} | |
}) | |
_ = textField | |
.rx | |
.controlEvent(.editingDidEndOnExit) | |
.subscribe(onNext: { [weak self] in | |
self?.searchResultsUpdater?.returnedFromSearch(with: self?.textField.text) | |
}) | |
} | |
private func showSearchResultsViewController() { | |
containingScrollView.isScrollEnabled = false | |
parentViewController.view.addSubview(dimmedView) | |
pinToParent(dimmedView) | |
dimmedView.hideAndFadeIn() | |
parentViewController.addChildViewController(searchResultsViewController) | |
parentViewController.view.addSubview(searchResultsViewController.view) | |
searchResultsViewController.didMove(toParentViewController: parentViewController) | |
searchResultsViewController.view.translatesAutoresizingMaskIntoConstraints = false | |
pinToParent(searchResultsViewController.view) | |
} | |
private func pinToParent(_ view: UIView) { | |
NSLayoutConstraint.activate([ | |
view.topAnchor.constraint(equalTo: textField.bottomAnchor), | |
view.leftAnchor.constraint(equalTo: parentViewController.view.leftAnchor), | |
view.bottomAnchor.constraint(equalTo: parentViewController.bottomLayoutGuide.topAnchor), | |
view.rightAnchor.constraint(equalTo: parentViewController.view.rightAnchor), | |
]) | |
} | |
private func hideSearchResultsViewController() { | |
dimmedView.removeFromSuperview() | |
searchResultsViewController.willMove(toParentViewController: nil) | |
searchResultsViewController.view.removeFromSuperview() | |
searchResultsViewController.removeFromParentViewController() | |
containingScrollView.isScrollEnabled = true | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment