Skip to content

Instantly share code, notes, and snippets.

@phlippieb
Last active August 24, 2016 06:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save phlippieb/3be2b00104d8cd86304badf20b45cfbe to your computer and use it in GitHub Desktop.
Save phlippieb/3be2b00104d8cd86304badf20b45cfbe to your computer and use it in GitHub Desktop.
A swift autocomplete solution. I needed this to subclass a specific textview class (from the Material pod, if you're interested), but it should work fine with UITextView. I also used RxSwift to capture text changes, which can probably be done with the text view's delegate methods, though it might be harder.
//
// AutoCompleteTextField.swift
//
// Created by Phlippie Bosman on 2016/08/23.
// Copyright © 2016 Kalido. All rights reserved.
//
import Material
import RxSwift
@objc protocol AutoCompleteProvider {
// for async fetching
func provideSuggestionsForAutoCompleteTextField(textField: AutoCompleteTextField, forString string: String, toCallback callback: ([String]) -> Void)
// for dismissing async fetch
optional func disposeFetchForAutoCompleteTextField(textField: AutoCompleteTextField)
}
class AutoCompleteTextField: ErrorTextField, UITableViewDelegate, UITableViewDataSource {
// public interface
var autocompleteDelay: NSTimeInterval = 0.5
var autoCompleteProvider: AutoCompleteProvider?
var autoCompleteFont: UIFont = RobotoFont.lightWithSize(12)
var autoCompleteTextColor: UIColor = MaterialColor.black
var autoCompleteBackgroundColor: UIColor = MaterialColor.white
var autoCompleteSeparatorsEnabled: Bool = false
var autoCompleteMaxHeight: CGFloat = 500
// b/c my tableview got stuck behind other subviews of this textview's superview
// set this to the textview's superview (might work with getter property?)
var viewToBringAutoCompleteToFront: UIView?
// ui
private let completionsTableView = UITableView(forAutoLayout: ())
private var tableViewHeightConstraint: NSLayoutConstraint?
// data
private var autoCompleteSuggestionStrings: [String] = []
private let reuseIdentifier = "cell"
private let disposeBag = DisposeBag()
// call this! in like viewDidLoad or something
func setupAutoComplete() {
completionsTableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: reuseIdentifier)
completionsTableView.separatorStyle = autoCompleteSeparatorsEnabled ? .SingleLine : .None
completionsTableView.backgroundColor = autoCompleteBackgroundColor
completionsTableView.delegate = self
completionsTableView.dataSource = self
self.rx_text
.skip(1) // an event always fires at startup and it is useless
.doOnNext { [weak self] (text) in
if text == "" {
self?.dismissAutoCompleteSuggestions()
}
if let this = self,
let provider = this.autoCompleteProvider,
let dispose = provider.disposeFetchForAutoCompleteTextField {
dispose(this)
}
}
.throttle(autocompleteDelay, scheduler: MainScheduler.instance)
.subscribeNext { [weak self] (text) in
if text == "" {
self?.dismissAutoCompleteSuggestions()
} else if let this = self
where this.isFirstResponder(),
let provider = this.autoCompleteProvider {
provider.provideSuggestionsForAutoCompleteTextField(this, forString: text, toCallback: this.presentAutoCompleteSuggestions)
}
}
.addDisposableTo(disposeBag)
addSubview(completionsTableView)
tableViewHeightConstraint = completionsTableView.autoSetDimension(.Height, toSize: 20)
completionsTableView.autoMatchDimension(.Width, toDimension: .Width, ofView: self)
completionsTableView.autoPinEdge(.Top, toEdge: .Bottom, ofView: self, withOffset: 8)
completionsTableView.autoPinEdgeToSuperviewEdge(.Left, withInset: 0)
completionsTableView.userInteractionEnabled = true
adjustTableviewHeight()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return autoCompleteSuggestionStrings.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .Default, reuseIdentifier: reuseIdentifier)
cell.backgroundColor = autoCompleteBackgroundColor
let suggestion = autoCompleteSuggestionStrings[indexPath.row]
if let label = cell.textLabel {
label.text = suggestion
label.font = autoCompleteFont
label.textColor = autoCompleteTextColor
}
cell.addGestureRecognizer(
UITapGestureRecognizer(target: self, action: #selector(didSelectSuggestion(_:))))
cell.userInteractionEnabled = true
return cell
}
func presentAutoCompleteSuggestions(suggestions: [String]) {
autoCompleteSuggestionStrings = suggestions
completionsTableView.reloadData()
adjustTableviewHeight()
viewToBringAutoCompleteToFront?.bringSubviewToFront(self)
}
private func adjustTableviewHeight() {
if let heightConstraint = tableViewHeightConstraint {
let h = min(completionsTableView.contentSize.height, autoCompleteMaxHeight)
UIView.animateWithDuration(0.3) {
heightConstraint.constant = h
self.setNeedsUpdateConstraints()
}
}
}
func didSelectSuggestion(sender: AnyObject!) {
guard let tap = sender as? UIGestureRecognizer,
let cell = tap.view as? UITableViewCell,
let cellLabel = cell.textLabel,
let suggestion = cellLabel.text
else { return }
text = suggestion
dismissAutoCompleteSuggestions()
}
func dismissAutoCompleteSuggestions() {
autoCompleteSuggestionStrings = []
completionsTableView.reloadData()
adjustTableviewHeight()
}
/*
This allows the suggestion cells to capture touch events
even though the table view is entirely outside the text view's frame
*/
override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
return super.pointInside(point, withEvent: event) ||
completionsTableView.pointInside(
convertPoint(point, toView: completionsTableView),
withEvent: event)
}
override func resignFirstResponder() -> Bool {
dismissAutoCompleteSuggestions()
return super.resignFirstResponder()
}
}
@phlippieb
Copy link
Author

Pods used:
Material
RxSwift

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