完善任务流程、键盘适配与页面交互

This commit is contained in:
2026-07-10 15:56:15 +08:00
parent f88a85a807
commit ab5220e460
189 changed files with 16779 additions and 1038 deletions

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,90 @@
//
// IQKeyboardToolbarConfiguration.swift
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
// 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
import IQKeyboardToolbar
@available(iOSApplicationExtension, unavailable)
@MainActor
@objcMembers public final class IQKeyboardToolbarConfiguration: NSObject {
/**
If YES, then uses textInputView's tintColor property,
otherwise tint color is default. Default is NO.
*/
public var useTextInputViewTintColor: Bool = false
/**
This is used for toolbar.tintColor when textInputView.keyboardAppearance is UIKeyboardAppearanceDefault.
If useTextInputViewTintColor is YES then this property is ignored. Default is nil and uses black color.
*/
public var tintColor: UIColor?
/**
This is used for toolbar.barTintColor. Default is nil.
*/
public var barTintColor: UIColor?
/**
IQPreviousNextDisplayModeDefault: Show NextPrevious when there are more than 1 textInputView otherwise hide.
IQPreviousNextDisplayModeAlwaysHide: Do not show NextPrevious buttons in any case.
IQPreviousNextDisplayModeAlwaysShow: Always show nextPrevious buttons,
if there are more than 1 textInputView then both buttons will be visible but will be shown as disabled.
*/
public var previousNextDisplayMode: IQPreviousNextDisplayMode = .default
/**
/**
IQAutoToolbarBySubviews: Creates Toolbar according to subview's hierarchy of TextInputView's in view.
IQAutoToolbarByTag: Creates Toolbar according to tag property of TextInputView's.
IQAutoToolbarByPosition: Creates Toolbar according to the y,x position
of textInputView in it's superview coordinate.
Default is IQAutoToolbarBySubviews.
*/
AutoToolbar managing behavior. Default is IQAutoToolbarBySubviews.
*/
public var manageBehavior: IQKeyboardToolbarManageBehavior = .bySubviews
/**
Buttons configuration displayed on the toolbar, the selector parameter is ignored in below configuration
*/
public var previousBarButtonConfiguration: IQBarButtonItemConfiguration?
public var nextBarButtonConfiguration: IQBarButtonItemConfiguration?
public var doneBarButtonConfiguration: IQBarButtonItemConfiguration?
public let placeholderConfiguration: IQKeyboardToolbarPlaceholderConfiguration = .init()
}
// swiftlint:disable unused_setter_value
@available(iOSApplicationExtension, unavailable)
@MainActor
@objc public extension IQKeyboardToolbarConfiguration {
@available(*, unavailable, renamed: "useTextInputViewTintColor")
var useTextFieldTintColor: Bool {
get { fatalError() }
set {}
}
}
// swiftlint:enable unused_setter_value

View File

@ -0,0 +1,68 @@
//
// IQKeyboardToolbarConstants.swift
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
// 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 Foundation
// MARK: IQKeyboardToolbarManageBehavior
/**
`IQAutoToolbarBySubviews`
Creates Toolbar according to subview's hierarchy of textInputView's in view.
`IQAutoToolbarByTag`
Creates Toolbar according to tag property of textInputView's.
`IQAutoToolbarByPosition`
Creates Toolbar according to the y,x position of textInputView in it's superview coordinate.
*/
@available(iOSApplicationExtension, unavailable)
@objc public enum IQKeyboardToolbarManageBehavior: Int {
case bySubviews
case byTag
case byPosition
}
@available(*, unavailable, renamed: "IQKeyboardToolbarManageBehavior")
@objc public enum IQAutoToolbarManageBehavior: Int {
case bySubviews
case byTag
case byPosition
}
/**
`IQPreviousNextDisplayModeDefault`
Show NextPrevious when there are more than 1 textInputView otherwise hide.
`IQPreviousNextDisplayModeAlwaysHide`
Do not show NextPrevious buttons in any case.
`IQPreviousNextDisplayModeAlwaysShow`
Always show nextPrevious buttons,
if there are more than 1 textInputView then both buttons will be visible but will be shown as disabled.
*/
@available(iOSApplicationExtension, unavailable)
@objc public enum IQPreviousNextDisplayMode: Int {
case `default`
case alwaysHide
case alwaysShow
}

View File

@ -0,0 +1,52 @@
//
// IQKeyboardToolbarManager+Debug
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
// 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 Foundation
// MARK: Debugging & Developer options
@available(iOSApplicationExtension, unavailable)
@MainActor
internal extension IQKeyboardToolbarManager {
func showLog(_ logString: String, indentation: Int = 0) {
guard isDebuggingEnabled else {
return
}
if indentation < 0 {
self.logIndentation = max(0, self.logIndentation + indentation)
}
var preLog: String = "IQKeyboardToolbarManager"
for _ in 0 ... self.logIndentation {
preLog += "|\t"
}
print(preLog + logString)
if indentation > 0 {
self.logIndentation += indentation
}
}
}

View File

@ -0,0 +1,29 @@
//
// IQDeepResponderContainerView.swift
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
// 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
@objcMembers open class IQDeepResponderContainerView: UIView {
}

View File

@ -0,0 +1,183 @@
//
// IQKeyboardToolbarManager+Deprecated.swift
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
// 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
import IQKeyboardToolbar
// swiftlint:disable unused_setter_value
// swiftlint:disable identifier_name
@available(iOSApplicationExtension, unavailable)
@MainActor
@objc public extension IQKeyboardToolbarManager {
@available(*, unavailable, renamed: "isEnabled")
var enableAutoToolbar: Bool {
get { false }
set { }
}
@available(*, unavailable, renamed: "playInputClicks")
var shouldPlayInputClicks: Bool {
get { false }
set { }
}
}
@available(iOSApplicationExtension, unavailable)
@MainActor
@objc public extension IQKeyboardToolbarManager {
@available(*, unavailable, renamed: "toolbarConfiguration.manageBehavior")
var toolbarManageBehaviour: IQAutoToolbarManageBehavior {
get { .bySubviews }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.useTextInputViewTintColor")
var shouldToolbarUsesTextFieldTintColor: Bool {
get { false }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.tintColor")
var toolbarTintColor: UIColor? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.barTintColor")
var toolbarBarTintColor: UIColor? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.previousNextDisplayMode")
var previousNextDisplayMode: IQPreviousNextDisplayMode {
get { .default }
set { }
}
@available(*, unavailable, renamed: "deepResponderAllowedContainerClasses")
var toolbarPreviousNextAllowedClasses: [UIView.Type] {
get { [] }
set { }
}
}
@available(iOSApplicationExtension, unavailable)
@MainActor
@objc public extension IQKeyboardToolbarManager {
@available(*, unavailable, renamed: "toolbarConfiguration.previousBarButtonConfiguration.image",
message: "To change, please assign a new toolbarConfiguration.previousBarButtonConfiguration")
var toolbarPreviousBarButtonItemImage: UIImage? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.previousBarButtonConfiguration.title",
message: "To change, please assign a new toolbarConfiguration.previousBarButtonConfiguration")
var toolbarPreviousBarButtonItemText: String? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.previousBarButtonConfiguration.accessibilityLabel",
message: "To change, please assign a new toolbarConfiguration.previousBarButtonConfiguration")
var toolbarPreviousBarButtonItemAccessibilityLabel: String? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.nextBarButtonConfiguration.image",
message: "To change, please assign a new toolbarConfiguration.nextBarButtonConfiguration")
var toolbarNextBarButtonItemImage: UIImage? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.nextBarButtonConfiguration.title",
message: "To change, please assign a new toolbarConfiguration.nextBarButtonConfiguration")
var toolbarNextBarButtonItemText: String? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.nextBarButtonConfiguration.accessibilityLabel",
message: "To change, please assign a new toolbarConfiguration.nextBarButtonConfiguration")
var toolbarNextBarButtonItemAccessibilityLabel: String? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.doneBarButtonConfiguration.image",
message: "To change, please assign a new toolbarConfiguration.doneBarButtonConfiguration")
var toolbarDoneBarButtonItemImage: UIImage? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.doneBarButtonConfiguration.title",
message: "To change, please assign a new toolbarConfiguration.doneBarButtonConfiguration")
var toolbarDoneBarButtonItemText: String? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.doneBarButtonConfiguration.accessibilityLabel",
message: "To change, please assign a new toolbarConfiguration.doneBarButtonConfiguration")
var toolbarDoneBarButtonItemAccessibilityLabel: String? {
get { nil }
set { }
}
}
@available(iOSApplicationExtension, unavailable)
@MainActor
@objc public extension IQKeyboardToolbarManager {
@available(*, unavailable, renamed: "toolbarConfiguration.placeholderConfiguration.accessibilityLabel")
var toolbarTitleBarButtonItemAccessibilityLabel: String? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.placeholderConfiguration.showPlaceholder")
var shouldShowToolbarPlaceholder: Bool {
get { false }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.placeholderConfiguration.font")
var placeholderFont: UIFont? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.placeholderConfiguration.color")
var placeholderColor: UIColor? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.placeholderConfiguration.buttonColor")
var placeholderButtonColor: UIColor? {
get { nil }
set { }
}
}
// swiftlint:enable unused_setter_value
// swiftlint:enable identifier_name

View File

@ -0,0 +1,148 @@
//
// IQKeyboardToolbarManager+Internal.swift
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
// 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
import IQTextInputViewNotification
import IQKeyboardCore
@available(iOSApplicationExtension, unavailable)
@MainActor
internal extension IQKeyboardToolbarManager {
/** Get all textInputView siblings of textInputView. */
func responderViews(of textInputView: some IQTextInputView) -> [any IQTextInputView]? {
var superConsideredView: UIView?
// If find any consider responderView in it's upper hierarchy then will get deepResponderView.
for allowedClass in deepResponderAllowedContainerClasses {
superConsideredView = (textInputView as UIView).iq.superviewOf(type: allowedClass)
if superConsideredView != nil {
break
}
}
let swiftUIHostingView: UIView? = Self.getSwiftUIHostingView(textInputView: textInputView)
// (Enhancement ID: #22)
// If there is a superConsideredView in view's hierarchy,
// then fetching all it's subview that responds.
// No sorting for superConsideredView, it's by subView position.
if let view: UIView = swiftUIHostingView {
return view.iq.deepResponderViews()
} else if let view: UIView = superConsideredView {
return view.iq.deepResponderViews()
} else { // Otherwise fetching all the siblings
let textInputViews: [any IQTextInputView] = textInputView.iq.responderSiblings()
// Sorting textInputViews according to behavior
switch toolbarConfiguration.manageBehavior {
// If autoToolbar behavior is bySubviews, then returning it.
case .bySubviews: return textInputViews
// If autoToolbar behavior is by tag, then sorting it according to tag property.
case .byTag: return textInputViews.sortedByTag()
// If autoToolbar behavior is by tag, then sorting it according to tag property.
case .byPosition: return textInputViews.sortedByPosition()
}
}
}
func privateIsEnableAutoToolbar(of textInputView: some IQTextInputView) -> Bool {
var isEnabled: Bool = isEnabled
guard var textInputViewController = (textInputView as UIView).iq.viewContainingController() else {
return isEnabled
}
if textInputView is UISearchTextField {
if let navController: UINavigationController = textInputViewController as? UINavigationController,
let topController: UIViewController = navController.topViewController {
textInputViewController = topController
}
// Not adding toolbar for searchTextField inside searchController.
if textInputViewController.navigationItem.searchController?.searchBar.searchTextField == textInputView {
return false
}
}
if !isEnabled, enabledToolbarClasses.contains(where: { textInputViewController.isKind(of: $0) }) {
isEnabled = true
}
if isEnabled {
// If found any toolbar disabled classes then return.
if disabledToolbarClasses.contains(where: { textInputViewController.isKind(of: $0) }) {
isEnabled = false
}
// Special Controllers
if isEnabled {
let classNameString: String = "\(type(of: textInputViewController.self))"
// _UIAlertControllerTextFieldViewController
if classNameString.contains("UIAlertController"),
classNameString.hasSuffix("TextFieldViewController") {
isEnabled = false
}
}
}
return isEnabled
}
}
@available(iOSApplicationExtension, unavailable)
@MainActor
private extension IQKeyboardToolbarManager {
private static func getSwiftUIHostingView(textInputView: some IQTextInputView) -> UIView? {
var swiftUIHostingView: UIView?
let swiftUIHostingViewName: String = "UIHostingView<"
var superView: UIView? = textInputView.superview
while let aSuperview: UIView = superView {
let classNameString: String = {
var name: String = "\(type(of: aSuperview.self))"
if name.hasPrefix("_") {
name.removeFirst()
}
return name
}()
if classNameString.hasPrefix(swiftUIHostingViewName) {
swiftUIHostingView = aSuperview
break
}
superView = aSuperview.superview
}
return swiftUIHostingView
}
}

View File

@ -0,0 +1,173 @@
//
// IQKeyboardToolbarManager.swift
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
// 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
import IQTextInputViewNotification
import IQKeyboardCore
import IQKeyboardToolbar
import Combine
@available(iOSApplicationExtension, unavailable)
@MainActor
@objcMembers public final class IQKeyboardToolbarManager: NSObject {
private let textInputViewObserver: IQTextInputViewNotification = .init()
private var storage: Set<AnyCancellable> = []
internal var textInputView: (some IQTextInputView)? {
textInputViewObserver.textInputView
}
/**
Returns the default singleton instance.
*/
@MainActor
@objc public static let shared: IQKeyboardToolbarManager = .init()
/**
Automatic add the toolbar functionality. Default is false.
*/
public var isEnabled: Bool = false {
didSet {
reloadInputViews()
showLog("isEnabled: \(isEnabled ? "Yes" : "NO")")
}
}
public var isDebuggingEnabled: Bool = false
/**
Configurations related to the toolbar display over the keyboard.
*/
public let toolbarConfiguration: IQKeyboardToolbarConfiguration = .init()
// MARK: UISound handling
/**
If YES, then it plays inputClick sound on next/previous/done click.
*/
public var playInputClicks: Bool = true
/**
Disable automatic toolbar creation within the scope of disabled toolbar viewControllers classes.
Within this scope, 'isEnabled' property is ignored. Class should be kind of UIViewController.
*/
public var disabledToolbarClasses: [UIViewController.Type] = [
UIAlertController.self,
UIInputViewController.self
]
/**
Enable automatic toolbar creation within the scope of enabled toolbar viewControllers classes.
Within this scope, 'isEnabled' property is ignored. Class should be kind of UIViewController.
If same Class is added in disabledToolbarClasses list, then enabledToolbarClasses will be ignore.
*/
public var enabledToolbarClasses: [UIViewController.Type] = []
/**
Allowed subclasses of UIView to add all inner textInputView,
this will allow to navigate between textInputView contains in different superview.
Class should be kind of UIView.
*/
public var deepResponderAllowedContainerClasses: [UIView.Type] = [
UITableView.self,
UICollectionView.self,
IQDeepResponderContainerView.self
]
internal var logIndentation = 0
internal override init() {
super.init()
addTextInputViewObserver()
addBecomeActiveObserver()
// (Bug ID: #550)
// Loading IQKeyboardToolbar, IQTitleBarButtonItem, IQBarButtonItem to fix first time keyboard appearance delay
// 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 textInputView: UITextField = UITextField()
textInputView.iq.addDone(target: nil, action: #selector(self.doneAction(_:)))
textInputView.iq.addPreviousNextDone(target: nil,
previousAction: #selector(self.previousAction(_:)),
nextAction: #selector(self.nextAction(_:)),
doneAction: #selector(self.doneAction(_:)))
}
}
}
@available(iOSApplicationExtension, unavailable)
@MainActor
private extension IQKeyboardToolbarManager {
private func removeTextInputViewObserver() {
textInputViewObserver.unsubscribe(identifier: "IQKeyboardToolbarManager")
}
private func addTextInputViewObserver() {
textInputViewObserver.subscribe(identifier: "IQKeyboardToolbarManager",
changeHandler: { [weak self] event, textInputView in
guard let self = self else { return }
guard (textInputView as UIView).iq.isAlertViewTextField() == false else {
return
}
switch event {
case .beginEditing:
reloadInputViews()
case .endEditing:
removeToolbarIfRequired(of: textInputView)
}
})
}
}
@available(iOSApplicationExtension, unavailable)
@MainActor
private extension IQKeyboardToolbarManager {
private func removeBecomeActiveObserver() {
storage.forEach { $0.cancel() }
storage.removeAll()
}
private func addBecomeActiveObserver() {
let beginEditingNotificationNames: [Notification.Name] = [
UIApplication.didBecomeActiveNotification
]
for notificationName in beginEditingNotificationNames {
NotificationCenter.default.publisher(for: notificationName)
.sink(receiveValue: { [weak self] info in
guard let self = self else { return }
// In SwiftUI View's when app comes from background to foreground
// The inputAccessoryView was getting removed in iOS 26. So forcing to reload.
self.reloadInputViews()
})
.store(in: &storage)
}
}
}

View File

@ -0,0 +1,210 @@
//
// IQKeyboardToolbarManager+Action.swift
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
// 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
import IQKeyboardCore
import IQKeyboardToolbar
// MARK: Previous next button actions
@available(iOSApplicationExtension, unavailable)
@MainActor
@objc public extension IQKeyboardToolbarManager {
/**
Returns YES if can navigate to previous responder textInputView, otherwise NO.
*/
var canGoPrevious: Bool {
// If it is not first textInputView. then it's previous object canBecomeFirstResponder.
guard let textInputView: any IQTextInputView = self.textInputView,
let textInputViews: [any IQTextInputView] = responderViews(of: textInputView),
let index: Int = textInputViews.firstIndex(where: { $0 == textInputView }),
index > 0 else {
return false
}
return true
}
/**
Returns YES if can navigate to next responder textInputViews, otherwise NO.
*/
var canGoNext: Bool {
// If it is not first textInputView. then it's previous object canBecomeFirstResponder.
guard let textInputView: any IQTextInputView = self.textInputView,
let textInputViews: [any IQTextInputView] = responderViews(of: textInputView),
let index: Int = textInputViews.firstIndex(where: { $0 == textInputView }),
index < textInputViews.count-1 else {
return false
}
return true
}
/**
Navigate to previous responder textInputViews
*/
@discardableResult
func goPrevious() -> Bool {
// If it is not first textInputView. then it's previous object becomeFirstResponder.
guard let textInputView: any IQTextInputView = self.textInputView,
let textInputViews: [any IQTextInputView] = responderViews(of: textInputView),
let index: Int = textInputViews.firstIndex(where: { $0 == textInputView }),
index > 0 else {
return false
}
let nextTextInputView: any IQTextInputView = textInputViews[index-1]
let isAcceptAsFirstResponder: Bool = nextTextInputView.becomeFirstResponder()
// If it refuses then becoming previous textInputView as first responder again. (Bug ID: #96)
if !isAcceptAsFirstResponder {
showLog("Refuses to become first responder: \(nextTextInputView)")
}
return isAcceptAsFirstResponder
}
/**
Navigate to next responder textInputView.
*/
@discardableResult
func goNext() -> Bool {
// If it is not first textInputView. then it's previous object becomeFirstResponder.
guard let textInputView: any IQTextInputView = self.textInputView,
let textInputViews: [any IQTextInputView] = responderViews(of: textInputView),
let index: Int = textInputViews.firstIndex(where: { $0 == textInputView }),
index < textInputViews.count-1 else {
return false
}
let nextTextInputView: any IQTextInputView = textInputViews[index+1]
let isAcceptAsFirstResponder: Bool = nextTextInputView.becomeFirstResponder()
// If it refuses then becoming previous textInputView as first responder again. (Bug ID: #96)
if !isAcceptAsFirstResponder {
showLog("Refuses to become first responder: \(nextTextInputView)")
}
return isAcceptAsFirstResponder
}
}
@available(iOSApplicationExtension, unavailable)
@MainActor
internal extension IQKeyboardToolbarManager {
/** previousAction. */
func previousAction(_ barButton: IQBarButtonItem) {
// If user wants to play input Click sound.
if playInputClicks {
// Play Input Click Sound.
UIDevice.current.playInputClick()
}
guard canGoPrevious,
let textInputView: any IQTextInputView = self.textInputView else {
return
}
let isAcceptedAsFirstResponder: Bool = goPrevious()
if isAcceptedAsFirstResponder {
Self.sendInvokeAction(of: barButton, sender: textInputView)
}
}
/** nextAction. */
func nextAction(_ barButton: IQBarButtonItem) {
// If user wants to play input Click sound.
if playInputClicks {
// Play Input Click Sound.
UIDevice.current.playInputClick()
}
guard canGoNext,
let textInputView: any IQTextInputView = self.textInputView else {
return
}
let isAcceptedAsFirstResponder: Bool = goNext()
if isAcceptedAsFirstResponder {
Self.sendInvokeAction(of: barButton, sender: textInputView)
}
}
/** doneAction. Resigning current textInputView. */
func doneAction(_ barButton: IQBarButtonItem) {
// If user wants to play input Click sound.
if playInputClicks {
// Play Input Click Sound.
UIDevice.current.playInputClick()
}
guard let textInputView: any IQTextInputView = self.textInputView else {
return
}
// Resign textInputView.
let isResignedFirstResponder: Bool = textInputView.resignFirstResponder()
if isResignedFirstResponder {
Self.sendInvokeAction(of: barButton, sender: textInputView)
}
}
}
@available(iOSApplicationExtension, unavailable)
@MainActor
private extension IQKeyboardToolbarManager {
private static func sendInvokeAction(of barButton: IQBarButtonItem, sender: some IQTextInputView) {
// Handling search bar special case
if let searchBar: UISearchBar = sender.iq.textFieldSearchBar() {
switch barButton {
case sender.internalToolbar.nextBarButton:
searchBar.internalToolbar.nextBarButton.invocation?.invoke(from: searchBar)
case sender.internalToolbar.previousBarButton:
searchBar.internalToolbar.previousBarButton.invocation?.invoke(from: searchBar)
case sender.internalToolbar.doneBarButton:
searchBar.internalToolbar.doneBarButton.invocation?.invoke(from: searchBar)
default:
break
}
} else {
barButton.invocation?.invoke(from: sender)
}
}
}
@available(iOSApplicationExtension, unavailable)
@MainActor
fileprivate extension IQTextInputView {
var internalToolbar: IQKeyboardToolbar {
return iq.toolbar
}
}

View File

@ -0,0 +1,287 @@
//
// IQKeyboardToolbarManager+Toolbar.swift
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
// 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
import IQKeyboardCore
import IQKeyboardToolbar
@available(iOSApplicationExtension, unavailable)
@MainActor
@objc public extension IQKeyboardToolbarManager {
/** reloadInputViews to reload toolbar buttons enable/disable state on the fly Enhancement ID #434. */
func reloadInputViews() {
guard let textInputView = textInputView else { return }
// If enabled then adding toolbar.
if privateIsEnableAutoToolbar(of: textInputView) {
self.addToolbarIfRequired(of: textInputView)
} else {
self.removeToolbarIfRequired(of: textInputView)
}
}
}
@available(iOSApplicationExtension, unavailable)
@MainActor
internal extension IQKeyboardToolbarManager {
/**
Default tag for toolbar with Done button -1001
*/
private static let toolbarTag = -1001
// swiftlint:disable function_body_length
// swiftlint:disable cyclomatic_complexity
/**
Add toolbar if it is required to add on textInputViews and it's siblings.
*/
func addToolbarIfRequired(of textInputView: 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: [any IQTextInputView] = responderViews(of: textInputView), !siblings.isEmpty,
!Self.hasUserDefinedInputAccessoryView(textInputView: textInputView) else {
return
}
showLog(">>>>> \(#function) started >>>>>", indentation: 1)
defer {
showLog("<<<<< \(#function) ended <<<<<", indentation: -1)
}
showLog("Found \(siblings.count) responder sibling(s)")
let rightConfiguration: IQBarButtonItemConfiguration = getRightConfiguration()
let isTableCollectionView: Bool
if (textInputView as UIView).iq.superviewOf(type: UITableView.self) != nil ||
(textInputView as UIView).iq.superviewOf(type: UICollectionView.self) != nil {
isTableCollectionView = true
} else {
isTableCollectionView = false
}
let previousNextDisplayMode: IQPreviousNextDisplayMode = toolbarConfiguration.previousNextDisplayMode
let havePreviousNext: Bool
switch previousNextDisplayMode {
case .default:
// If the textInputView is part of UITableView/UICollectionView then we should be exposing previous/next too
// Because at this time we don't know previous or next cell if it contains another textInputView to move.
if isTableCollectionView {
havePreviousNext = true
} else if siblings.count <= 1 {
// If only one object is found, then adding only Done button.
havePreviousNext = false
} else {
havePreviousNext = true
}
case .alwaysShow:
havePreviousNext = true
case .alwaysHide:
havePreviousNext = false
}
let placeholderConfig: IQKeyboardToolbarPlaceholderConfiguration = toolbarConfiguration.placeholderConfiguration
let titleText: String? = placeholderConfig.showPlaceholder ? textInputView.iq.drawingPlaceholder : nil
if havePreviousNext {
let prevConfiguration: IQBarButtonItemConfiguration = getPreviousConfiguration()
let nextConfiguration: IQBarButtonItemConfiguration = getNextConfiguration()
textInputView.iq.addToolbar(target: self,
previousConfiguration: prevConfiguration,
nextConfiguration: nextConfiguration,
rightConfiguration: rightConfiguration, title: titleText,
titleAccessibilityLabel: placeholderConfig.accessibilityLabel)
if isTableCollectionView {
// (Bug ID: #56)
// In case of UITableView, the next/previous buttons should always be enabled.
textInputView.iq.toolbar.previousBarButton.isEnabled = true
textInputView.iq.toolbar.nextBarButton.isEnabled = true
} else {
// If first textInputView, then previous should not be enabled.
if let first = siblings.first {
textInputView.iq.toolbar.previousBarButton.isEnabled = first != textInputView
} else {
textInputView.iq.toolbar.previousBarButton.isEnabled = false
}
// If last textInputView, then next should not be enabled.
if let last = siblings.last {
textInputView.iq.toolbar.nextBarButton.isEnabled = last != textInputView
} else {
textInputView.iq.toolbar.nextBarButton.isEnabled = false
}
}
} else {
textInputView.iq.addToolbar(target: self, rightConfiguration: rightConfiguration,
title: titleText,
titleAccessibilityLabel: placeholderConfig.accessibilityLabel)
}
// (Bug ID: #78)
textInputView.inputAccessoryView?.tag = IQKeyboardToolbarManager.toolbarTag
Self.applyToolbarConfiguration(textInputView: textInputView, toolbarConfiguration: toolbarConfiguration)
}
// swiftlint:enable function_body_length
// swiftlint:enable cyclomatic_complexity
/** Remove any toolbar if it is IQKeyboardToolbar. */
func removeToolbarIfRequired(of textInputView: some IQTextInputView) { // (Bug ID: #18)
guard let toolbar: IQKeyboardToolbar = textInputView.inputAccessoryView as? IQKeyboardToolbar,
toolbar.tag == IQKeyboardToolbarManager.toolbarTag else {
return
}
showLog(">>>>> \(#function) started >>>>>", indentation: 1)
defer {
showLog("<<<<< \(#function) ended <<<<<", indentation: -1)
}
textInputView.inputAccessoryView = nil
}
}
@available(iOSApplicationExtension, unavailable)
@MainActor
private extension IQKeyboardToolbarManager {
static func hasUserDefinedInputAccessoryView(textInputView: some IQTextInputView) -> Bool {
guard let inputAccessoryView: UIView = textInputView.inputAccessoryView,
inputAccessoryView.tag != IQKeyboardToolbarManager.toolbarTag else { return false }
let swiftUIAccessoryNamePrefixes: [String] = [
"InputAccessoryHost<InputAccessoryBar>", // iOS 17 and below
"RootUIView", // iOS 18
"_UIInputViewContent", // iOS 18 system views
"_UIKeyboardInputAccessory" // Additional iOS 18 system view
]
let classNameString: String = "\(type(of: inputAccessoryView.classForCoder))"
// If it's SwiftUI accessory view but doesn't have a height (fake accessory view), then we should
// add our own accessoryView otherwise, keep the SwiftUI accessoryView since user has added it from code
let isSwiftUIAccessoryView = swiftUIAccessoryNamePrefixes.contains(where: { classNameString.hasPrefix($0) })
// A height of 0 or very small height typically indicates a system placeholder
guard isSwiftUIAccessoryView,
inputAccessoryView.subviews.isEmpty else {
return true
}
return false
}
func getRightConfiguration() -> IQBarButtonItemConfiguration {
let rightConfiguration: IQBarButtonItemConfiguration
if let configuration: IQBarButtonItemConfiguration = toolbarConfiguration.doneBarButtonConfiguration {
rightConfiguration = configuration
rightConfiguration.action = #selector(self.doneAction(_:))
} else {
#if compiler(>=6.2) // Xcode 26
let requiresCompatibility: Bool = Bundle.main.object(forInfoDictionaryKey: "UIDesignRequiresCompatibility") as? Bool ?? false
if #available(iOS 26.0, *), !requiresCompatibility {
rightConfiguration = IQBarButtonItemConfiguration(image: UIImage(systemName: "checkmark")!, action: #selector(self.doneAction(_:)))
} else {
rightConfiguration = IQBarButtonItemConfiguration(systemItem: .done, action: #selector(self.doneAction(_:)))
}
#else
rightConfiguration = IQBarButtonItemConfiguration(systemItem: .done, action: #selector(self.doneAction(_:)))
#endif
rightConfiguration.accessibilityLabel = "Done"
}
return rightConfiguration
}
func getPreviousConfiguration() -> IQBarButtonItemConfiguration {
let prevConfiguration: IQBarButtonItemConfiguration
if let configuration: IQBarButtonItemConfiguration = toolbarConfiguration.previousBarButtonConfiguration {
configuration.action = #selector(self.previousAction(_:))
prevConfiguration = configuration
} else {
let chevronUp: UIImage = UIImage(systemName: "chevron.up") ?? UIImage()
prevConfiguration = IQBarButtonItemConfiguration(image: chevronUp,
action: #selector(self.previousAction(_:)))
prevConfiguration.accessibilityLabel = "Previous"
}
return prevConfiguration
}
func getNextConfiguration() -> IQBarButtonItemConfiguration {
let nextConfiguration: IQBarButtonItemConfiguration
if let configuration: IQBarButtonItemConfiguration = toolbarConfiguration.nextBarButtonConfiguration {
configuration.action = #selector(self.nextAction(_:))
nextConfiguration = configuration
} else {
let chevronDown: UIImage = UIImage(systemName: "chevron.down") ?? UIImage()
nextConfiguration = IQBarButtonItemConfiguration(image: chevronDown,
action: #selector(self.nextAction(_:)))
nextConfiguration.accessibilityLabel = "Next"
}
return nextConfiguration
}
static func applyToolbarConfiguration(textInputView: some IQTextInputView,
toolbarConfiguration: IQKeyboardToolbarConfiguration) {
let toolbar: IQKeyboardToolbar = textInputView.iq.toolbar
// Setting toolbar tintColor // (Enhancement ID: #30)
if toolbarConfiguration.useTextInputViewTintColor {
toolbar.tintColor = textInputView.tintColor
} else {
toolbar.tintColor = toolbarConfiguration.tintColor
}
// Bar style according to keyboard appearance
switch textInputView.keyboardAppearance {
case .dark:
toolbar.barStyle = .black
toolbar.barTintColor = nil
default:
toolbar.barStyle = .default
toolbar.barTintColor = toolbarConfiguration.barTintColor
}
// Setting toolbar title font. // (Enhancement ID: #30)
guard toolbarConfiguration.placeholderConfiguration.showPlaceholder,
!textInputView.iq.hidePlaceholder else {
toolbar.titleBarButton.title = nil
return
}
// Updating placeholder font to toolbar. //(Bug ID: #148, #272)
if toolbar.titleBarButton.title == nil ||
toolbar.titleBarButton.title != textInputView.iq.drawingPlaceholder {
toolbar.titleBarButton.title = textInputView.iq.drawingPlaceholder
}
// Setting toolbar title font. // (Enhancement ID: #30)
toolbar.titleBarButton.titleFont = toolbarConfiguration.placeholderConfiguration.font
// Setting toolbar title color. // (Enhancement ID: #880)
toolbar.titleBarButton.titleColor = toolbarConfiguration.placeholderConfiguration.color
// Setting toolbar button title color. // (Enhancement ID: #880)
toolbar.titleBarButton.selectableTitleColor = toolbarConfiguration.placeholderConfiguration.buttonColor
}
}

View File

@ -0,0 +1,58 @@
//
// Array+Sort.swift
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
// 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
import IQKeyboardCore
/**
UIView.subviews sorting category.
*/
@available(iOSApplicationExtension, unavailable)
@MainActor
internal extension Array where Element: IQTextInputView {
/**
Returns the array by sorting the UIView's by their tag property.
*/
func sortedByTag() -> [Element] {
return sorted(by: { (obj1: Element, obj2: Element) -> Bool in
return (obj1.tag < obj2.tag)
})
}
/**
Returns the array by sorting the UIView's by their tag property.
*/
func sortedByPosition() -> [Element] {
return sorted(by: { (obj1: Element, obj2: Element) -> Bool in
if obj1.frame.minY != obj2.frame.minY {
return obj1.frame.minY < obj2.frame.minY
} else {
return obj1.frame.minX < obj2.frame.minX
}
})
}
}

View File

@ -0,0 +1,179 @@
//
// UIView+Responders.swift
// https://github.com/hackiftekhar/IQKeyboardToolbarManager
// 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
import IQKeyboardCore
@available(iOSApplicationExtension, unavailable)
@MainActor
private struct AssociatedKeys {
static var ignoreSwitchingByNextPrevious: Int = 0
}
/**
UIView category for managing textInputView
*/
@available(iOSApplicationExtension, unavailable)
@MainActor
public extension IQKeyboardExtension where Base: IQTextInputView {
/**
If ignoreSwitchingByNextPrevious is true then library will ignore this textInputView
while moving to other textInputView using keyboard toolbar next previous buttons.
Default is false
*/
var ignoreSwitchingByNextPrevious: Bool {
get {
if let base = base {
return objc_getAssociatedObject(base, &AssociatedKeys.ignoreSwitchingByNextPrevious) as? Bool ?? false
}
return false
}
set(newValue) {
if let base = base {
objc_setAssociatedObject(base, &AssociatedKeys.ignoreSwitchingByNextPrevious,
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
}
// swiftlint:disable unused_setter_value
@available(iOSApplicationExtension, unavailable)
@MainActor
@objc public extension UIView {
@available(*, unavailable, renamed: "iq.ignoreSwitchingByNextPrevious")
var ignoreSwitchingByNextPrevious: Bool {
get { false }
set { }
}
}
// swiftlint:enable unused_setter_value
@available(iOSApplicationExtension, unavailable)
@MainActor
internal extension IQKeyboardExtension where Base: IQTextInputView {
/**
Returns all siblings of the receiver which canBecomeFirstResponder.
*/
func responderSiblings() -> [any IQTextInputView] {
// Getting all siblings
guard let siblings: [UIView] = base?.superview?.subviews else { return [] }
// Array of textInputView.
var textInputViews: [any IQTextInputView] = []
for view in siblings {
if let view = view as? any IQTextInputView,
view == base || !view.internalIgnoreSwitchingByNextPrevious,
view.internalCanBecomeFirstResponder() {
textInputViews.append(view)
}
}
return textInputViews
}
}
/**
UIView category for managing textInputView
*/
@available(iOSApplicationExtension, unavailable)
@MainActor
internal extension IQKeyboardExtension where Base: UIView {
/**
Returns all deep subViews of the receiver which canBecomeFirstResponder.
*/
func deepResponderViews() -> [any IQTextInputView] {
guard let subviews: [UIView] = base?.subviews, !subviews.isEmpty else { return [] }
// Array of textInputViews.
var textInputViews: [any IQTextInputView] = []
for view in subviews {
if let view = view as? any IQTextInputView,
view == base || !view.internalIgnoreSwitchingByNextPrevious,
view.internalCanBecomeFirstResponder() {
textInputViews.append(view)
}
// Sometimes there are hidden or disabled views and textInputView inside them still recorded,
// so we added some more validations here (Bug ID: #458)
// Uncommented else (Bug ID: #625)
else if view.subviews.count != 0,
base?.isUserInteractionEnabled == true,
base?.isHidden == false, base?.alpha != 0.0 {
for deepView in view.iq.deepResponderViews() {
textInputViews.append(deepView)
}
}
}
// subviews are returning in opposite order. Sorting according the frames 'y'.
return textInputViews.sorted(by: { (view1: any IQTextInputView, view2: any IQTextInputView) -> Bool in
let frame1: CGRect = view1.convert(view1.bounds, to: base)
let frame2: CGRect = view2.convert(view2.bounds, to: base)
if frame1.minY != frame2.minY {
return frame1.minY < frame2.minY
} else {
return frame1.minX < frame2.minX
}
})
}
}
@available(iOSApplicationExtension, unavailable)
@MainActor
private extension IQKeyboardExtension where Base: IQTextInputView {
func canBecomeFirstResponder() -> Bool {
var canBecomeFirstResponder: Bool = base?.iqIsEnabled == true
if canBecomeFirstResponder {
canBecomeFirstResponder = base?.isUserInteractionEnabled == true &&
base?.isHidden == false &&
base?.alpha != 0.0 &&
!isAlertViewTextField() &&
textFieldSearchBar() == nil
}
return canBecomeFirstResponder
}
}
@available(iOSApplicationExtension, unavailable)
@MainActor
fileprivate extension IQTextInputView {
var internalIgnoreSwitchingByNextPrevious: Bool {
return iq.ignoreSwitchingByNextPrevious
}
func internalCanBecomeFirstResponder() -> Bool {
return iq.canBecomeFirstResponder()
}
}

View File

@ -0,0 +1,44 @@
//
// UIView+RespondersObjc.swift
// https://github.com/hackiftekhar/IQKeyboardToolbar
// 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
// MARK: For ObjectiveC Compatibility
// swiftlint:disable identifier_name
@objc public extension UITextField {
var iq_ignoreSwitchingByNextPrevious: Bool {
get { iq.ignoreSwitchingByNextPrevious }
set { iq.ignoreSwitchingByNextPrevious = newValue }
}
}
@objc public extension UITextView {
var iq_ignoreSwitchingByNextPrevious: Bool {
get { iq.ignoreSwitchingByNextPrevious }
set { iq.ignoreSwitchingByNextPrevious = newValue }
}
}
// swiftlint:enable identifier_name

21
Pods/IQKeyboardToolbarManager/LICENSE generated Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Mohd 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.

134
Pods/IQKeyboardToolbarManager/README.md generated Normal file
View File

@ -0,0 +1,134 @@
# IQKeyboardToolbarManager
[![CI Status](https://img.shields.io/travis/hackiftekhar/IQKeyboardToolbarManager.svg?style=flat)](https://travis-ci.org/hackiftekhar/IQKeyboardToolbarManager)
[![Version](https://img.shields.io/cocoapods/v/IQKeyboardToolbarManager.svg?style=flat)](https://cocoapods.org/pods/IQKeyboardToolbarManager)
[![License](https://img.shields.io/cocoapods/l/IQKeyboardToolbarManager.svg?style=flat)](https://cocoapods.org/pods/IQKeyboardToolbarManager)
[![Platform](https://img.shields.io/cocoapods/p/IQKeyboardToolbarManager.svg?style=flat)](https://cocoapods.org/pods/IQKeyboardToolbarManager)
![Screenshot](https://raw.githubusercontent.com/hackiftekhar/IQKeyboardToolbarManager/master/Screenshot/IQKeyboardToolbarManagerScreenshot.png)
## Example
To run the example project, clone the repo, and run `pod install` from the Example directory first.
## Requirements
## Installation
### Swift Package Manager (Recommended)
IQKeyboardToolbarManager is available through [Swift Package Manager](https://swift.org/package-manager/).
**Requirements:** iOS 13.0+, Swift 5.7+
#### Using Xcode:
1. In Xcode, go to `File``Add Package Dependencies...`
2. Enter the repository URL: `https://github.com/hackiftekhar/IQKeyboardToolbarManager`
3. Select the version rule (e.g., "Up to Next Major Version")
4. Click `Add Package`
5. Select the `IQKeyboardToolbarManager` library and click `Add Package`
#### Using Package.swift:
Add the following dependency to your `Package.swift` file:
```swift
dependencies: [
.package(url: "https://github.com/hackiftekhar/IQKeyboardToolbarManager", from: "1.1.3")
]
```
Then add `IQKeyboardToolbarManager` to your target dependencies:
```swift
.target(
name: "YourTarget",
dependencies: ["IQKeyboardToolbarManager"]
)
```
### CocoaPods
IQKeyboardToolbarManager is also available through [CocoaPods](https://cocoapods.org). To install
it, simply add the following line to your Podfile:
```ruby
pod 'IQKeyboardToolbarManager'
```
### Carthage
Add the following line to your `Cartfile`:
```
github "hackiftekhar/IQKeyboardToolbarManager"
```
## Usage
After adding IQKeyboardToolbarManager to your project, import it and enable toolbar handling in AppDelegate:
```swift
import UIKit
import IQKeyboardToolbarManager
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
IQKeyboardToolbarManager.shared.isEnabled = true
return true
}
```
Cuistomize Behavior
```swift
IQKeyboardToolbarManager.shared.toolbarConfiguration.useTextInputViewTintColor = true
IQKeyboardToolbarManager.shared.toolbarConfiguration.tintColor = UIColor.systemGreen
IQKeyboardToolbarManager.shared.toolbarConfiguration.barTintColor = UIColor.systemYellow
IQKeyboardToolbarManager.shared.toolbarConfiguration.previousNextDisplayMode = .alwaysShow
IQKeyboardToolbarManager.shared.toolbarConfiguration.manageBehavior = .byPosition
IQKeyboardToolbarManager.shared.toolbarConfiguration.previousBarButtonConfiguration = ... // BarButton configuration to change title, image or system image etc
IQKeyboardToolbarManager.shared.toolbarConfiguration.nextBarButtonConfiguration = ... // BarButton configuration to change title, image or system image etc
IQKeyboardToolbarManager.shared.toolbarConfiguration.doneBarButtonConfiguration = ... // BarButton configuration to change title, image or system image etc
IQKeyboardToolbarManager.shared.toolbarConfiguration.placeholderConfiguration.showPlaceholder = false
IQKeyboardToolbarManager.shared.toolbarConfiguration.placeholderConfiguration.font = UIFont.italicSystemFont(ofSize: 14)
IQKeyboardToolbarManager.shared.toolbarConfiguration.placeholderConfiguration.color = UIColor.systemPurple
IQKeyboardToolbarManager.shared.toolbarConfiguration.placeholderConfiguration.buttonColor = UIColor.systemBrown // This is used only if placeholder is an action button
IQKeyboardToolbarManager.shared.playInputClicks = false
IQKeyboardToolbarManager.shared.disabledToolbarClasses.append(ChatViewController.self)
IQKeyboardToolbarManager.shared.enabledToolbarClasses.append(LoginViewController.self)
IQKeyboardToolbarManager.shared.deepResponderAllowedContainerClasses.append(UIStackView.self)
```
Useful functions and variables
```swift
if IQKeyboardToolbarManager.shared.canGoPrevious {
...
}
if IQKeyboardToolbarManager.shared.canGoNext {
...
}
IQKeyboardToolbarManager.shared.goPrevious()
IQKeyboardToolbarManager.shared.goNext()
IQKeyboardToolbarManager.shared.reloadInputViews() // If some textInputView hierarchy are changed on the fly then use this to reload button states
```
Useful functions and variables for TextInputView
```swift
textField.iq.ignoreSwitchingByNextPrevious = false
```
## Author
Iftekhar Qurashi hack.iftekhar@gmail.com
## License
IQKeyboardToolbarManager is available under the MIT license. See the LICENSE file for more info.