Skip to content

Commit

Permalink
- Added IQTextInputView
Browse files Browse the repository at this point in the history
-  Improved IQReturnKeyHandler
  • Loading branch information
hackiftekhar committed Aug 2, 2024
1 parent 5ec18bf commit 9febb72
Show file tree
Hide file tree
Showing 16 changed files with 456 additions and 395 deletions.
1 change: 0 additions & 1 deletion IQKeyboardManagerSwift.podspec.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"ios": "13.0"
},
"swift_versions": [
"5.6",
"5.7",
"5.8",
"5.9"
Expand Down
86 changes: 86 additions & 0 deletions IQKeyboardManagerSwift/IQKeyboardCore/IQTextInputView.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ import UIKit
return false
}

@objc public override init() {
override init() {
super.init()
addKeyboardListener()
addTextFieldViewListener()
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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
}

Expand All @@ -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
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 9 additions & 10 deletions IQKeyboardManagerSwift/IQKeyboardManager/IQKeyboardManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -111,29 +111,27 @@ 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
}
}

// MARK: Common

// swiftlint:disable cyclomatic_complexity
// swiftlint:disable function_body_length
func addToolbar(target: AnyObject?,
previousConfiguration: IQBarButtonItemConfiguration? = nil,
nextConfiguration: IQBarButtonItemConfiguration? = nil,
Expand All @@ -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
Expand Down Expand Up @@ -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?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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(_:)),
Expand All @@ -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
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
Loading

0 comments on commit 9febb72

Please sign in to comment.