IQKeyboardManagerSwift/ReturnKeyHandler/IQKeyboardReturnKeyHandler.swift
//
// IQKeyboardReturnKeyHandler.swift
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-24 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 UIKit
/**
Manages the return key to work like next/done in a view hierarchy.
*/
@available(iOSApplicationExtension, unavailable)
@MainActor
@objc public final class IQKeyboardReturnKeyHandler: NSObject {
// 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 model in textFieldInfoCache {
if let view: UIView = model.textFieldView {
updateReturnKeyTypeOnTextField(view)
}
}
}
}
// MARK: Initialization/De-initialization
@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 model in textFieldInfoCache {
// model.restore()
// }
textFieldInfoCache.removeAll()
}
// MARK: Private variables
private var textFieldInfoCache: [IQTextFieldViewInfoModel] = []
// MARK: Private Functions
internal func textFieldViewCachedInfo(_ textField: UIView) -> IQTextFieldViewInfoModel? {
for model in textFieldInfoCache {
if let view: UIView = model.textFieldView {
if view == textField {
return model
}
}
}
return nil
}
internal 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 allowedClasse in IQKeyboardManager.shared.toolbarPreviousNextAllowedClasses {
superConsideredView = view.iq.superviewOf(type: allowedClasse)
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: UIView = superConsideredView { // (Enhancement ID: #22)
textFields = unwrappedTableView.iq.deepResponderViews()
} else { // Otherwise fetching all the siblings
textFields = view.iq.responderSiblings()
// Sorting textFields according to behavior
switch IQKeyboardManager.shared.toolbarConfiguration.manageBehavior {
// If needs to sort it by tag
case .byTag: textFields = textFields.sortedByTag()
// If needs to sort it by Position
case .byPosition: textFields = textFields.sortedByPosition()
default: break
}
}
if let lastView: UIView = textFields.last {
if let textField: UITextField = 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: UITextView = 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 instance. Assign textFieldView delegate to self, change it's returnKeyType.
@param view UITextField/UITextView object to register.
*/
@objc public func addTextFieldView(_ view: UIView) {
if let textField: UITextField = view as? UITextField {
let model = IQTextFieldViewInfoModel(textField: textField)
textFieldInfoCache.append(model)
textField.delegate = self
} else if let textView: UITextView = view as? UITextView {
let model = IQTextFieldViewInfoModel(textView: textView)
textFieldInfoCache.append(model)
textView.delegate = self
}
}
/**
Should pass UITextField/UITextView instance. Restore it's textFieldView delegate and it's returnKeyType.
@param view UITextField/UITextView object to unregister.
*/
@objc public func removeTextFieldView(_ view: UIView) {
if let model: IQTextFieldViewInfoModel = textFieldViewCachedInfo(view) {
model.restore()
if let index: Int = 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: [UIView] = view.iq.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: [UIView] = view.iq.deepResponderViews()
for textField in textFields {
removeTextFieldView(textField)
}
}
@discardableResult
internal 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 allowedClass in IQKeyboardManager.shared.toolbarPreviousNextAllowedClasses {
superConsideredView = view.iq.superviewOf(type: allowedClass)
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: UIView = superConsideredView { // (Enhancement ID: #22)
textFields = unwrappedTableView.iq.deepResponderViews()
} else { // Otherwise fetching all the siblings
textFields = view.iq.responderSiblings()
// Sorting textFields according to behavior
switch IQKeyboardManager.shared.toolbarConfiguration.manageBehavior {
// If needs to sort it by tag
case .byTag: textFields = textFields.sortedByTag()
// If needs to sort it by Position
case .byPosition: textFields = textFields.sortedByPosition()
default:
break
}
}
// Getting index of current textField.
if let index: Int = textFields.firstIndex(of: view) {
// If it is not last textField. then it's next object becomeFirstResponder.
if index < (textFields.count - 1) {
let nextTextField: UIView = textFields[index+1]
nextTextField.becomeFirstResponder()
return false
} else {
view.resignFirstResponder()
return true
}
} else {
return true
}
}
}