Skip to content

Instantly share code, notes, and snippets.

@bithavoc
Last active April 23, 2024 13:32
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bithavoc/f5c9d06f024bfb5d9d3bd9452b94849f to your computer and use it in GitHub Desktop.
Save bithavoc/f5c9d06f024bfb5d9d3bd9452b94849f to your computer and use it in GitHub Desktop.
Eureka Swift 3 Composable Search Row
open class _ComposableSearchablePushRow<T: Equatable, Cell: CellType> : TableSelectorRow<Cell, ComposableSearchableViewController<T>> where Cell: BaseCell, Cell: TypedCellType, Cell.Value == ComposableSearchableItem<T>, T: SearchableItem, T: CustomStringConvertible {
public required init(tag: String?) {
super.init(tag: tag)
onCreateControllerCallback = { [weak self] _ in
let controller = ComposableSearchableViewController<T>()
controller.searchPlaceholder = self?.searchPlaceholder
return controller
}
}
var searchPlaceholder: String?
}
/// Selector Controller (used to select one option among a list)
open class ComposableSearchableViewController<T:Equatable> : _ComposableSearchableViewController<T, ListCheckRow<ComposableSearchableItem<T>>, T> where T:SearchableItem, T: CustomStringConvertible {
}
open class _ComposableSearchableViewController<T: Equatable, Row: SelectableRowType, TOriginal:Equatable> : UITableViewController, UISearchResultsUpdating, TypedRowControllerType where Row: BaseRow, Row: TypedRowType, Row.Cell.Value == ComposableSearchableItem<T>, T: SearchableItem, T: CustomStringConvertible, TOriginal: SearchableItem, TOriginal: CustomStringConvertible {
/// A closure to be called when the controller disappears.
public var onDismissCallback: ((UIViewController) -> ())?
open var row: RowOf<Row.Cell.Value>!
let searchController = UISearchController(searchResultsController: nil)
required public init() {
super.init(style: .grouped)
self.navigationItem.titleView = self.searchController.searchBar
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController.hidesNavigationBarDuringPresentation = false
self.definesPresentationContext = true
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var originalOptions = [ComposableSearchableItem<T>]()
var currentOptions = [ComposableSearchableItem<T>]()
var searchPlaceholder: String?
open override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
searchController.searchBar.placeholder = searchPlaceholder
//tableView!.tableHeaderView = searchController.searchBar
if let options = row.dataProvider?.arrayData {
self.originalOptions = options
self.currentOptions = options
}
self.tableView.reloadData()
if let composableItem = row.value {
switch composableItem {
case .composedQuery(let query):
searchController.searchBar.text = query
case .existingItem:
if let index = currentOptions.index(of: composableItem) {
let indexPath = IndexPath(index: index)
self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: .top)
}
}
}
}
fileprivate func filter(_ query: String) {
if query == "" {
currentOptions = self.originalOptions
} else {
currentOptions = self.originalOptions.filter{ $0.matchesSearchQuery(query) }
if currentOptions.isEmpty {
currentOptions.append(ComposableSearchableItem<T>.composedQuery(query))
}
}
self.tableView.reloadData()
}
open override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
open override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.row?.title
}
open override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return currentOptions.count
}
open override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let option = self.currentOptions[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
switch(option) {
case .composedQuery(let query):
let text = NSMutableAttributedString()
text.append(NSAttributedString(string: "Otro: ", attributes: [NSFontAttributeName: UIFont.boldSystemFont(ofSize: UIFont.labelFontSize)]))
text.append(NSAttributedString(string: query))
cell.textLabel?.attributedText = text
case .existingItem(let item):
cell.textLabel?.text = item.description
}
return cell
}
open override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let option = self.currentOptions[indexPath.row]
row.value = option
onDismissCallback?(self)
}
open func updateSearchResults(for searchController: UISearchController) {
filter(searchController.searchBar.text!)
}
}
public final class ComposableSearchablePushRow<T: Equatable> : _ComposableSearchablePushRow<T, PushSelectorCell<ComposableSearchableItem<T>>>, RowType where T: SearchableItem, T: CustomStringConvertible {
public required init(tag: String?) {
super.init(tag: tag)
}
}
public enum ComposableSearchableItem<T:Equatable> : Equatable, CustomStringConvertible, SearchableItem where T: SearchableItem, T: CustomStringConvertible {
case existingItem(T)
case composedQuery(String)
public var description: String {
switch(self) {
case .existingItem(let item):
return item.description
case .composedQuery(let query):
return query
}
}
public func matchesSearchQuery(_ query: String) -> Bool {
switch(self) {
case .existingItem(let item):
return item.matchesSearchQuery(query)
case .composedQuery(let q):
return q == query
}
}
}
public func ==<T>(lhs: ComposableSearchableItem<T>, rhs: ComposableSearchableItem<T>) -> Bool {
switch (lhs, rhs) {
case let (.existingItem(l), .existingItem(r)):
return l == r
case let(.composedQuery(l), .composedQuery(r)):
return l == r
default:
return false
}
}
/// Generic row type where a user must select a value among several options.
open class TableSelectorRow<Cell: CellType, VCType: TypedRowControllerType>: OptionsRow<Cell> where Cell: BaseCell, VCType: UITableViewController, VCType.RowValue == Cell.Value {
open var onCreateControllerCallback : ((FormViewController)->(VCType))?
required public init(tag: String?) {
super.init(tag: tag)
}
/**
Extends `didSelect` method
*/
open override func customDidSelect() {
super.customDidSelect()
if isDisabled {
return
}
guard let createController = onCreateControllerCallback else {
return
}
let controller = createController(cell.formViewController()!)
prepareSelector(controller: controller)
let formViewController = cell.formViewController()!
formViewController.show(controller, sender: nil)
}
/**
Prepares the pushed row setting its title and completion callback.
*/
open override func prepare(for segue: UIStoryboardSegue) {
super.prepare(for: segue)
guard let controller = segue.destination as? VCType else { return }
prepareSelector(controller: controller)
}
fileprivate func prepareSelector(controller: VCType) {
controller.row = self
controller.title = selectorTitle ?? controller.title
controller.onDismissCallback = {vc in
_ = vc.navigationController?.popViewController(animated: true)
}
}
}
public protocol SearchableItem {
func matchesSearchQuery(_ query: String) -> Bool
}
@allaire
Copy link

allaire commented Feb 24, 2017

@bithavoc Getting a lot of errors using Swift 3 and lastest Eureka release, any cues?

Thanks!

2017-02-24 at 08 14

EDIT: Solved, I had a BaseCell class in my project as well :(

@d-neri
Copy link

d-neri commented Mar 8, 2017

I'm having some trouble getting this to work - can you give me some pointers on how to set options to a type of ComposableSearchableItem? Thanks!

@calli23
Copy link

calli23 commented Mar 12, 2017

Can you give me some evidence how to call ComposableSearchablePushRow in my existing FormViewController? In search I'm looking for a specific String value in an given string array.

@fictorial
Copy link

String doesn't adhere to SearchableItem so you need to wrap your String in something that does, say X.

        <<< ComposableSearchablePushRow<X>() {
            $0.title = "X"
            $0.options = sequenceOfX.map {
                return ComposableSearchableItem<X>.existingItem($0)
            }

@fictorial
Copy link

Seeing this warning which I haven't dived into yet: "Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior"

@maxhanglin
Copy link

maxhanglin commented Apr 13, 2017

Guys, if anyone interested, I just modified this gist to follow the simpler SearchablePushRow found in the original post from the author:
http://bithavoc.io/blog/2016/07/04/eureka-search-push-row/

Code:
https://gist.github.com/maxhanglin/032033405a2717a4cdd445bf5190b65f

It can be used as:

<<< SearchablePushRow<Contact>("person") { row in
    row.options = contactsList
}

Contact, in this example, needs to be:

public class Contact: CustomStringConvertible, Equatable, SearchableItem { ... }

@rhalbrook
Copy link

I am kind of new to using Eureka forms...Do you have any of you have any working examples that you could offer, for download, to see this in action?

@noefroidevaux
Copy link

I made another version of SearchPushRow which works with Eureka 3 and Swift 3.1:

https://gist.github.com/noefroidevaux/d033e4fb4382f0dcfc877fa4e6ba95a2

In this version, I simply subclasses _SelectorViewController and add the UISearchController.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment