Skip to content

Commit

Permalink
Add toggle for multiple select and removable tags
Browse files Browse the repository at this point in the history
  • Loading branch information
obstructer committed Jan 6, 2019
1 parent 71c6534 commit 72a33ef
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 33 deletions.
6 changes: 3 additions & 3 deletions Source/BackspaceDetectingTextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@ protocol BackspaceDetectingTextFieldDelegate: UITextFieldDelegate {
func textFieldDidDeleteBackwards(_ textField: UITextField)
}

class BackspaceDetectingTextField: UITextField {
open class BackspaceDetectingTextField: UITextField {

var onDeleteBackwards: (() -> Void)?

init() {
super.init(frame: CGRect.zero)
}

required init?(coder aDecoder: NSCoder) {
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func deleteBackward() {
override open func deleteBackward() {
onDeleteBackwards?()
// Call super afterwards. The `text` property will return text prior to the delete.
super.deleteBackward()
Expand Down
20 changes: 13 additions & 7 deletions Source/WSTagView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import UIKit

open class WSTagView: UIView {
fileprivate let textLabel = UILabel()
let textLabel = UILabel()

open var displayText: String = "" {
didSet {
Expand Down Expand Up @@ -78,15 +78,20 @@ open class WSTagView: UIView {

open var selected: Bool = false {
didSet {
if selected && !isFirstResponder {
_ = becomeFirstResponder()
} else
if !selected && isFirstResponder {
_ = resignFirstResponder()
if !allowsMultipleSelection {
if selected && !isFirstResponder {
_ = becomeFirstResponder()
} else
if !selected && isFirstResponder {
_ = resignFirstResponder()
}
}
updateContent(animated: true)
}
}

open var allowsMultipleSelection: Bool = false
open var removable: Bool = true

public init(tag: WSTag) {
super.init(frame: CGRect.zero)
Expand All @@ -106,6 +111,7 @@ open class WSTagView: UIView {

self.displayText = tag.text
updateLabelText()
textLabel.translatesAutoresizingMaskIntoConstraints = false

let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGestureRecognizer))
addGestureRecognizer(tapRecognizer)
Expand Down Expand Up @@ -208,7 +214,7 @@ open class WSTagView: UIView {

// MARK: - Gesture Recognizers
@objc func handleTapGestureRecognizer(_ sender: UITapGestureRecognizer) {
if selected {
if selected && !allowsMultipleSelection {
return
}
onDidRequestSelection?(self)
Expand Down
94 changes: 71 additions & 23 deletions Source/WSTagsField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,16 @@ open class WSTagsField: UIScrollView {
return false
}

open var allowsMultipleSelection: Bool = false {
didSet {
tagViews.forEach { $0.allowsMultipleSelection = allowsMultipleSelection }
}
}

open var autoSelectTagWhenAdded: Bool = false

open fileprivate(set) var tags = [WSTag]()
internal var tagViews = [WSTagView]()
open var tagViews = [WSTagView]()

// MARK: - Events

Expand Down Expand Up @@ -317,15 +325,21 @@ open class WSTagsField: UIScrollView {

open func beginEditing() {
self.textField.becomeFirstResponder()
self.unselectAllTagViewsAnimated(false)
if !allowsMultipleSelection {
self.unselectAllTagViewsAnimated(false)
}
}

open func endEditing() {
// NOTE: We used to check if .isFirstResponder and then resign first responder, but sometimes we noticed
// that it would be the first responder, but still return isFirstResponder=NO.
// NOTE: We used to check if .isFirstResponder and then resign first responder, but sometimes we noticed
// that it would be the first responder, but still return isFirstResponder=NO.
// So always attempt to resign without checking.
self.textField.resignFirstResponder()
}

open func addInputAccessoryView(view: UIView) {
textField.inputAccessoryView = view
}

// MARK: - Adding / Removing Tags
open func addTags(_ tags: [String]) {
Expand Down Expand Up @@ -362,15 +376,16 @@ open class WSTagsField: UIScrollView {
tagView.borderColor = self.borderColor
tagView.keyboardAppearanceType = self.keyboardAppearance
tagView.layoutMargins = self.layoutMargins
tagView.allowsMultipleSelection = allowsMultipleSelection

tagView.onDidRequestSelection = { [weak self] tagView in
self?.selectTagView(tagView, animated: true)
self?.toggleTagView(tagView, animated: true)
}

tagView.onDidRequestDelete = { [weak self] tagView, replacementText in
// First, refocus the text field
self?.textField.becomeFirstResponder()
if (replacementText?.isEmpty ?? false) == false {
if replacementText?.isEmpty == false {
self?.textField.text = replacementText
}
// Then remove the view from our data
Expand Down Expand Up @@ -401,6 +416,15 @@ open class WSTagsField: UIScrollView {
repositionViews()
}

open func setRemovable(tags: [String], removable: Bool = false) {
assert(tagViews.count > 0, "There are no tagViews. Did you call this method after adding tags?")
tagViews.filter { tags.contains($0.textLabel.text ?? "") }.forEach { $0.removable = removable }
}

open func getSelectedTagStrings() -> [String] {
return tagViews.filter { $0.selected }.compactMap { $0.textLabel.text }
}

open func removeTag(_ tag: String) {
removeTag(WSTag(tag))
}
Expand All @@ -415,6 +439,10 @@ open class WSTagsField: UIScrollView {
if index < 0 || index >= self.tags.count { return }

let tagView = self.tagViews[index]
if !tagView.removable {
return
}

tagView.removeFromSuperview()
self.tagViews.remove(at: index)

Expand All @@ -435,7 +463,18 @@ open class WSTagsField: UIScrollView {
let text = self.textField.text?.trimmingCharacters(in: CharacterSet.whitespaces) ?? ""
if text.isEmpty == false && (onVerifyTag?(self, text) ?? true) {
let tag = WSTag(text)
if self.tags.contains(tag) {
self.textField.text = ""
return nil
}

addTag(tag)
if let tagView = tagViews.last, autoSelectTagWhenAdded {
//There's a bug that causes the text to be truncated during animation ("New York" becomes "New Y..."). This delays animating the TagView until after it's set in the TextField.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
self.toggleTagView(tagView, animated: false)
}
}

self.textField.text = ""
onTextFieldDidChange(self.textField)
Expand Down Expand Up @@ -477,23 +516,25 @@ open class WSTagsField: UIScrollView {
}
}

open func selectTagView(_ tagView: WSTagView, animated: Bool = false) {
open func toggleTagView(_ tagView: WSTagView, animated: Bool = false) {
if self.readOnly {
return
}

if tagView.selected {
tagView.onDidRequestDelete?(tagView, nil)
return

tagView.selected = !tagView.selected

if !allowsMultipleSelection {
tagViews.filter { $0 != tagView }.forEach {
$0.selected = false
onDidUnselectTagView?(self, $0)
}
}

tagView.selected = true
tagViews.filter { $0 != tagView }.forEach {
$0.selected = false
onDidUnselectTagView?(self, $0)
if tagView.selected {
onDidSelectTagView?(self, tagView)
} else {
onDidUnselectTagView?(self, tagView)
}

onDidSelectTagView?(self, tagView)
}

open func unselectAllTagViewsAnimated(_ animated: Bool = false) {
Expand Down Expand Up @@ -592,10 +633,15 @@ extension WSTagsField {
}

textField.onDeleteBackwards = { [weak self] in
if self?.readOnly ?? true { return }
if self?.readOnly == true { return }

if self?.allowsMultipleSelection == true, self?.textField.text?.isEmpty == true, let lastTag = self?.tags.last {
self?.removeTag(lastTag)
return
}

if self?.textField.text?.isEmpty ?? true, let tagView = self?.tagViews.last {
self?.selectTagView(tagView, animated: true)
if self?.textField.text?.isEmpty == true, let tagView = self?.tagViews.last {
self?.toggleTagView(tagView, animated: true)
self?.textField.resignFirstResponder()
}
}
Expand Down Expand Up @@ -709,7 +755,7 @@ extension WSTagsField {
oldIntrinsicContentHeight = newIntrinsicContentHeight
}

if self.enableScrolling {
if self.enableScrolling {
self.isScrollEnabled = contentRect.height + contentInset.top + contentInset.bottom >= newIntrinsicContentHeight
}
self.contentSize.width = self.bounds.width - contentInset.left - contentInset.right
Expand Down Expand Up @@ -746,7 +792,9 @@ extension WSTagsField: UITextFieldDelegate {

public func textFieldDidBeginEditing(_ textField: UITextField) {
textDelegate?.textFieldDidBeginEditing?(textField)
unselectAllTagViewsAnimated(true)
if !allowsMultipleSelection {
unselectAllTagViewsAnimated(true)
}
}

public func textFieldDidEndEditing(_ textField: UITextField) {
Expand Down

0 comments on commit 72a33ef

Please sign in to comment.