From 9febb727b93b858ef285ad0e8c44fbdb7bdd8a78 Mon Sep 17 00:00:00 2001 From: hackiftekhar Date: Fri, 2 Aug 2024 19:26:03 +0530 Subject: [PATCH] - Added IQTextInputView - Improved IQReturnKeyHandler --- IQKeyboardManagerSwift.podspec.json | 1 - .../IQKeyboardCore/IQTextInputView.swift | 86 ++++++ .../Configuration/IQActiveConfiguration.swift | 9 +- .../IQKeyboardManager+Internal.swift | 8 +- .../IQKeyboardManager/IQKeyboardManager.swift | 19 +- .../IQUIView+IQKeyboardToolbar.swift | 57 ++-- .../IQKeyboardToolbarManager.swift | 7 +- .../IQPreviousNextView.swift | 5 + .../Toolbar/IQKeyboardManager+Toolbar.swift | 3 +- .../IQKeyboardManager+ToolbarActions.swift | 6 +- .../IQTextFieldViewInfo.swift | 6 +- .../IQTextFieldViewListener.swift | 36 +-- ...rdReturnKeyHandler+TextFieldDelegate.swift | 71 +++-- ...ardReturnKeyHandler+TextViewDelegate.swift | 232 +++++++++------ .../IQKeyboardReturnKeyHandler.swift | 272 ++++++++---------- .../IQTextFieldViewInfoModel.swift | 33 +-- 16 files changed, 456 insertions(+), 395 deletions(-) create mode 100644 IQKeyboardManagerSwift/IQKeyboardCore/IQTextInputView.swift diff --git a/IQKeyboardManagerSwift.podspec.json b/IQKeyboardManagerSwift.podspec.json index 1da750de..6160381b 100644 --- a/IQKeyboardManagerSwift.podspec.json +++ b/IQKeyboardManagerSwift.podspec.json @@ -16,7 +16,6 @@ "ios": "13.0" }, "swift_versions": [ - "5.6", "5.7", "5.8", "5.9" diff --git a/IQKeyboardManagerSwift/IQKeyboardCore/IQTextInputView.swift b/IQKeyboardManagerSwift/IQKeyboardCore/IQTextInputView.swift new file mode 100644 index 00000000..3b909e08 --- /dev/null +++ b/IQKeyboardManagerSwift/IQKeyboardCore/IQTextInputView.swift @@ -0,0 +1,86 @@ +// +// IQTextInputView.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 + +@available(iOSApplicationExtension, unavailable) +@MainActor +@objc public protocol IQTextInputView where Self: UIView, Self: UITextInputTraits { + + @available(iOS 16.0, *) + @objc var iqIsFindInteractionEnabled: Bool { get } + + @available(iOS 16.0, *) + @objc var iqFindInteraction: UIFindInteraction? { get } + + @objc var returnKeyType: UIReturnKeyType { get set } + @objc var keyboardAppearance: UIKeyboardAppearance { get set } + + @objc var iqIsEnabled: Bool { get } + + @objc var inputAccessoryView: UIView? { get set } +} + +@available(iOSApplicationExtension, unavailable) +@MainActor +@objc extension UITextField: IQTextInputView { + + @available(iOS 16.0, *) + @objc public var iqIsFindInteractionEnabled: Bool { false } + + @available(iOS 16.0, *) + @objc public var iqFindInteraction: UIFindInteraction? { nil } + + @objc public var iqIsEnabled: Bool { isEnabled } +} + +@available(iOSApplicationExtension, unavailable) +@MainActor +@objc extension UITextView: IQTextInputView { + @available(iOS 16.0, *) + public var iqIsFindInteractionEnabled: Bool { isFindInteractionEnabled } + + @available(iOS 16.0, *) + public var iqFindInteraction: UIFindInteraction? { findInteraction } + + public var iqIsEnabled: Bool { isEditable } +} + +@available(iOSApplicationExtension, unavailable) +@MainActor +@objc extension UISearchBar: IQTextInputView { + + @available(iOS 16.0, *) + @objc public var iqIsFindInteractionEnabled: Bool { false } + + @available(iOS 16.0, *) + @objc public var iqFindInteraction: UIFindInteraction? { nil } + + public var iqIsEnabled: Bool { + if #available(iOS 16.4, *) { + return isEnabled + } else { + return searchTextField.isEnabled + } + } +} diff --git a/IQKeyboardManagerSwift/IQKeyboardManager/Configuration/IQActiveConfiguration.swift b/IQKeyboardManagerSwift/IQKeyboardManager/Configuration/IQActiveConfiguration.swift index 1f7c9381..cad2f9d2 100644 --- a/IQKeyboardManagerSwift/IQKeyboardManager/Configuration/IQActiveConfiguration.swift +++ b/IQKeyboardManagerSwift/IQKeyboardManager/Configuration/IQActiveConfiguration.swift @@ -61,7 +61,7 @@ import UIKit return false } - @objc public override init() { + override init() { super.init() addKeyboardListener() addTextFieldViewListener() @@ -93,7 +93,7 @@ import UIKit private func updateRootController(info: IQTextFieldViewInfo?) { guard let info = info, - let controller: UIViewController = info.textFieldView.iq.parentContainerViewController() else { + let controller: UIViewController = (info.textFieldView as UIView).iq.parentContainerViewController() else { if let rootControllerConfiguration = rootControllerConfiguration, rootControllerConfiguration.hasChanged { animate(alongsideTransition: { @@ -164,7 +164,8 @@ extension IQActiveConfiguration { extension IQActiveConfiguration { var textFieldViewInfo: IQTextFieldViewInfo? { - guard textFieldViewListener.textFieldView?.iq.isAlertViewTextField() == false else { + guard let textFieldView: UIView = textFieldViewListener.textFieldView, + textFieldView.iq.isAlertViewTextField() == false else { return nil } @@ -175,7 +176,7 @@ extension IQActiveConfiguration { textFieldViewListener.registerTextFieldViewChange(identifier: "IQActiveConfiguration", changeHandler: { [self] info in - guard info.textFieldView.iq.isAlertViewTextField() == false else { + guard (info.textFieldView as UIView).iq.isAlertViewTextField() == false else { return } diff --git a/IQKeyboardManagerSwift/IQKeyboardManager/IQKeyboardManager+Internal.swift b/IQKeyboardManagerSwift/IQKeyboardManager/IQKeyboardManager+Internal.swift index 014427e6..e7b31f02 100644 --- a/IQKeyboardManagerSwift/IQKeyboardManager/IQKeyboardManager+Internal.swift +++ b/IQKeyboardManagerSwift/IQKeyboardManager/IQKeyboardManager+Internal.swift @@ -30,20 +30,20 @@ internal extension IQKeyboardManager { var isEnabled: Bool = enable - guard let textFieldViewInfo: IQTextFieldViewInfo = activeConfiguration.textFieldViewInfo else { + guard let textFieldView: UIView = activeConfiguration.textFieldViewInfo?.textFieldView else { return isEnabled } - let enableMode: IQEnableMode = textFieldViewInfo.textFieldView.iq.enableMode + let enableMode: IQEnableMode = textFieldView.iq.enableMode if enableMode == .enabled { isEnabled = true } else if enableMode == .disabled { isEnabled = false - } else if var textFieldViewController = textFieldViewInfo.textFieldView.iq.viewContainingController() { + } else if var textFieldViewController = textFieldView.iq.viewContainingController() { // If it is searchBar textField embedded in Navigation Bar - if textFieldViewInfo.textFieldView.iq.textFieldSearchBar() != nil, + if textFieldView.iq.textFieldSearchBar() != nil, let navController: UINavigationController = textFieldViewController as? UINavigationController, let topController: UIViewController = navController.topViewController { textFieldViewController = topController diff --git a/IQKeyboardManagerSwift/IQKeyboardManager/IQKeyboardManager.swift b/IQKeyboardManagerSwift/IQKeyboardManager/IQKeyboardManager.swift index e01a1ad5..02fe3366 100644 --- a/IQKeyboardManagerSwift/IQKeyboardManager/IQKeyboardManager.swift +++ b/IQKeyboardManagerSwift/IQKeyboardManager/IQKeyboardManager.swift @@ -47,6 +47,15 @@ Code-less drop-in universal library allows to prevent issues of keyboard sliding @MainActor @objc public static let shared: IQKeyboardManager = .init() + @objc internal let toolbarManager: IQKeyboardToolbarManager = .init() + + @objc internal let resignHandler: IQKeyboardResignHandler = .init() + + @objc internal let appearanceManager: IQKeyboardAppearanceManager = .init() + + @objc internal var activeConfiguration: IQActiveConfiguration = .init() + + // MARK: UIKeyboard handling /** @@ -77,16 +86,6 @@ Code-less drop-in universal library allows to prevent issues of keyboard sliding */ @objc public var keyboardDistanceFromTextField: CGFloat = 10.0 - // MARK: IQToolbar handling - - @objc internal let toolbarManager: IQKeyboardToolbarManager = .init() - - @objc internal let resignHandler: IQKeyboardResignHandler = .init() - - @objc internal let appearanceManager: IQKeyboardAppearanceManager = .init() - - internal var activeConfiguration: IQActiveConfiguration = .init() - /*******************************************/ // MARK: UIAnimation handling diff --git a/IQKeyboardManagerSwift/IQKeyboardToolbar/IQKeyboardExtension/IQUIView+IQKeyboardToolbar.swift b/IQKeyboardManagerSwift/IQKeyboardToolbar/IQKeyboardExtension/IQUIView+IQKeyboardToolbar.swift index d9f6389e..05d8a53d 100644 --- a/IQKeyboardManagerSwift/IQKeyboardToolbar/IQKeyboardExtension/IQUIView+IQKeyboardToolbar.swift +++ b/IQKeyboardManagerSwift/IQKeyboardToolbar/IQKeyboardExtension/IQUIView+IQKeyboardToolbar.swift @@ -33,7 +33,7 @@ private struct AssociatedKeys { @available(iOSApplicationExtension, unavailable) @MainActor -public extension IQKeyboardManagerWrapper where Base: UIView { +public extension IQKeyboardManagerWrapper where Base: IQTextInputView { // MARK: Toolbar @@ -111,20 +111,20 @@ public extension IQKeyboardManagerWrapper where Base: UIView { */ var drawingPlaceholder: String? { - if hidePlaceholder { - return nil - } else if placeholder?.isEmpty == false { + guard !hidePlaceholder else { return nil } + + if let placeholder = placeholder, + !placeholder.isEmpty { + return placeholder + } + + guard let placeholderable: any IQPlaceholderable = base as? (any IQPlaceholderable) else { return nil } + + if let placeholder = placeholderable.attributedPlaceholder?.string, + !placeholder.isEmpty { + return placeholder + } else if let placeholder = placeholderable.placeholder { return placeholder - } else if let placeholderable: any IQPlaceholderable = base as? (any IQPlaceholderable) { - - if let placeholder = placeholderable.attributedPlaceholder?.string, - !placeholder.isEmpty { - return placeholder - } else if let placeholder = placeholderable.placeholder { - return placeholder - } else { - return nil - } } else { return nil } @@ -132,8 +132,6 @@ public extension IQKeyboardManagerWrapper where Base: UIView { // MARK: Common - // swiftlint:disable cyclomatic_complexity - // swiftlint:disable function_body_length func addToolbar(target: AnyObject?, previousConfiguration: IQBarButtonItemConfiguration? = nil, nextConfiguration: IQBarButtonItemConfiguration? = nil, @@ -142,7 +140,8 @@ public extension IQKeyboardManagerWrapper where Base: UIView { titleAccessibilityLabel: String? = nil) { // If can't set InputAccessoryView. Then return - if base?.responds(to: #selector(setter: UITextField.inputAccessoryView)) == true { + if base?.responds(to: #selector(setter: UITextField.inputAccessoryView)) == true, + let base = base { // Creating a toolBar for phoneNumber keyboard let toolbar: IQToolbar = toolbar @@ -204,29 +203,21 @@ public extension IQKeyboardManagerWrapper where Base: UIView { // Adding button to toolBar. toolbar.items = items - if let textInput: any UITextInput = base as? (any UITextInput) { - switch textInput.keyboardAppearance { - case .dark?: - toolbar.barStyle = .black - default: - toolbar.barStyle = .default - } + switch base.keyboardAppearance { + case .dark: + toolbar.barStyle = .black + default: + toolbar.barStyle = .default } // Setting toolbar to keyboard. - let reloadInputViews: Bool = base?.inputAccessoryView != toolbar + let reloadInputViews: Bool = base.inputAccessoryView != toolbar if reloadInputViews { - if let textField: UITextField = base as? UITextField { - textField.inputAccessoryView = toolbar - } else if let textView: UITextView = base as? UITextView { - textView.inputAccessoryView = toolbar - } - base?.reloadInputViews() + base.inputAccessoryView = toolbar + base.reloadInputViews() } } } - // swiftlint:enable function_body_length - // swiftlint:enable cyclomatic_complexity // MARK: Right func addDone(target: AnyObject?, diff --git a/IQKeyboardManagerSwift/IQKeyboardToolbarManager/IQKeyboardToolbarManager.swift b/IQKeyboardManagerSwift/IQKeyboardToolbarManager/IQKeyboardToolbarManager.swift index 71ca46b1..e1e4a4d5 100644 --- a/IQKeyboardManagerSwift/IQKeyboardToolbarManager/IQKeyboardToolbarManager.swift +++ b/IQKeyboardManagerSwift/IQKeyboardToolbarManager/IQKeyboardToolbarManager.swift @@ -74,7 +74,8 @@ import UIKit @objc var toolbarPreviousNextAllowedClasses: [UIView.Type] = [ UITableView.self, UICollectionView.self, - IQPreviousNextView.self + IQDeepResponderContainerView.self, + IQPreviousNextView.self, ] @objc public override init() { @@ -87,7 +88,7 @@ import UIKit // If you experience exception breakpoint issue at below line then try these solutions // https://stackoverflow.com/questions/27375640/all-exception-break-point-is-stopping-for-no-reason-on-simulator DispatchQueue.main.async { - let textField: UIView = UITextField() + let textField: UITextField = UITextField() textField.iq.addDone(target: nil, action: #selector(self.doneAction(_:))) textField.iq.addPreviousNextDone(target: nil, previousAction: #selector(self.previousAction(_:)), nextAction: #selector(self.nextAction(_:)), @@ -107,7 +108,7 @@ private extension IQKeyboardToolbarManager { textInputViewObserver.registerTextFieldViewChange(identifier: "TextInputViewObserverForToolbar", changeHandler: { [weak self] info in guard let self = self else { return } - guard info.textFieldView.iq.isAlertViewTextField() == false else { + guard (info.textFieldView as UIView).iq.isAlertViewTextField() == false else { return } diff --git a/IQKeyboardManagerSwift/IQKeyboardToolbarManager/IQPreviousNextView.swift b/IQKeyboardManagerSwift/IQKeyboardToolbarManager/IQPreviousNextView.swift index d841752a..24c1a862 100644 --- a/IQKeyboardManagerSwift/IQKeyboardToolbarManager/IQPreviousNextView.swift +++ b/IQKeyboardManagerSwift/IQKeyboardToolbarManager/IQPreviousNextView.swift @@ -25,5 +25,10 @@ import UIKit @available(iOSApplicationExtension, unavailable) @MainActor +@objc open class IQDeepResponderContainerView: UIView { +} + +@available(*, deprecated, renamed: "IQDeepResponderContainerView", message: "Deprecated in favor of IQDeepResponderContainerView and will be removed in future release.") +@MainActor @objc open class IQPreviousNextView: UIView { } diff --git a/IQKeyboardManagerSwift/IQKeyboardToolbarManager/Toolbar/IQKeyboardManager+Toolbar.swift b/IQKeyboardManagerSwift/IQKeyboardToolbarManager/Toolbar/IQKeyboardManager+Toolbar.swift index 3c14f7d3..98b6900e 100644 --- a/IQKeyboardManagerSwift/IQKeyboardToolbarManager/Toolbar/IQKeyboardManager+Toolbar.swift +++ b/IQKeyboardManagerSwift/IQKeyboardToolbarManager/Toolbar/IQKeyboardManager+Toolbar.swift @@ -36,13 +36,12 @@ internal extension IQKeyboardToolbarManager { /** Add toolbar if it is required to add on textFields and it's siblings. */ - func addToolbarIfRequired(of textField: UIView) { + func addToolbarIfRequired(of textField: some IQTextInputView) { // Either there is no inputAccessoryView or // if accessoryView is not appropriate for current situation // (There is Previous/Next/Done toolbar) guard let siblings: [UIView] = responderViews(of: textField), !siblings.isEmpty, - let textField: UIView = textInputViewObserver.textFieldViewInfo?.textFieldView, textField.responds(to: #selector(setter: UITextField.inputAccessoryView)) else { return } diff --git a/IQKeyboardManagerSwift/IQKeyboardToolbarManager/Toolbar/IQKeyboardManager+ToolbarActions.swift b/IQKeyboardManagerSwift/IQKeyboardToolbarManager/Toolbar/IQKeyboardManager+ToolbarActions.swift index 16f48f5a..0dd66f5f 100644 --- a/IQKeyboardManagerSwift/IQKeyboardToolbarManager/Toolbar/IQKeyboardManager+ToolbarActions.swift +++ b/IQKeyboardManagerSwift/IQKeyboardToolbarManager/Toolbar/IQKeyboardManager+ToolbarActions.swift @@ -118,7 +118,7 @@ internal extension IQKeyboardToolbarManager { // Handling search bar special case do { - if let searchBar: UIView = textFieldRetain.iq.textFieldSearchBar() { + if let searchBar: UISearchBar = textFieldRetain.iq.textFieldSearchBar() { invocation = searchBar.iq.toolbar.previousBarButton.invocation sender = searchBar } @@ -150,7 +150,7 @@ internal extension IQKeyboardToolbarManager { // Handling search bar special case do { - if let searchBar: UIView = textFieldRetain.iq.textFieldSearchBar() { + if let searchBar: UISearchBar = textFieldRetain.iq.textFieldSearchBar() { invocation = searchBar.iq.toolbar.nextBarButton.invocation sender = searchBar } @@ -182,7 +182,7 @@ internal extension IQKeyboardToolbarManager { // Handling search bar special case do { - if let searchBar: UIView = textFieldRetain.iq.textFieldSearchBar() { + if let searchBar: UISearchBar = textFieldRetain.iq.textFieldSearchBar() { invocation = searchBar.iq.toolbar.doneBarButton.invocation sender = searchBar } diff --git a/IQKeyboardManagerSwift/IQTextInputViewNotification/IQTextFieldViewInfo.swift b/IQKeyboardManagerSwift/IQTextInputViewNotification/IQTextFieldViewInfo.swift index e6ce9441..139b6e1d 100644 --- a/IQKeyboardManagerSwift/IQTextInputViewNotification/IQTextFieldViewInfo.swift +++ b/IQKeyboardManagerSwift/IQTextInputViewNotification/IQTextFieldViewInfo.swift @@ -40,10 +40,10 @@ public struct IQTextFieldViewInfo: Equatable { public let name: Name - public let textFieldView: UIView + public let textFieldView: any IQTextInputView - internal init?(notification: Notification?, name: Name) { - guard let view: UIView = notification?.object as? UIView else { + internal init?(notification: Notification, name: Name) { + guard let view: any IQTextInputView = notification.object as? (any IQTextInputView) else { return nil } diff --git a/IQKeyboardManagerSwift/IQTextInputViewNotification/IQTextFieldViewListener.swift b/IQKeyboardManagerSwift/IQTextInputViewNotification/IQTextFieldViewListener.swift index c22c2d46..7470a26d 100644 --- a/IQKeyboardManagerSwift/IQTextInputViewNotification/IQTextFieldViewListener.swift +++ b/IQKeyboardManagerSwift/IQTextInputViewNotification/IQTextFieldViewListener.swift @@ -29,13 +29,11 @@ import UIKit private var textFieldViewObservers: [AnyHashable: TextFieldViewCompletion] = [:] -#if swift(>=5.7) - private(set) var lastTextFieldViewInfo: IQTextFieldViewInfo? -#endif + private var findInteractionTextInputViewInfo: IQTextFieldViewInfo? public private(set) var textFieldViewInfo: IQTextFieldViewInfo? - public var textFieldView: UIView? { + public var textFieldView: (any IQTextInputView)? { return textFieldViewInfo?.textFieldView } @@ -59,28 +57,19 @@ import UIKit return } -#if swift(>=5.7) - if #available(iOS 16.0, *), - let lastTextFieldViewInfo = lastTextFieldViewInfo, - let textView: UITextView = lastTextFieldViewInfo.textFieldView as? UITextView, - textView.findInteraction?.isFindNavigatorVisible == true { + let findInteractionTextInputViewInfo = findInteractionTextInputViewInfo, + findInteractionTextInputViewInfo.textFieldView.iqFindInteraction?.isFindNavigatorVisible == true { // // This means the this didBeginEditing call comes due to find interaction - textFieldViewInfo = lastTextFieldViewInfo - sendEvent(info: lastTextFieldViewInfo) + textFieldViewInfo = findInteractionTextInputViewInfo + sendEvent(info: findInteractionTextInputViewInfo) } else if textFieldViewInfo != info { textFieldViewInfo = info - lastTextFieldViewInfo = nil + findInteractionTextInputViewInfo = nil sendEvent(info: info) } else { - lastTextFieldViewInfo = nil - } -#else - if textFieldViewInfo != info { - textFieldViewInfo = info - sendEvent(info: info) + findInteractionTextInputViewInfo = nil } -#endif } @objc private func didEndEditing(_ notification: Notification) { @@ -89,15 +78,12 @@ import UIKit } if textFieldViewInfo != info { -#if swift(>=5.7) if #available(iOS 16.0, *), - let textView: UITextView = info.textFieldView as? UITextView, - textView.isFindInteractionEnabled { - lastTextFieldViewInfo = textFieldViewInfo + info.textFieldView.iqIsFindInteractionEnabled { + findInteractionTextInputViewInfo = textFieldViewInfo } else { - lastTextFieldViewInfo = nil + findInteractionTextInputViewInfo = nil } -#endif textFieldViewInfo = info sendEvent(info: info) textFieldViewInfo = nil diff --git a/IQKeyboardManagerSwift/ReturnKeyHandler/Delegates/IQKeyboardReturnKeyHandler+TextFieldDelegate.swift b/IQKeyboardManagerSwift/ReturnKeyHandler/Delegates/IQKeyboardReturnKeyHandler+TextFieldDelegate.swift index bb31b037..1c255ee5 100644 --- a/IQKeyboardManagerSwift/ReturnKeyHandler/Delegates/IQKeyboardReturnKeyHandler+TextFieldDelegate.swift +++ b/IQKeyboardManagerSwift/ReturnKeyHandler/Delegates/IQKeyboardReturnKeyHandler+TextFieldDelegate.swift @@ -29,26 +29,29 @@ extension IQKeyboardReturnKeyHandler: UITextFieldDelegate { @objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { - if delegate == nil { + var returnValue: Bool = true - if let unwrapDelegate: any UITextFieldDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate { - if unwrapDelegate.responds(to: #selector((any UITextFieldDelegate).textFieldShouldBeginEditing(_:))) { - return unwrapDelegate.textFieldShouldBeginEditing?(textField) ?? false - } + if delegate == nil, + let textFieldDelegate: any UITextFieldDelegate = textInputViewCachedInfo(textField)?.textFieldDelegate { + if textFieldDelegate.responds(to: #selector((any UITextFieldDelegate).textFieldShouldBeginEditing(_:))) { + returnValue = textFieldDelegate.textFieldShouldBeginEditing?(textField) ?? false } } - return true + if returnValue { + updateReturnKey(textInputView: textField) + } + + return returnValue } @objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { - if delegate == nil { + guard delegate == nil else { return true } - if let unwrapDelegate: any UITextFieldDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate { - if unwrapDelegate.responds(to: #selector((any UITextFieldDelegate).textFieldShouldEndEditing(_:))) { - return unwrapDelegate.textFieldShouldEndEditing?(textField) ?? false - } + if let textFieldDelegate: any UITextFieldDelegate = textInputViewCachedInfo(textField)?.textFieldDelegate { + if textFieldDelegate.responds(to: #selector((any UITextFieldDelegate).textFieldShouldEndEditing(_:))) { + return textFieldDelegate.textFieldShouldEndEditing?(textField) ?? false } } @@ -56,13 +59,12 @@ extension IQKeyboardReturnKeyHandler: UITextFieldDelegate { } @objc public func textFieldDidBeginEditing(_ textField: UITextField) { - updateReturnKeyTypeOnTextField(textField) var aDelegate: (any UITextFieldDelegate)? = delegate if aDelegate == nil { - if let model: IQTextFieldViewInfoModel = textFieldViewCachedInfo(textField) { + if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textField) { aDelegate = model.textFieldDelegate } } @@ -76,7 +78,7 @@ extension IQKeyboardReturnKeyHandler: UITextFieldDelegate { if aDelegate == nil { - if let model: IQTextFieldViewInfoModel = textFieldViewCachedInfo(textField) { + if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textField) { aDelegate = model.textFieldDelegate } } @@ -90,7 +92,7 @@ extension IQKeyboardReturnKeyHandler: UITextFieldDelegate { if aDelegate == nil { - if let model: IQTextFieldViewInfoModel = textFieldViewCachedInfo(textField) { + if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textField) { aDelegate = model.textFieldDelegate } } @@ -102,16 +104,15 @@ extension IQKeyboardReturnKeyHandler: UITextFieldDelegate { shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - if delegate == nil { + guard delegate == nil else { return true } - if let unwrapDelegate: any UITextFieldDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate { - let selector: Selector = #selector((any UITextFieldDelegate).textField(_:shouldChangeCharactersIn: + if let textFieldDelegate: any UITextFieldDelegate = textInputViewCachedInfo(textField)?.textFieldDelegate { + let selector: Selector = #selector((any UITextFieldDelegate).textField(_:shouldChangeCharactersIn: replacementString:)) - if unwrapDelegate.responds(to: selector) { - return unwrapDelegate.textField?(textField, - shouldChangeCharactersIn: range, - replacementString: string) ?? false - } + if textFieldDelegate.responds(to: selector) { + return textFieldDelegate.textField?(textField, + shouldChangeCharactersIn: range, + replacementString: string) ?? false } } return true @@ -119,12 +120,11 @@ extension IQKeyboardReturnKeyHandler: UITextFieldDelegate { @objc public func textFieldShouldClear(_ textField: UITextField) -> Bool { - if delegate == nil { + guard delegate == nil else { return true } - if let unwrapDelegate: any UITextFieldDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate { - if unwrapDelegate.responds(to: #selector((any UITextFieldDelegate).textFieldShouldClear(_:))) { - return unwrapDelegate.textFieldShouldClear?(textField) ?? false - } + if let textFieldDelegate: any UITextFieldDelegate = textInputViewCachedInfo(textField)?.textFieldDelegate { + if textFieldDelegate.responds(to: #selector((any UITextFieldDelegate).textFieldShouldClear(_:))) { + return textFieldDelegate.textFieldShouldClear?(textField) ?? false } } @@ -133,22 +133,21 @@ extension IQKeyboardReturnKeyHandler: UITextFieldDelegate { @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { - var isReturn: Bool = true + guard delegate == nil else { return true } - if delegate == nil { + var isReturn: Bool = true - if let unwrapDelegate: any UITextFieldDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate { - if unwrapDelegate.responds(to: #selector((any UITextFieldDelegate).textFieldShouldReturn(_:))) { - isReturn = unwrapDelegate.textFieldShouldReturn?(textField) ?? false - } + if let textFieldDelegate: any UITextFieldDelegate = textInputViewCachedInfo(textField)?.textFieldDelegate { + if textFieldDelegate.responds(to: #selector((any UITextFieldDelegate).textFieldShouldReturn(_:))) { + isReturn = textFieldDelegate.textFieldShouldReturn?(textField) ?? false } } if isReturn { - goToNextResponderOrResign(textField) + goToNextResponderOrResign(from: textField) return true } else { - return goToNextResponderOrResign(textField) + return goToNextResponderOrResign(from: textField) } } } diff --git a/IQKeyboardManagerSwift/ReturnKeyHandler/Delegates/IQKeyboardReturnKeyHandler+TextViewDelegate.swift b/IQKeyboardManagerSwift/ReturnKeyHandler/Delegates/IQKeyboardReturnKeyHandler+TextViewDelegate.swift index 3a799dcd..f3b6da24 100644 --- a/IQKeyboardManagerSwift/ReturnKeyHandler/Delegates/IQKeyboardReturnKeyHandler+TextViewDelegate.swift +++ b/IQKeyboardManagerSwift/ReturnKeyHandler/Delegates/IQKeyboardReturnKeyHandler+TextViewDelegate.swift @@ -29,26 +29,29 @@ extension IQKeyboardReturnKeyHandler: UITextViewDelegate { @objc public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool { - if delegate == nil { + var returnValue: Bool = true - if let unwrapDelegate: any UITextViewDelegate = textFieldViewCachedInfo(textView)?.textViewDelegate { - if unwrapDelegate.responds(to: #selector((any UITextViewDelegate).textViewShouldBeginEditing(_:))) { - return unwrapDelegate.textViewShouldBeginEditing?(textView) ?? false - } + if delegate == nil, + let textViewDelegate: any UITextViewDelegate = textInputViewCachedInfo(textView)?.textViewDelegate { + if textViewDelegate.responds(to: #selector((any UITextViewDelegate).textViewShouldBeginEditing(_:))) { + returnValue = textViewDelegate.textViewShouldBeginEditing?(textView) ?? false } } - return true + if returnValue { + updateReturnKey(textInputView: textView) + } + + return returnValue } @objc public func textViewShouldEndEditing(_ textView: UITextView) -> Bool { - if delegate == nil { + guard delegate == nil else { return true } - if let unwrapDelegate: any UITextViewDelegate = textFieldViewCachedInfo(textView)?.textViewDelegate { - if unwrapDelegate.responds(to: #selector((any UITextViewDelegate).textViewShouldEndEditing(_:))) { - return unwrapDelegate.textViewShouldEndEditing?(textView) ?? false - } + if let textViewDelegate: any UITextViewDelegate = textInputViewCachedInfo(textView)?.textViewDelegate { + if textViewDelegate.responds(to: #selector((any UITextViewDelegate).textViewShouldEndEditing(_:))) { + return textViewDelegate.textViewShouldEndEditing?(textView) ?? false } } @@ -56,13 +59,12 @@ extension IQKeyboardReturnKeyHandler: UITextViewDelegate { } @objc public func textViewDidBeginEditing(_ textView: UITextView) { - updateReturnKeyTypeOnTextField(textView) var aDelegate: (any UITextViewDelegate)? = delegate if aDelegate == nil { - if let model: IQTextFieldViewInfoModel = textFieldViewCachedInfo(textView) { + if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textView) { aDelegate = model.textViewDelegate } } @@ -76,7 +78,7 @@ extension IQKeyboardReturnKeyHandler: UITextViewDelegate { if aDelegate == nil { - if let model: IQTextFieldViewInfoModel = textFieldViewCachedInfo(textView) { + if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textView) { aDelegate = model.textViewDelegate } } @@ -88,25 +90,26 @@ extension IQKeyboardReturnKeyHandler: UITextViewDelegate { shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { - var isReturn = true + var shouldChange = true if delegate == nil { - if let unwrapDelegate: any UITextViewDelegate = textFieldViewCachedInfo(textView)?.textViewDelegate { + if let textViewDelegate: any UITextViewDelegate = textInputViewCachedInfo(textView)?.textViewDelegate { let selector = #selector((any UITextViewDelegate).textView(_:shouldChangeTextIn:replacementText:)) - if unwrapDelegate.responds(to: selector) { - isReturn = (unwrapDelegate.textView?(textView, - shouldChangeTextIn: range, - replacementText: text)) ?? false + if textViewDelegate.responds(to: selector) { + shouldChange = (textViewDelegate.textView?(textView, + shouldChangeTextIn: range, + replacementText: text)) ?? true } } } - if isReturn, text == "\n" { - isReturn = goToNextResponderOrResign(textView) + if self.dismissTextViewOnReturn, text == "\n" { + goToNextResponderOrResign(from: textView) + return false } - return isReturn + return shouldChange } @objc public func textViewDidChange(_ textView: UITextView) { @@ -115,7 +118,7 @@ extension IQKeyboardReturnKeyHandler: UITextViewDelegate { if aDelegate == nil { - if let model: IQTextFieldViewInfoModel = textFieldViewCachedInfo(textView) { + if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textView) { aDelegate = model.textViewDelegate } } @@ -129,7 +132,7 @@ extension IQKeyboardReturnKeyHandler: UITextViewDelegate { if aDelegate == nil { - if let model: IQTextFieldViewInfoModel = textFieldViewCachedInfo(textView) { + if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textView) { aDelegate = model.textViewDelegate } } @@ -137,45 +140,45 @@ extension IQKeyboardReturnKeyHandler: UITextViewDelegate { aDelegate?.textViewDidChangeSelection?(textView) } + @available(iOS, deprecated: 17.0) @objc public func textView(_ aTextView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { - if delegate == nil { + guard delegate == nil else { return true } - if let unwrapDelegate: any UITextViewDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate { - let selector: Selector = #selector(textView as - (UITextView, URL, NSRange, UITextItemInteraction) -> Bool) - if unwrapDelegate.responds(to: selector) { - return unwrapDelegate.textView?(aTextView, - shouldInteractWith: URL, - in: characterRange, - interaction: interaction) ?? false - } + if let textViewDelegate: any UITextViewDelegate = textInputViewCachedInfo(aTextView)?.textViewDelegate { + let selector: Selector = #selector(textView as + (UITextView, URL, NSRange, UITextItemInteraction) -> Bool) + if textViewDelegate.responds(to: selector) { + return textViewDelegate.textView?(aTextView, + shouldInteractWith: URL, + in: characterRange, + interaction: interaction) ?? false } } return true } + @available(iOS, deprecated: 17.0) @objc public func textView(_ aTextView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { - if delegate == nil { - - if let unwrapDelegate: any UITextViewDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate { - let selector: Selector = #selector(textView as - (UITextView, NSTextAttachment, NSRange, UITextItemInteraction) - -> Bool) - if unwrapDelegate.responds(to: selector) { - return unwrapDelegate.textView?(aTextView, - shouldInteractWith: textAttachment, - in: characterRange, - interaction: interaction) ?? false - } + guard delegate == nil else { return true } + + if let textViewDelegate: any UITextViewDelegate = textInputViewCachedInfo(aTextView)?.textViewDelegate { + let selector: Selector = #selector(textView as + (UITextView, NSTextAttachment, NSRange, UITextItemInteraction) + -> Bool) + if textViewDelegate.responds(to: selector) { + return textViewDelegate.textView?(aTextView, + shouldInteractWith: textAttachment, + in: characterRange, + interaction: interaction) ?? false } } @@ -187,14 +190,13 @@ extension IQKeyboardReturnKeyHandler: UITextViewDelegate { shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool { - if delegate == nil { + guard delegate == nil else { return true } - if let unwrapDelegate: any UITextViewDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate { - if unwrapDelegate.responds(to: #selector(textView as (UITextView, URL, NSRange) -> Bool)) { - return unwrapDelegate.textView?(aTextView, - shouldInteractWith: URL, - in: characterRange) ?? false - } + if let textViewDelegate: any UITextViewDelegate = textInputViewCachedInfo(aTextView)?.textViewDelegate { + if textViewDelegate.responds(to: #selector(textView as (UITextView, URL, NSRange) -> Bool)) { + return textViewDelegate.textView?(aTextView, + shouldInteractWith: URL, + in: characterRange) ?? false } } @@ -206,14 +208,13 @@ extension IQKeyboardReturnKeyHandler: UITextViewDelegate { shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange) -> Bool { - if delegate == nil { + guard delegate == nil else { return true } - if let unwrapDelegate: any UITextViewDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate { - if unwrapDelegate.responds(to: #selector(textView as (UITextView, NSTextAttachment, NSRange) -> Bool)) { - return unwrapDelegate.textView?(aTextView, - shouldInteractWith: textAttachment, - in: characterRange) ?? false - } + if let textViewDelegate: any UITextViewDelegate = textInputViewCachedInfo(aTextView)?.textViewDelegate { + if textViewDelegate.responds(to: #selector(textView as (UITextView, NSTextAttachment, NSRange) -> Bool)) { + return textViewDelegate.textView?(aTextView, + shouldInteractWith: textAttachment, + in: characterRange) ?? false } } @@ -221,24 +222,23 @@ extension IQKeyboardReturnKeyHandler: UITextViewDelegate { } } -#if swift(>=5.7) @available(iOS 16.0, *) @available(iOSApplicationExtension, unavailable) extension IQKeyboardReturnKeyHandler { public func textView(_ aTextView: UITextView, editMenuForTextIn range: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu? { - if delegate == nil { - if let unwrapDelegate: any UITextViewDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate { + guard delegate == nil else { return nil } - let selector: Selector = #selector(textView as - (UITextView, NSRange, [UIMenuElement]) -> UIMenu?) - if unwrapDelegate.responds(to: selector) { - return unwrapDelegate.textView?(aTextView, - editMenuForTextIn: range, - suggestedActions: suggestedActions) - } + if let textViewDelegate: any UITextViewDelegate = textInputViewCachedInfo(aTextView)?.textViewDelegate { + + let selector: Selector = #selector(textView as + (UITextView, NSRange, [UIMenuElement]) -> UIMenu?) + if textViewDelegate.responds(to: selector) { + return textViewDelegate.textView?(aTextView, + editMenuForTextIn: range, + suggestedActions: suggestedActions) } } @@ -251,7 +251,7 @@ extension IQKeyboardReturnKeyHandler { if aDelegate == nil { - if let model: IQTextFieldViewInfoModel = textFieldViewCachedInfo(aTextView) { + if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(aTextView) { aDelegate = model.textViewDelegate } } @@ -265,7 +265,7 @@ extension IQKeyboardReturnKeyHandler { if aDelegate == nil { - if let model: IQTextFieldViewInfoModel = textFieldViewCachedInfo(aTextView) { + if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(aTextView) { aDelegate = model.textViewDelegate } } @@ -273,7 +273,6 @@ extension IQKeyboardReturnKeyHandler { aDelegate?.textView?(aTextView, willDismissEditMenuWith: animator) } } -#endif #if swift(>=5.9) @available(iOS 17.0, *) @@ -283,14 +282,13 @@ extension IQKeyboardReturnKeyHandler { public func textView(_ aTextView: UITextView, primaryActionFor textItem: UITextItem, defaultAction: UIAction) -> UIAction? { - if delegate == nil { + guard delegate == nil else { return nil } - if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate { - if unwrapDelegate.responds(to: #selector(textView as (UITextView, UITextItem, UIAction) -> UIAction?)) { - return unwrapDelegate.textView?(aTextView, - primaryActionFor: textItem, - defaultAction: defaultAction) - } + if let textViewDelegate = textInputViewCachedInfo(aTextView)?.textViewDelegate { + if textViewDelegate.responds(to: #selector(textView as (UITextView, UITextItem, UIAction) -> UIAction?)) { + return textViewDelegate.textView?(aTextView, + primaryActionFor: textItem, + defaultAction: defaultAction) } } @@ -300,16 +298,15 @@ extension IQKeyboardReturnKeyHandler { public func textView(_ aTextView: UITextView, menuConfigurationFor textItem: UITextItem, defaultMenu: UIMenu) -> UITextItem.MenuConfiguration? { - if delegate == nil { - - if let unwrapDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate { - let selector: Selector = #selector(textView as (UITextView, UITextItem, UIMenu) - -> UITextItem.MenuConfiguration?) - if unwrapDelegate.responds(to: selector) { - return unwrapDelegate.textView?(aTextView, - menuConfigurationFor: textItem, - defaultMenu: defaultMenu) - } + guard delegate == nil else { return nil } + + if let textViewDelegate = textInputViewCachedInfo(aTextView)?.textViewDelegate { + let selector: Selector = #selector(textView as (UITextView, UITextItem, UIMenu) + -> UITextItem.MenuConfiguration?) + if textViewDelegate.responds(to: selector) { + return textViewDelegate.textView?(aTextView, + menuConfigurationFor: textItem, + defaultMenu: defaultMenu) } } @@ -323,7 +320,7 @@ extension IQKeyboardReturnKeyHandler { if aDelegate == nil { - if let model = textFieldViewCachedInfo(textView) { + if let model = textInputViewCachedInfo(textView) { aDelegate = model.textViewDelegate } } @@ -338,7 +335,7 @@ extension IQKeyboardReturnKeyHandler { if aDelegate == nil { - if let model = textFieldViewCachedInfo(textView) { + if let model = textInputViewCachedInfo(textView) { aDelegate = model.textViewDelegate } } @@ -347,3 +344,52 @@ extension IQKeyboardReturnKeyHandler { } } #endif + +#if swift(>=6.0) // Xcode 16 +@available(iOS 18.0, *) +@available(iOSApplicationExtension, unavailable) +@MainActor +extension IQKeyboardReturnManager { + + @objc public func textViewWritingToolsWillBegin(_ textView: UITextView) { + + var aDelegate: (any UITextViewDelegate)? = delegate + + if aDelegate == nil { + + if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textView) { + aDelegate = model.textViewDelegate + } + } + + aDelegate?.textViewWritingToolsWillBegin?(textView) + } + + @objc public func textViewWritingToolsDidEnd(_ textView: UITextView) { + + var aDelegate: (any UITextViewDelegate)? = delegate + + if aDelegate == nil { + + if let model: IQTextInputViewInfoModel = textInputViewCachedInfo(textView) { + aDelegate = model.textViewDelegate + } + } + + aDelegate?.textViewWritingToolsDidEnd?(textView) + } + + @objc public func textView(_ textView: UITextView, + writingToolsIgnoredRangesInEnclosingRange enclosingRange: NSRange) -> [NSValue] { + guard delegate == nil else { return [] } + + if let textViewDelegate = textInputViewCachedInfo(aTextView)?.textViewDelegate { + if textViewDelegate.responds(to: #selector(textView as (UITextView, NSRange) -> [NSValue])) { + return textViewDelegate.textView?(aTextView, + writingToolsIgnoredRangesInEnclosingRange: enclosingRange) + } + } + return [] + } +} +#endif diff --git a/IQKeyboardManagerSwift/ReturnKeyHandler/IQKeyboardReturnKeyHandler.swift b/IQKeyboardManagerSwift/ReturnKeyHandler/IQKeyboardReturnKeyHandler.swift index 7597f27e..f9f8e12c 100644 --- a/IQKeyboardManagerSwift/ReturnKeyHandler/IQKeyboardReturnKeyHandler.swift +++ b/IQKeyboardManagerSwift/ReturnKeyHandler/IQKeyboardReturnKeyHandler.swift @@ -30,23 +30,31 @@ Manages the return key to work like next/done in a view hierarchy. @MainActor @objc public final class IQKeyboardReturnKeyHandler: NSObject { + // MARK: Private variables + private var textInputViewInfoCache: [IQTextInputViewInfoModel] = [] + // MARK: Settings /** - Delegate of textField/textView. - */ + Delegate of textField/textView. + */ @objc public weak var delegate: (any UITextFieldDelegate & UITextViewDelegate)? + @objc public var dismissTextViewOnReturn: Bool = false + /** - Set the last textfield return key type. Default is UIReturnKeyDefault. - */ + 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) + if let activeModel = textInputViewInfoCache.first(where: { + guard let textInputView = $0.textInputView else { + return false } + return textInputView.isFirstResponder + }), let view: any IQTextInputView = activeModel.textInputView { + updateReturnKey(textInputView: view) } } } @@ -58,8 +66,8 @@ Manages the return key to work like next/done in a view hierarchy. } /** - Add all the textFields available in UIViewController's view. - */ + Add all the textFields available in UIViewController's view. + */ @objc public init(controller: UIViewController) { super.init() @@ -68,223 +76,167 @@ Manages the return key to work like next/done in a view hierarchy. 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 { + // model.restore() + // } - for model in textFieldInfoCache { - - if let view: UIView = model.textFieldView { - if view == textField { - return model - } - } - } - - return nil + textInputViewInfoCache.removeAll() } +} - internal func updateReturnKeyTypeOnTextField(_ view: UIView) { - - guard let index: Int = textFieldInfoCache.firstIndex(where: { $0.textFieldView == view}) else { return } - - let isLast: Bool = index == textFieldInfoCache.count - 1 - - let returnKey: UIReturnKeyType = isLast ? lastTextFieldReturnKeyType: UIReturnKeyType.next - if let view: UITextField = view as? UITextField, view.returnKeyType != returnKey { - - // If it's the last textInputView in responder view, else next - view.returnKeyType = returnKey - view.reloadInputViews() - } else if let view: UITextView = view as? UITextView, view.returnKeyType != returnKey { - - // If it's the last textInputView in responder view, else next - view.returnKeyType = returnKey - view.reloadInputViews() - } - } +// MARK: Registering/Unregistering textFieldView - // MARK: Registering/Unregistering textFieldView +@available(iOSApplicationExtension, unavailable) +@MainActor +public extension IQKeyboardReturnKeyHandler { /** - Should pass UITextField/UITextView instance. Assign textFieldView delegate to self, change it's returnKeyType. + 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) { + @param view UITextField/UITextView object to register. + */ + @objc func addTextFieldView(_ textInputView: any IQTextInputView) { - if let textField: UITextField = view as? UITextField { - let model = IQTextFieldViewInfoModel(textField: textField) - textFieldInfoCache.append(model) - textField.delegate = self + let model = IQTextInputViewInfoModel(textInputView: textInputView) + textInputViewInfoCache.append(model) - } else if let textView: UITextView = view as? UITextView { - let model = IQTextFieldViewInfoModel(textView: textView) - textFieldInfoCache.append(model) - textView.delegate = self + if let view: UITextField = textInputView as? UITextField { + view.delegate = self + } else if let view: UITextView = textInputView as? UITextView { + view.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) { + Should pass UITextField/UITextView instance. Restore it's textFieldView delegate and it's returnKeyType. - if let model: IQTextFieldViewInfoModel = textFieldViewCachedInfo(view) { - model.restore() + @param view UITextField/UITextView object to unregister. + */ + @objc func removeTextFieldView(_ textInputView: any IQTextInputView) { - if let index: Int = textFieldInfoCache.firstIndex(where: { $0.textFieldView == view}) { - textFieldInfoCache.remove(at: index) - } - } + guard let index: Int = textInputViewCachedInfoIndex(textInputView) else { return } + + let model = textInputViewInfoCache.remove(at: index) + model.restore() } /** Add all the UITextField/UITextView responderView's. - + @param view UIView object to register all it's responder subviews. */ - @objc public func addResponderFromView(_ view: UIView, recursive: Bool = true) { + @objc func addResponderFromView(_ view: UIView, recursive: Bool = true) { - let textFields: [UIView] - if recursive { - textFields = view.deepResponderSubviews() - } else { - textFields = view.responderSubviews() - } + let textInputViews: [any IQTextInputView] = view.responderSubviews(recursive: recursive) - for textField in textFields { - - addTextFieldView(textField) + for view in textInputViews { + addTextFieldView(view) } } /** Remove all the UITextField/UITextView responderView's. - + @param view UIView object to unregister all it's responder subviews. */ - @objc public func removeResponderFromView(_ view: UIView, recursive: Bool = true) { + @objc func removeResponderFromView(_ view: UIView, recursive: Bool = true) { - let textFields: [UIView] - if recursive { - textFields = view.deepResponderSubviews() - } else { - textFields = view.responderSubviews() - } - - for textField in textFields { + let textInputViews: [any IQTextInputView] = view.responderSubviews(recursive: recursive) - removeTextFieldView(textField) + for view in textInputViews { + removeTextFieldView(view) } } +} +@available(iOSApplicationExtension, unavailable) +@MainActor +public extension IQKeyboardReturnKeyHandler { @discardableResult - internal func goToNextResponderOrResign(_ view: UIView) -> Bool { - - guard let index: Int = textFieldInfoCache.firstIndex(where: { $0.textFieldView == view}) else { return false } - - let isNotLast: Bool = index != textFieldInfoCache.count - 1 + func goToNextResponderOrResign(from textInputView: any IQTextInputView) -> Bool { - if isNotLast, let textFieldView = textFieldInfoCache[index+1].textFieldView { - textFieldView.becomeFirstResponder() - return false - } else { - view.resignFirstResponder() + guard let textInfoCache: IQTextInputViewInfoModel = nextResponderFromTextInputView(textInputView), + let textInputView = textInfoCache.textInputView else { + textInputView.resignFirstResponder() return true } + + textInputView.becomeFirstResponder() + return false } } -fileprivate extension UIView { +@available(iOSApplicationExtension, unavailable) +@MainActor +internal extension IQKeyboardReturnKeyHandler { - func responderSubviews() -> [UIView] { + func nextResponderFromTextInputView(_ textInputView: any IQTextInputView) -> IQTextInputViewInfoModel? { + guard let currentIndex: Int = textInputViewCachedInfoIndex(textInputView), + currentIndex < textInputViewInfoCache.count - 1 else { return nil } - // Array of TextInputViews. - var textInputViews: [UIView] = [] - - for view in subviews { + let candidates: [IQTextInputViewInfoModel] = Array(textInputViewInfoCache[currentIndex+1..