Skip to content

Instantly share code, notes, and snippets.

@irace
Last active November 29, 2017 19:35
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save irace/fef067935c4d4f73e91a4b7250005204 to your computer and use it in GitHub Desktop.
Save irace/fef067935c4d4f73e91a4b7250005204 to your computer and use it in GitHub Desktop.
UISearchController replacement. Relies on a couple of internal categories, helpers, etc.
//
// 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