123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620 |
- //
- // IQKeyboardReturnKeyHandler.swift
- // https://github.com/hackiftekhar/IQKeyboardManager
- // Copyright (c) 2013-16 Iftekhar Qurashi.
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- import Foundation
- import UIKit
- private class IQTextFieldViewInfoModal: NSObject {
- fileprivate weak var textFieldDelegate: UITextFieldDelegate?
- fileprivate weak var textViewDelegate: UITextViewDelegate?
- fileprivate weak var textFieldView: UIView?
- fileprivate var originalReturnKeyType = UIReturnKeyType.default
-
- init(textFieldView: UIView?, textFieldDelegate: UITextFieldDelegate?, textViewDelegate: UITextViewDelegate?, originalReturnKeyType: UIReturnKeyType = .default) {
- self.textFieldView = textFieldView
- self.textFieldDelegate = textFieldDelegate
- self.textViewDelegate = textViewDelegate
- self.originalReturnKeyType = originalReturnKeyType
- }
- }
- /**
- Manages the return key to work like next/done in a view hierarchy.
- */
- public class IQKeyboardReturnKeyHandler: NSObject, UITextFieldDelegate, UITextViewDelegate {
- ///---------------
- /// MARK: Settings
- ///---------------
-
- /**
- Delegate of textField/textView.
- */
- @objc public weak var delegate: (UITextFieldDelegate & UITextViewDelegate)?
-
- /**
- Set the last textfield return key type. Default is UIReturnKeyDefault.
- */
- @objc public var lastTextFieldReturnKeyType: UIReturnKeyType = UIReturnKeyType.default {
-
- didSet {
-
- for modal in textFieldInfoCache {
-
- if let view = modal.textFieldView {
- updateReturnKeyTypeOnTextField(view)
- }
- }
- }
- }
-
- ///--------------------------------------
- /// MARK: Initialization/Deinitialization
- ///--------------------------------------
- @objc public override init() {
- super.init()
- }
-
- /**
- Add all the textFields available in UIViewController's view.
- */
- @objc public init(controller: UIViewController) {
- super.init()
-
- addResponderFromView(controller.view)
- }
- deinit {
-
- for modal in textFieldInfoCache {
-
- if let textField = modal.textFieldView as? UITextField {
- textField.returnKeyType = modal.originalReturnKeyType
-
- textField.delegate = modal.textFieldDelegate
- } else if let textView = modal.textFieldView as? UITextView {
- textView.returnKeyType = modal.originalReturnKeyType
- textView.delegate = modal.textViewDelegate
- }
- }
-
- textFieldInfoCache.removeAll()
- }
- ///------------------------
- /// MARK: Private variables
- ///------------------------
- private var textFieldInfoCache = [IQTextFieldViewInfoModal]()
-
- ///------------------------
- /// MARK: Private Functions
- ///------------------------
- private func textFieldViewCachedInfo(_ textField: UIView) -> IQTextFieldViewInfoModal? {
-
- for modal in textFieldInfoCache {
-
- if let view = modal.textFieldView {
- if view == textField {
- return modal
- }
- }
- }
-
- return nil
- }
- private func updateReturnKeyTypeOnTextField(_ view: UIView) {
- var superConsideredView: UIView?
-
- //If find any consider responderView in it's upper hierarchy then will get deepResponderView. (Bug ID: #347)
- for disabledClass in IQKeyboardManager.shared.toolbarPreviousNextAllowedClasses {
-
- superConsideredView = view.superviewOfClassType(disabledClass)
-
- if superConsideredView != nil {
- break
- }
- }
- var textFields = [UIView]()
-
- //If there is a tableView in view's hierarchy, then fetching all it's subview that responds.
- if let unwrappedTableView = superConsideredView { // (Enhancement ID: #22)
- textFields = unwrappedTableView.deepResponderViews()
- } else { //Otherwise fetching all the siblings
-
- textFields = view.responderSiblings()
-
- //Sorting textFields according to behaviour
- switch IQKeyboardManager.shared.toolbarManageBehaviour {
- //If needs to sort it by tag
- case .byTag: textFields = textFields.sortedArrayByTag()
- //If needs to sort it by Position
- case .byPosition: textFields = textFields.sortedArrayByPosition()
- default: break
- }
- }
-
- if let lastView = textFields.last {
-
- if let textField = view as? UITextField {
-
- //If it's the last textField in responder view, else next
- textField.returnKeyType = (view == lastView) ? lastTextFieldReturnKeyType: UIReturnKeyType.next
- } else if let textView = view as? UITextView {
-
- //If it's the last textField in responder view, else next
- textView.returnKeyType = (view == lastView) ? lastTextFieldReturnKeyType: UIReturnKeyType.next
- }
- }
- }
- ///----------------------------------------------
- /// MARK: Registering/Unregistering textFieldView
- ///----------------------------------------------
- /**
- Should pass UITextField/UITextView intance. Assign textFieldView delegate to self, change it's returnKeyType.
-
- @param view UITextField/UITextView object to register.
- */
- @objc public func addTextFieldView(_ view: UIView) {
-
- let modal = IQTextFieldViewInfoModal(textFieldView: view, textFieldDelegate: nil, textViewDelegate: nil)
-
- if let textField = view as? UITextField {
-
- modal.originalReturnKeyType = textField.returnKeyType
- modal.textFieldDelegate = textField.delegate
- textField.delegate = self
-
- } else if let textView = view as? UITextView {
- modal.originalReturnKeyType = textView.returnKeyType
- modal.textViewDelegate = textView.delegate
- textView.delegate = self
- }
-
- textFieldInfoCache.append(modal)
- }
-
- /**
- Should pass UITextField/UITextView intance. Restore it's textFieldView delegate and it's returnKeyType.
-
- @param view UITextField/UITextView object to unregister.
- */
- @objc public func removeTextFieldView(_ view: UIView) {
-
- if let modal = textFieldViewCachedInfo(view) {
-
- if let textField = view as? UITextField {
- textField.returnKeyType = modal.originalReturnKeyType
- textField.delegate = modal.textFieldDelegate
- } else if let textView = view as? UITextView {
- textView.returnKeyType = modal.originalReturnKeyType
- textView.delegate = modal.textViewDelegate
- }
-
- if let index = textFieldInfoCache.firstIndex(where: { $0.textFieldView == view}) {
- textFieldInfoCache.remove(at: index)
- }
- }
- }
-
- /**
- Add all the UITextField/UITextView responderView's.
-
- @param view UIView object to register all it's responder subviews.
- */
- @objc public func addResponderFromView(_ view: UIView) {
-
- let textFields = view.deepResponderViews()
-
- for textField in textFields {
-
- addTextFieldView(textField)
- }
- }
-
- /**
- Remove all the UITextField/UITextView responderView's.
-
- @param view UIView object to unregister all it's responder subviews.
- */
- @objc public func removeResponderFromView(_ view: UIView) {
-
- let textFields = view.deepResponderViews()
-
- for textField in textFields {
-
- removeTextFieldView(textField)
- }
- }
-
- @discardableResult private func goToNextResponderOrResign(_ view: UIView) -> Bool {
-
- var superConsideredView: UIView?
-
- //If find any consider responderView in it's upper hierarchy then will get deepResponderView. (Bug ID: #347)
- for disabledClass in IQKeyboardManager.shared.toolbarPreviousNextAllowedClasses {
-
- superConsideredView = view.superviewOfClassType(disabledClass)
-
- if superConsideredView != nil {
- break
- }
- }
-
- var textFields = [UIView]()
-
- //If there is a tableView in view's hierarchy, then fetching all it's subview that responds.
- if let unwrappedTableView = superConsideredView { // (Enhancement ID: #22)
- textFields = unwrappedTableView.deepResponderViews()
- } else { //Otherwise fetching all the siblings
-
- textFields = view.responderSiblings()
-
- //Sorting textFields according to behaviour
- switch IQKeyboardManager.shared.toolbarManageBehaviour {
- //If needs to sort it by tag
- case .byTag: textFields = textFields.sortedArrayByTag()
- //If needs to sort it by Position
- case .byPosition: textFields = textFields.sortedArrayByPosition()
- default:
- break
- }
- }
- //Getting index of current textField.
- if let index = textFields.firstIndex(of: view) {
- //If it is not last textField. then it's next object becomeFirstResponder.
- if index < (textFields.count - 1) {
-
- let nextTextField = textFields[index+1]
- nextTextField.becomeFirstResponder()
- return false
- } else {
-
- view.resignFirstResponder()
- return true
- }
- } else {
- return true
- }
- }
- ///---------------------------------------
- /// MARK: UITextField/UITextView delegates
- ///---------------------------------------
-
- @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
-
- if delegate == nil {
-
- if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate {
- if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldBeginEditing(_:))) {
- return unwrapDelegate.textFieldShouldBeginEditing?(textField) == true
- }
- }
- }
-
- return true
- }
-
- @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
-
- if delegate == nil {
-
- if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate {
- if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldEndEditing(_:))) {
- return unwrapDelegate.textFieldShouldEndEditing?(textField) == true
- }
- }
- }
-
- return true
- }
-
- @objc public func textFieldDidBeginEditing(_ textField: UITextField) {
- updateReturnKeyTypeOnTextField(textField)
-
- var aDelegate: UITextFieldDelegate? = delegate
-
- if aDelegate == nil {
-
- if let modal = textFieldViewCachedInfo(textField) {
- aDelegate = modal.textFieldDelegate
- }
- }
-
- aDelegate?.textFieldDidBeginEditing?(textField)
- }
-
- @objc public func textFieldDidEndEditing(_ textField: UITextField) {
-
- var aDelegate: UITextFieldDelegate? = delegate
-
- if aDelegate == nil {
-
- if let modal = textFieldViewCachedInfo(textField) {
- aDelegate = modal.textFieldDelegate
- }
- }
-
- aDelegate?.textFieldDidEndEditing?(textField)
- }
-
- #if swift(>=4.2)
- @available(iOS 10.0, *)
- @objc public func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {
-
- var aDelegate: UITextFieldDelegate? = delegate
-
- if aDelegate == nil {
-
- if let modal = textFieldViewCachedInfo(textField) {
- aDelegate = modal.textFieldDelegate
- }
- }
-
- aDelegate?.textFieldDidEndEditing?(textField, reason: reason)
- }
- #else
- @available(iOS 10.0, *)
- @objc public func textFieldDidEndEditing(_ textField: UITextField, reason: UITextFieldDidEndEditingReason) {
- var aDelegate: UITextFieldDelegate? = delegate
-
- if aDelegate == nil {
-
- if let modal = textFieldViewCachedInfo(textField) {
- aDelegate = modal.textFieldDelegate
- }
- }
-
- aDelegate?.textFieldDidEndEditing?(textField, reason: reason)
- }
- #endif
- @objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
-
- if delegate == nil {
-
- if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate {
- if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textField(_:shouldChangeCharactersIn:replacementString:))) {
- return unwrapDelegate.textField?(textField, shouldChangeCharactersIn: range, replacementString: string) == true
- }
- }
- }
- return true
- }
-
- @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool {
-
- if delegate == nil {
-
- if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate {
- if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldClear(_:))) {
- return unwrapDelegate.textFieldShouldClear?(textField) == true
- }
- }
- }
- return true
- }
- @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
-
- var shouldReturn = true
- if delegate == nil {
-
- if let unwrapDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate {
- if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldReturn(_:))) {
- shouldReturn = unwrapDelegate.textFieldShouldReturn?(textField) == true
- }
- }
- }
- if shouldReturn == true {
- goToNextResponderOrResign(textField)
- return true
- } else {
- return goToNextResponderOrResign(textField)
- }
- }
- @objc public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
-
- if delegate == nil {
-
- if let unwrapDelegate = textFieldViewCachedInfo(textView)?.textViewDelegate {
- if unwrapDelegate.responds(to: #selector(UITextViewDelegate.textViewShouldBeginEditing(_:))) {
- return unwrapDelegate.textViewShouldBeginEditing?(textView) == true
- }
- }
- }
-
- return true
- }
-
- @objc public func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
-
- if delegate == nil {
-
- if let unwrapDelegate = textFieldViewCachedInfo(textView)?.textViewDelegate {
- if unwrapDelegate.responds(to: #selector(UITextViewDelegate.textViewShouldEndEditing(_:))) {
- return unwrapDelegate.textViewShouldEndEditing?(textView) == true
- }
- }
- }
-
- return true
- }
-
- @objc public func textViewDidBeginEditing(_ textView: UITextView) {
- updateReturnKeyTypeOnTextField(textView)
-
- var aDelegate: UITextViewDelegate? = delegate
-
- if aDelegate == nil {
-
- if let modal = textFieldViewCachedInfo(textView) {
- aDelegate = modal.textViewDelegate
- }
- }
-
- aDelegate?.textViewDidBeginEditing?(textView)
- }
-
- @objc public func textViewDidEndEditing(_ textView: UITextView) {
-
- var aDelegate: UITextViewDelegate? = delegate
-
- if aDelegate == nil {
-
- if let modal = textFieldViewCachedInfo(textView) {
- aDelegate = modal.textViewDelegate
- }
- }
-
- aDelegate?.textViewDidEndEditing?(textView)
- }
-
- @objc public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
-
- var shouldReturn = true
-
- if delegate == nil {
-
- if let unwrapDelegate = textFieldViewCachedInfo(textView)?.textViewDelegate {
- if unwrapDelegate.responds(to: #selector(UITextViewDelegate.textView(_:shouldChangeTextIn:replacementText:))) {
- shouldReturn = (unwrapDelegate.textView?(textView, shouldChangeTextIn: range, replacementText: text)) == true
- }
- }
- }
-
- if shouldReturn == true && text == "\n" {
- shouldReturn = goToNextResponderOrResign(textView)
- }
-
- return shouldReturn
- }
-
- @objc public func textViewDidChange(_ textView: UITextView) {
-
- var aDelegate: UITextViewDelegate? = delegate
-
- if aDelegate == nil {
-
- if let modal = textFieldViewCachedInfo(textView) {
- aDelegate = modal.textViewDelegate
- }
- }
-
- aDelegate?.textViewDidChange?(textView)
- }
-
- @objc public func textViewDidChangeSelection(_ textView: UITextView) {
-
- var aDelegate: UITextViewDelegate? = delegate
-
- if aDelegate == nil {
-
- if let modal = textFieldViewCachedInfo(textView) {
- aDelegate = modal.textViewDelegate
- }
- }
-
- aDelegate?.textViewDidChangeSelection?(textView)
- }
-
- @available(iOS 10.0, *)
- @objc public func textView(_ aTextView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
-
- if delegate == nil {
-
- if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate {
- if unwrapDelegate.responds(to: #selector(textView as (UITextView, URL, NSRange, UITextItemInteraction) -> Bool)) {
- return unwrapDelegate.textView?(aTextView, shouldInteractWith: URL, in: characterRange, interaction: interaction) == true
- }
- }
- }
-
- return true
- }
-
- @available(iOS 10.0, *)
- @objc public func textView(_ aTextView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
-
- if delegate == nil {
-
- if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate {
- if unwrapDelegate.responds(to: #selector(textView as (UITextView, NSTextAttachment, NSRange, UITextItemInteraction) -> Bool)) {
- return unwrapDelegate.textView?(aTextView, shouldInteractWith: textAttachment, in: characterRange, interaction: interaction) == true
- }
- }
- }
-
- return true
- }
-
- @available(iOS, deprecated: 10.0)
- @objc public func textView(_ aTextView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
-
- if delegate == nil {
-
- if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate {
- if unwrapDelegate.responds(to: #selector(textView as (UITextView, URL, NSRange) -> Bool)) {
- return unwrapDelegate.textView?(aTextView, shouldInteractWith: URL, in: characterRange) == true
- }
- }
- }
-
- return true
- }
-
- @available(iOS, deprecated: 10.0)
- @objc public func textView(_ aTextView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange) -> Bool {
-
- if delegate == nil {
-
- if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate {
- if unwrapDelegate.responds(to: #selector(textView as (UITextView, NSTextAttachment, NSRange) -> Bool)) {
- return unwrapDelegate.textView?(aTextView, shouldInteractWith: textAttachment, in: characterRange) == true
- }
- }
- }
-
- return true
- }
- }
|