no message

This commit is contained in:
忆海16 2024-04-22 10:40:31 +08:00
parent 803519a909
commit 35831caa2d
39 changed files with 131 additions and 5441 deletions

0
.gitignore vendored Normal file
View File

View File

@ -1,203 +0,0 @@
//
// IQActiveConfiguration.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
internal final class IQActiveConfiguration {
private let keyboardListener: IQKeyboardListener = IQKeyboardListener()
private let textFieldViewListener: IQTextFieldViewListener = IQTextFieldViewListener()
private var changeObservers: [AnyHashable: ConfigurationCompletion] = [:]
@objc public enum Event: Int {
case hide
case show
case change
var name: String {
switch self {
case .hide:
return "hide"
case .show:
return "show"
case .change:
return "change"
}
}
}
private var lastEvent: Event = .hide
var rootControllerConfiguration: IQRootControllerConfiguration?
var isReady: Bool {
if textFieldViewInfo != nil,
let rootControllerConfiguration = rootControllerConfiguration {
return rootControllerConfiguration.isReady
}
return false
}
init() {
addKeyboardListener()
addTextFieldViewListener()
}
private func sendEvent() {
if let textFieldViewInfo = textFieldViewInfo,
let rootControllerConfiguration = rootControllerConfiguration,
rootControllerConfiguration.isReady {
if keyboardInfo.keyboardShowing {
if lastEvent == .hide {
self.notify(event: .show, keyboardInfo: keyboardInfo, textFieldViewInfo: textFieldViewInfo)
} else {
self.notify(event: .change, keyboardInfo: keyboardInfo, textFieldViewInfo: textFieldViewInfo)
}
} else if lastEvent != .hide {
if rootControllerConfiguration.beginOrientation == rootControllerConfiguration.currentOrientation {
self.notify(event: .hide, keyboardInfo: keyboardInfo, textFieldViewInfo: textFieldViewInfo)
self.rootControllerConfiguration = nil
} else if rootControllerConfiguration.hasChanged {
animate(alongsideTransition: {
rootControllerConfiguration.restore()
}, completion: nil)
}
}
}
}
private func updateRootController(info: IQTextFieldViewInfo?) {
guard let info = info,
let controller: UIViewController = info.textFieldView.iq.parentContainerViewController() else {
if let rootControllerConfiguration = rootControllerConfiguration,
rootControllerConfiguration.hasChanged {
animate(alongsideTransition: {
rootControllerConfiguration.restore()
}, completion: nil)
}
rootControllerConfiguration = nil
return
}
let newConfiguration = IQRootControllerConfiguration(rootController: controller)
if newConfiguration.rootController.view.window != rootControllerConfiguration?.rootController.view.window ||
newConfiguration.beginOrientation != rootControllerConfiguration?.beginOrientation {
if rootControllerConfiguration?.rootController != newConfiguration.rootController {
// If there was an old configuration but things are changed
if let rootControllerConfiguration = rootControllerConfiguration,
rootControllerConfiguration.hasChanged {
animate(alongsideTransition: {
rootControllerConfiguration.restore()
}, completion: nil)
}
}
rootControllerConfiguration = newConfiguration
}
}
}
@available(iOSApplicationExtension, unavailable)
extension IQActiveConfiguration {
var keyboardInfo: IQKeyboardInfo {
return keyboardListener.keyboardInfo
}
private func addKeyboardListener() {
keyboardListener.registerSizeChange(identifier: "IQActiveConfiguration", changeHandler: { [self] name, _ in
if let info = textFieldViewInfo, keyboardInfo.keyboardShowing {
if let rootControllerConfiguration = rootControllerConfiguration {
let beginIsPortrait: Bool = rootControllerConfiguration.beginOrientation.isPortrait
let currentIsPortrait: Bool = rootControllerConfiguration.currentOrientation.isPortrait
if beginIsPortrait != currentIsPortrait {
updateRootController(info: info)
}
} else {
updateRootController(info: info)
}
}
self.sendEvent()
if name == .didHide {
updateRootController(info: nil)
}
})
}
public func animate(alongsideTransition transition: @escaping () -> Void, completion: (() -> Void)? = nil) {
keyboardListener.animate(alongsideTransition: transition, completion: completion)
}
}
@available(iOSApplicationExtension, unavailable)
extension IQActiveConfiguration {
var textFieldViewInfo: IQTextFieldViewInfo? {
return textFieldViewListener.textFieldViewInfo
}
private func addTextFieldViewListener() {
textFieldViewListener.registerTextFieldViewChange(identifier: "IQActiveConfiguration",
changeHandler: { [self] info in
if info.name == .beginEditing {
updateRootController(info: info)
self.sendEvent()
}
})
}
}
@available(iOSApplicationExtension, unavailable)
extension IQActiveConfiguration {
typealias ConfigurationCompletion = (_ event: Event,
_ keyboardInfo: IQKeyboardInfo,
_ textFieldInfo: IQTextFieldViewInfo) -> Void
func registerChange(identifier: AnyHashable, changeHandler: @escaping ConfigurationCompletion) {
changeObservers[identifier] = changeHandler
}
func unregisterChange(identifier: AnyHashable) {
changeObservers[identifier] = nil
}
private func notify(event: Event, keyboardInfo: IQKeyboardInfo, textFieldViewInfo: IQTextFieldViewInfo) {
lastEvent = event
for block in changeObservers.values {
block(event, keyboardInfo, textFieldViewInfo)
}
}
}

View File

@ -1,96 +0,0 @@
//
// IQBarButtonItemConfiguration.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
/**
IQBarButtonItemConfiguration for creating toolbar with bar button items
*/
@available(iOSApplicationExtension, unavailable)
@MainActor
@objc public final class IQBarButtonItemConfiguration: NSObject {
@objc public init(systemItem: UIBarButtonItem.SystemItem, action: Selector? = nil) {
self.systemItem = systemItem
self.image = nil
self.title = nil
self.action = action
super.init()
}
@objc public init(image: UIImage, action: Selector? = nil) {
self.systemItem = nil
self.image = image
self.title = nil
self.action = action
super.init()
}
@objc public init(title: String, action: Selector? = nil) {
self.systemItem = nil
self.image = nil
self.title = title
self.action = action
super.init()
}
public let systemItem: UIBarButtonItem.SystemItem? // System Item to be used to instantiate bar button.
@objc public let image: UIImage? // Image to show on bar button item if it's not a system item.
@objc public let title: String? // Title to show on bar button item if it's not a system item.
@objc public var action: Selector? // action for bar button item. Usually 'doneAction:(IQBarButtonItem*)item'.
public override var accessibilityLabel: String? { didSet { } } // Accessibility related labels
func apply(on oldBarButtonItem: IQBarButtonItem, target: AnyObject?) -> IQBarButtonItem {
var newBarButtonItem: IQBarButtonItem = oldBarButtonItem
if systemItem == nil, !oldBarButtonItem.isSystemItem {
newBarButtonItem.title = title
newBarButtonItem.accessibilityLabel = accessibilityLabel
newBarButtonItem.accessibilityIdentifier = newBarButtonItem.accessibilityLabel
newBarButtonItem.image = image
newBarButtonItem.target = target
newBarButtonItem.action = action
} else {
if let systemItem: UIBarButtonItem.SystemItem = systemItem {
newBarButtonItem = IQBarButtonItem(barButtonSystemItem: systemItem, target: target, action: action)
newBarButtonItem.isSystemItem = true
} else if let image: UIImage = image {
newBarButtonItem = IQBarButtonItem(image: image, style: .plain, target: target, action: action)
} else {
newBarButtonItem = IQBarButtonItem(title: title, style: .plain, target: target, action: action)
}
newBarButtonItem.invocation = oldBarButtonItem.invocation
newBarButtonItem.accessibilityLabel = accessibilityLabel
newBarButtonItem.accessibilityIdentifier = oldBarButtonItem.accessibilityLabel
newBarButtonItem.isEnabled = oldBarButtonItem.isEnabled
newBarButtonItem.tag = oldBarButtonItem.tag
}
return newBarButtonItem
}
}

View File

@ -1,39 +0,0 @@
//
// IQKeyboardConfiguration.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 final class IQKeyboardConfiguration: NSObject {
/**
Override the keyboardAppearance for all textField/textView. Default is NO.
*/
@objc public var overrideAppearance: Bool = false
/**
If overrideKeyboardAppearance is YES, then all the textField keyboardAppearance is set using this property.
*/
@objc public var appearance: UIKeyboardAppearance = UIKeyboardAppearance.default
}

View File

@ -1,95 +0,0 @@
//
// IQRootControllerConfiguration.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
internal struct IQRootControllerConfiguration {
let rootController: UIViewController
let beginOrigin: CGPoint
let beginSafeAreaInsets: UIEdgeInsets
let beginOrientation: UIInterfaceOrientation
init(rootController: UIViewController) {
self.rootController = rootController
beginOrigin = rootController.view.frame.origin
beginSafeAreaInsets = rootController.view.safeAreaInsets
let interfaceOrientation: UIInterfaceOrientation
#if swift(>=5.1)
if #available(iOS 13, *) {
if let scene = rootController.view.window?.windowScene {
interfaceOrientation = scene.interfaceOrientation
} else {
interfaceOrientation = .unknown
}
} else {
interfaceOrientation = UIApplication.shared.statusBarOrientation
}
#else
interfaceOrientation = UIApplication.shared.statusBarOrientation
#endif
beginOrientation = interfaceOrientation
}
var currentOrientation: UIInterfaceOrientation {
let interfaceOrientation: UIInterfaceOrientation
#if swift(>=5.1)
if #available(iOS 13, *) {
if let scene = rootController.view.window?.windowScene {
interfaceOrientation = scene.interfaceOrientation
} else {
interfaceOrientation = .unknown
}
} else {
interfaceOrientation = UIApplication.shared.statusBarOrientation
}
#else
interfaceOrientation = UIApplication.shared.statusBarOrientation
#endif
return interfaceOrientation
}
var isReady: Bool {
return rootController.view.window != nil
}
var hasChanged: Bool {
return !rootController.view.frame.origin.equalTo(beginOrigin)
}
@discardableResult
func restore() -> Bool {
if !rootController.view.frame.origin.equalTo(beginOrigin) {
// Setting it's new frame
var rect: CGRect = rootController.view.frame
rect.origin = beginOrigin
rootController.view.frame = rect
return true
}
return false
}
}

View File

@ -1,115 +0,0 @@
//
// IQScrollViewConfiguration.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
internal struct IQScrollViewConfiguration {
let scrollView: UIScrollView
let startingContentOffset: CGPoint
let startingScrollIndicatorInsets: UIEdgeInsets
let startingContentInset: UIEdgeInsets
private let canRestoreContentOffset: Bool
init(scrollView: UIScrollView, canRestoreContentOffset: Bool) {
self.scrollView = scrollView
self.canRestoreContentOffset = canRestoreContentOffset
startingContentOffset = scrollView.contentOffset
startingContentInset = scrollView.contentInset
#if swift(>=5.1)
if #available(iOS 11.1, *) {
startingScrollIndicatorInsets = scrollView.verticalScrollIndicatorInsets
} else {
startingScrollIndicatorInsets = scrollView.scrollIndicatorInsets
}
#else
startingScrollIndicatorInsets = scrollView.scrollIndicatorInsets
#endif
}
var hasChanged: Bool {
if scrollView.contentInset != self.startingContentInset {
return true
}
if canRestoreContentOffset,
scrollView.iq.restoreContentOffset,
!scrollView.contentOffset.equalTo(startingContentOffset) {
return true
}
return false
}
@discardableResult
func restore(for textFieldView: UIView?) -> Bool {
var success: Bool = false
if scrollView.contentInset != self.startingContentInset {
scrollView.contentInset = self.startingContentInset
scrollView.layoutIfNeeded() // (Bug ID: #1996)
success = true
}
#if swift(>=5.1)
if #available(iOS 11.1, *) {
if scrollView.verticalScrollIndicatorInsets != self.startingScrollIndicatorInsets {
scrollView.verticalScrollIndicatorInsets = self.startingScrollIndicatorInsets
}
} else {
if scrollView.scrollIndicatorInsets != self.startingScrollIndicatorInsets {
scrollView.scrollIndicatorInsets = self.startingScrollIndicatorInsets
}
}
#else
if scrollView.scrollIndicatorInsets != self.startingScrollIndicatorInsets {
scrollView.scrollIndicatorInsets = self.startingScrollIndicatorInsets
}
#endif
if canRestoreContentOffset,
scrollView.iq.restoreContentOffset,
!scrollView.contentOffset.equalTo(startingContentOffset) {
// (Bug ID: #1365, #1508, #1541)
let stackView: UIStackView? = textFieldView?.iq.superviewOf(type: UIStackView.self,
belowView: scrollView)
// (Bug ID: #1901, #1996)
let animatedContentOffset: Bool = stackView != nil ||
scrollView is UICollectionView ||
scrollView is UITableView
if animatedContentOffset {
scrollView.setContentOffset(startingContentOffset, animated: UIView.areAnimationsEnabled)
} else {
scrollView.contentOffset = startingContentOffset
}
success = true
}
return success
}
}

View File

@ -1,75 +0,0 @@
//
// IQToolbarConfiguration.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 final class IQToolbarConfiguration: NSObject {
/**
If YES, then uses textField's tintColor property for IQToolbar, otherwise tint color is default. Default is NO.
*/
@objc public var useTextFieldTintColor: Bool = false
/**
This is used for toolbar.tintColor when textfield.keyboardAppearance is UIKeyboardAppearanceDefault.
If useTextFieldTintColor is YES then this property is ignored. Default is nil and uses black color.
*/
@objc public var tintColor: UIColor?
/**
This is used for toolbar.barTintColor. Default is nil.
*/
@objc public var barTintColor: UIColor?
/**
IQPreviousNextDisplayModeDefault: Show NextPrevious when there are more than 1 textField otherwise hide.
IQPreviousNextDisplayModeAlwaysHide: Do not show NextPrevious buttons in any case.
IQPreviousNextDisplayModeAlwaysShow: Always show nextPrevious buttons,
if there are more than 1 textField then both buttons will be visible but will be shown as disabled.
*/
@objc public var previousNextDisplayMode: IQPreviousNextDisplayMode = .default
/**
/**
IQAutoToolbarBySubviews: Creates Toolbar according to subview's hierarchy of Textfield's in view.
IQAutoToolbarByTag: Creates Toolbar according to tag property of TextField's.
IQAutoToolbarByPosition: Creates Toolbar according to the y,x position
of textField in it's superview coordinate.
Default is IQAutoToolbarBySubviews.
*/
AutoToolbar managing behavior. Default is IQAutoToolbarBySubviews.
*/
@objc public var manageBehavior: IQAutoToolbarManageBehavior = .bySubviews
/**
Buttons configuration displayed on the toolbar, the selector parameter is ignored in below configuration
*/
@objc public var previousBarButtonConfiguration: IQBarButtonItemConfiguration?
@objc public var nextBarButtonConfiguration: IQBarButtonItemConfiguration?
@objc public var doneBarButtonConfiguration: IQBarButtonItemConfiguration?
@objc public let placeholderConfiguration: IQToolbarPlaceholderConfiguration = .init()
}

View File

@ -1,54 +0,0 @@
//
// IQToolbarPlaceholderConfiguration.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 final class IQToolbarPlaceholderConfiguration: NSObject {
/**
If YES, then it add the textField's placeholder text on IQToolbar. Default is YES.
*/
@objc public var showPlaceholder: Bool = true
/**
Placeholder Font. Default is nil.
*/
@objc public var font: UIFont?
/**
Placeholder Color. Default is nil. Which means lightGray
*/
@objc public var color: UIColor?
/**
Placeholder Button Color when it's treated as button. Default is nil.
*/
@objc public var buttonColor: UIColor?
/**
Placeholder accessibility Label
*/
public override var accessibilityLabel: String? { didSet { } }
}

View File

@ -1,71 +0,0 @@
//
// IQKeyboardManager+Debug.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
// MARK: Debugging & Developer options
@available(iOSApplicationExtension, unavailable)
public extension IQKeyboardManager {
@MainActor
private struct AssociatedKeys {
static var enableDebugging: Int = 0
}
@objc var enableDebugging: Bool {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.enableDebugging) as? Bool ?? false
}
set(newValue) {
objc_setAssociatedObject(self, &AssociatedKeys.enableDebugging,
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
@MainActor
struct Static {
static var indentation = 0
}
internal func showLog(_ logString: String, indentation: Int = 0) {
guard enableDebugging else {
return
}
if indentation < 0 {
Static.indentation = max(0, Static.indentation + indentation)
}
var preLog: String = "IQKeyboardManager"
for _ in 0 ... Static.indentation {
preLog += "|\t"
}
print(preLog + logString)
if indentation > 0 {
Static.indentation += indentation
}
}
}

View File

@ -1,220 +0,0 @@
//
// IQKeyboardManager.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
// swiftlint:disable unused_setter_value
// swiftlint:disable identifier_name
// swiftlint:disable line_length
@available(iOSApplicationExtension, unavailable)
public extension IQKeyboardManager {
@available(*, unavailable, renamed: "resignOnTouchOutside")
@objc var shouldResignOnTouchOutside: Bool {
get { false }
set { }
}
@available(*, unavailable, renamed: "playInputClicks")
@objc var shouldPlayInputClicks: Bool {
get { false }
set { }
}
@available(*, unavailable, message: "This feature has been removed due to few compatibility problems")
@objc func registerTextFieldViewClass(_ aClass: UIView.Type,
didBeginEditingNotificationName: String,
didEndEditingNotificationName: String) {
}
@available(*, unavailable, message: "This feature has been removed due to few compatibility problems")
@objc func unregisterTextFieldViewClass(_ aClass: UIView.Type,
didBeginEditingNotificationName: String,
didEndEditingNotificationName: String) {
}
}
@available(iOSApplicationExtension, unavailable)
public extension IQKeyboardManager {
@available(*, unavailable, renamed: "toolbarConfiguration.manageBehavior")
@objc var toolbarManageBehaviour: IQAutoToolbarManageBehavior {
get { .bySubviews }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.useTextFieldTintColor")
@objc var shouldToolbarUsesTextFieldTintColor: Bool {
get { false }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.tintColor")
@objc var toolbarTintColor: UIColor? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.barTintColor")
@objc var toolbarBarTintColor: UIColor? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.previousNextDisplayMode")
@objc var previousNextDisplayMode: IQPreviousNextDisplayMode {
get { .default }
set { }
}
}
@available(iOSApplicationExtension, unavailable)
public extension IQKeyboardManager {
@available(*, unavailable, renamed: "toolbarConfiguration.previousBarButtonConfiguration.image",
message: "To change, please assign a new toolbarConfiguration.previousBarButtonConfiguration")
@objc var toolbarPreviousBarButtonItemImage: UIImage? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.previousBarButtonConfiguration.title",
message: "To change, please assign a new toolbarConfiguration.previousBarButtonConfiguration")
@objc var toolbarPreviousBarButtonItemText: String? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.previousBarButtonConfiguration.accessibilityLabel",
message: "To change, please assign a new toolbarConfiguration.previousBarButtonConfiguration")
@objc var toolbarPreviousBarButtonItemAccessibilityLabel: String? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.nextBarButtonConfiguration.image",
message: "To change, please assign a new toolbarConfiguration.nextBarButtonConfiguration")
@objc var toolbarNextBarButtonItemImage: UIImage? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.nextBarButtonConfiguration.title",
message: "To change, please assign a new toolbarConfiguration.nextBarButtonConfiguration")
@objc var toolbarNextBarButtonItemText: String? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.nextBarButtonConfiguration.accessibilityLabel",
message: "To change, please assign a new toolbarConfiguration.nextBarButtonConfiguration")
@objc var toolbarNextBarButtonItemAccessibilityLabel: String? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.doneBarButtonConfiguration.image",
message: "To change, please assign a new toolbarConfiguration.doneBarButtonConfiguration")
@objc var toolbarDoneBarButtonItemImage: UIImage? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.doneBarButtonConfiguration.title",
message: "To change, please assign a new toolbarConfiguration.doneBarButtonConfiguration")
@objc var toolbarDoneBarButtonItemText: String? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.doneBarButtonConfiguration.accessibilityLabel",
message: "To change, please assign a new toolbarConfiguration.doneBarButtonConfiguration")
@objc var toolbarDoneBarButtonItemAccessibilityLabel: String? {
get { nil }
set { }
}
}
@available(iOSApplicationExtension, unavailable)
public extension IQKeyboardManager {
@available(*, unavailable, renamed: "toolbarConfiguration.placeholderConfiguration.accessibilityLabel")
@objc var toolbarTitlBarButtonItemAccessibilityLabel: String? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.placeholderConfiguration.showPlaceholder")
@objc var shouldShowToolbarPlaceholder: Bool {
get { false }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.placeholderConfiguration.font")
@objc var placeholderFont: UIFont? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.placeholderConfiguration.color")
@objc var placeholderColor: UIColor? {
get { nil }
set { }
}
@available(*, unavailable, renamed: "toolbarConfiguration.placeholderConfiguration.buttonColor")
@objc var placeholderButtonColor: UIColor? {
get { nil }
set { }
}
}
@available(iOSApplicationExtension, unavailable)
public extension IQKeyboardManager {
@available(*, unavailable, renamed: "keyboardConfiguration.overrideAppearance")
@objc var overrideKeyboardAppearance: Bool {
get { false }
set { }
}
@available(*, unavailable, renamed: "keyboardConfiguration.appearance")
@objc var keyboardAppearance: UIKeyboardAppearance {
get { .default }
set { }
}
}
@available(iOSApplicationExtension, unavailable)
public extension IQKeyboardManager {
typealias SizeBlock = (_ size: CGSize) -> Void
@available(*, unavailable, message: "This feature has been moved to IQKeyboardListener, use it directly by creating new instance")
@objc func registerKeyboardSizeChange(identifier: AnyHashable, sizeHandler: @escaping SizeBlock) {}
@available(*, unavailable, message: "This feature has been moved to IQKeyboardListener, use it directly by creating new instance")
@objc func unregisterKeyboardSizeChange(identifier: AnyHashable) {}
@available(*, unavailable, message: "This feature has been moved to IQKeyboardListener, use it directly by creating new instance")
@objc var keyboardShowing: Bool { false }
@available(*, unavailable, message: "This feature has been moved to IQKeyboardListener, use it directly by creating new instance")
@objc var keyboardFrame: CGRect { .zero }
}
// swiftlint:enable unused_setter_value
// swiftlint:enable identifier_name
// swiftlint:enable line_length

View File

@ -1,239 +0,0 @@
//
// IQKeyboardManager+Internal.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)
internal extension IQKeyboardManager {
/** Get all UITextField/UITextView siblings of textFieldView. */
func responderViews() -> [UIView]? {
guard let textFieldView: UIView = activeConfiguration.textFieldViewInfo?.textFieldView else {
return nil
}
var superConsideredView: UIView?
// If find any consider responderView in it's upper hierarchy then will get deepResponderView.
for allowedClass in toolbarPreviousNextAllowedClasses {
superConsideredView = textFieldView.iq.superviewOf(type: allowedClass)
if superConsideredView != nil {
break
}
}
var swiftUIHostingView: UIView?
let swiftUIHostingViewName: String = "UIHostingView<"
var superView: UIView? = textFieldView.superview
while let unwrappedSuperView: UIView = superView {
let classNameString: String = {
var name: String = "\(type(of: unwrappedSuperView.self))"
if name.hasPrefix("_") {
name.removeFirst()
}
return name
}()
if classNameString.hasPrefix(swiftUIHostingViewName) {
swiftUIHostingView = unwrappedSuperView
break
}
superView = unwrappedSuperView.superview
}
// (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 textFields: [UIView] = textFieldView.iq.responderSiblings()
// Sorting textFields according to behavior
switch toolbarConfiguration.manageBehavior {
// If autoToolbar behavior is bySubviews, then returning it.
case .bySubviews: return textFields
// If autoToolbar behavior is by tag, then sorting it according to tag property.
case .byTag: return textFields.sortedByTag()
// If autoToolbar behavior is by tag, then sorting it according to tag property.
case .byPosition: return textFields.sortedByPosition()
}
}
}
func privateIsEnabled() -> Bool {
var isEnabled: Bool = enable
guard let textFieldViewInfo: IQTextFieldViewInfo = activeConfiguration.textFieldViewInfo else {
return isEnabled
}
let enableMode: IQEnableMode = textFieldViewInfo.textFieldView.iq.enableMode
if enableMode == .enabled {
isEnabled = true
} else if enableMode == .disabled {
isEnabled = false
} else if var textFieldViewController = textFieldViewInfo.textFieldView.iq.viewContainingController() {
// If it is searchBar textField embedded in Navigation Bar
if textFieldViewInfo.textFieldView.iq.textFieldSearchBar() != nil,
let navController: UINavigationController = textFieldViewController as? UINavigationController,
let topController: UIViewController = navController.topViewController {
textFieldViewController = topController
}
// If viewController is kind of enable viewController class, then assuming it's enabled.
if !isEnabled, enabledDistanceHandlingClasses.contains(where: { textFieldViewController.isKind(of: $0) }) {
isEnabled = true
}
if isEnabled {
// If viewController is kind of disabled viewController class, then assuming it's disabled.
if disabledDistanceHandlingClasses.contains(where: { textFieldViewController.isKind(of: $0) }) {
isEnabled = false
}
// Special Controllers
if isEnabled {
let classNameString: String = "\(type(of: textFieldViewController.self))"
// _UIAlertControllerTextFieldViewController
if classNameString.contains("UIAlertController"),
classNameString.hasSuffix("TextFieldViewController") {
isEnabled = false
}
}
}
}
return isEnabled
}
func privateIsEnableAutoToolbar() -> Bool {
var isEnabled: Bool = enableAutoToolbar
guard let textFieldViewInfo: IQTextFieldViewInfo = activeConfiguration.textFieldViewInfo,
var textFieldViewController = textFieldViewInfo.textFieldView.iq.viewContainingController() else {
return isEnabled
}
// If it is searchBar textField embedded in Navigation Bar
if textFieldViewInfo.textFieldView.iq.textFieldSearchBar() != nil,
let navController: UINavigationController = textFieldViewController as? UINavigationController,
let topController: UIViewController = navController.topViewController {
textFieldViewController = topController
}
if !isEnabled, enabledToolbarClasses.contains(where: { textFieldViewController.isKind(of: $0) }) {
isEnabled = true
}
if isEnabled {
// If found any toolbar disabled classes then return.
if disabledToolbarClasses.contains(where: { textFieldViewController.isKind(of: $0) }) {
isEnabled = false
}
// Special Controllers
if isEnabled {
let classNameString: String = "\(type(of: textFieldViewController.self))"
// _UIAlertControllerTextFieldViewController
if classNameString.contains("UIAlertController"), classNameString.hasSuffix("TextFieldViewController") {
isEnabled = false
}
}
}
return isEnabled
}
func privateResignOnTouchOutside() -> Bool {
var isEnabled: Bool = resignOnTouchOutside
guard let textFieldViewInfo: IQTextFieldViewInfo = activeConfiguration.textFieldViewInfo else {
return isEnabled
}
let enableMode: IQEnableMode = textFieldViewInfo.textFieldView.iq.resignOnTouchOutsideMode
if enableMode == .enabled {
isEnabled = true
} else if enableMode == .disabled {
isEnabled = false
} else if var textFieldViewController = textFieldViewInfo.textFieldView.iq.viewContainingController() {
// If it is searchBar textField embedded in Navigation Bar
if textFieldViewInfo.textFieldView.iq.textFieldSearchBar() != nil,
let navController: UINavigationController = textFieldViewController as? UINavigationController,
let topController: UIViewController = navController.topViewController {
textFieldViewController = topController
}
// If viewController is kind of enable viewController class, then assuming resignOnTouchOutside is enabled.
if !isEnabled,
enabledTouchResignedClasses.contains(where: { textFieldViewController.isKind(of: $0) }) {
isEnabled = true
}
if isEnabled {
// If viewController is kind of disable viewController class,
// then assuming resignOnTouchOutside is disable.
if disabledTouchResignedClasses.contains(where: { textFieldViewController.isKind(of: $0) }) {
isEnabled = false
}
// Special Controllers
if isEnabled {
let classNameString: String = "\(type(of: textFieldViewController.self))"
// _UIAlertControllerTextFieldViewController
if classNameString.contains("UIAlertController"),
classNameString.hasSuffix("TextFieldViewController") {
isEnabled = false
}
}
}
}
return isEnabled
}
}

View File

@ -1,736 +0,0 @@
//
// IQKeyboardManager+Position.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
// swiftlint:disable file_length
@available(iOSApplicationExtension, unavailable)
public extension IQKeyboardManager {
@MainActor
private struct AssociatedKeys {
static var movedDistance: Int = 0
static var movedDistanceChanged: Int = 0
static var lastScrollViewConfiguration: Int = 0
static var startingTextViewConfiguration: Int = 0
static var activeConfiguration: Int = 0
}
/**
moved distance to the top used to maintain distance between keyboard and textField.
Most of the time this will be a positive value.
*/
private(set) var movedDistance: CGFloat {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.movedDistance) as? CGFloat ?? 0.0
}
set(newValue) {
objc_setAssociatedObject(self, &AssociatedKeys.movedDistance, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
movedDistanceChanged?(movedDistance)
}
}
/**
Will be called then movedDistance will be changed
*/
@objc var movedDistanceChanged: ((CGFloat) -> Void)? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.movedDistanceChanged) as? ((CGFloat) -> Void)
}
set(newValue) {
objc_setAssociatedObject(self, &AssociatedKeys.movedDistanceChanged,
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
movedDistanceChanged?(movedDistance)
}
}
/** Variable to save lastScrollView that was scrolled. */
internal var lastScrollViewConfiguration: IQScrollViewConfiguration? {
get {
return objc_getAssociatedObject(self,
&AssociatedKeys.lastScrollViewConfiguration) as? IQScrollViewConfiguration
}
set(newValue) {
objc_setAssociatedObject(self, &AssociatedKeys.lastScrollViewConfiguration,
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
/** used to adjust contentInset of UITextView. */
internal var startingTextViewConfiguration: IQScrollViewConfiguration? {
get {
return objc_getAssociatedObject(self,
&AssociatedKeys.startingTextViewConfiguration) as? IQScrollViewConfiguration
}
set(newValue) {
objc_setAssociatedObject(self, &AssociatedKeys.startingTextViewConfiguration,
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
internal func addActiveConfigurationObserver() {
activeConfiguration.registerChange(identifier: UUID().uuidString, changeHandler: { event, _, _ in
switch event {
case .show:
self.handleKeyboardTextFieldViewVisible()
case .change:
self.handleKeyboardTextFieldViewChanged()
case .hide:
self.handleKeyboardTextFieldViewHide()
}
})
}
@objc internal func applicationDidBecomeActive(_ notification: Notification) {
guard privateIsEnabled(),
activeConfiguration.keyboardInfo.keyboardShowing,
activeConfiguration.isReady else {
return
}
adjustPosition()
}
/* Adjusting RootViewController's frame according to interface orientation. */
// swiftlint:disable cyclomatic_complexity
// swiftlint:disable function_body_length
internal func adjustPosition() {
// We are unable to get textField object while keyboard showing on WKWebView's textField. (Bug ID: #11)
guard UIApplication.shared.applicationState == .active,
let textFieldView: UIView = activeConfiguration.textFieldViewInfo?.textFieldView,
let superview: UIView = textFieldView.superview,
let rootConfiguration = activeConfiguration.rootControllerConfiguration,
let window: UIWindow = rootConfiguration.rootController.view.window else {
return
}
showLog(">>>>> \(#function) started >>>>>", indentation: 1)
let startTime: CFTimeInterval = CACurrentMediaTime()
let rootController: UIViewController = rootConfiguration.rootController
let textFieldViewRectInWindow: CGRect = superview.convert(textFieldView.frame, to: window)
let textFieldViewRectInRootSuperview: CGRect = superview.convert(textFieldView.frame,
to: rootController.view.superview)
// Getting RootViewOrigin.
var rootViewOrigin: CGPoint = rootController.view.frame.origin
let keyboardDistance: CGFloat
do {
// Maintain keyboardDistanceFromTextField
let specialKeyboardDistanceFromTextField: CGFloat
if let searchBar: UIView = textFieldView.iq.textFieldSearchBar() {
specialKeyboardDistanceFromTextField = searchBar.iq.distanceFromKeyboard
} else {
specialKeyboardDistanceFromTextField = textFieldView.iq.distanceFromKeyboard
}
if specialKeyboardDistanceFromTextField == UIView.defaultKeyboardDistance {
keyboardDistance = keyboardDistanceFromTextField
} else {
keyboardDistance = specialKeyboardDistanceFromTextField
}
}
let kbSize: CGSize
let originalKbSize: CGSize = activeConfiguration.keyboardInfo.frame.size
do {
var kbFrame: CGRect = activeConfiguration.keyboardInfo.frame
kbFrame.origin.y -= keyboardDistance
kbFrame.size.height += keyboardDistance
kbFrame.origin.y -= rootConfiguration.beginSafeAreaInsets.bottom
kbFrame.size.height += rootConfiguration.beginSafeAreaInsets.bottom
// (Bug ID: #469) (Bug ID: #381) (Bug ID: #1506)
// Calculating actual keyboard covered size respect to window,
// keyboard frame may be different when hardware keyboard is attached
let intersectRect: CGRect = kbFrame.intersection(window.frame)
if intersectRect.isNull {
kbSize = CGSize(width: kbFrame.size.width, height: 0)
} else {
kbSize = intersectRect.size
}
}
let statusBarHeight: CGFloat
let navigationBarAreaHeight: CGFloat
if let navigationController: UINavigationController = rootController.navigationController {
navigationBarAreaHeight = navigationController.navigationBar.frame.maxY
} else {
#if swift(>=5.1)
if #available(iOS 13, *) {
statusBarHeight = window.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
} else {
statusBarHeight = UIApplication.shared.statusBarFrame.height
}
#else
statusBarHeight = UIApplication.shared.statusBarFrame.height
#endif
navigationBarAreaHeight = statusBarHeight
}
let isScrollableTextView: Bool
if let textView: UIScrollView = textFieldView as? UIScrollView,
textFieldView.responds(to: #selector(getter: UITextView.isEditable)) {
isScrollableTextView = textView.isScrollEnabled
} else {
isScrollableTextView = false
}
let directionalLayoutMargin: NSDirectionalEdgeInsets = rootController.view.directionalLayoutMargins
let topLayoutGuide: CGFloat = CGFloat.maximum(navigationBarAreaHeight, directionalLayoutMargin.top)
// Validation of textView for case where there is a tab bar
// at the bottom or running on iPhone X and textView is at the bottom.
let bottomLayoutGuide: CGFloat = isScrollableTextView ? 0 : directionalLayoutMargin.bottom
// Move positive = textField is hidden.
// Move negative = textField is showing.
// Calculating move position.
var moveUp: CGFloat
do {
let visibleHeight: CGFloat = window.frame.height-kbSize.height
let topMovement: CGFloat = textFieldViewRectInRootSuperview.minY-topLayoutGuide
let bottomMovement: CGFloat = textFieldViewRectInWindow.maxY - visibleHeight + bottomLayoutGuide
moveUp = CGFloat.minimum(topMovement, bottomMovement)
moveUp = CGFloat(Int(moveUp))
}
showLog("Need to move: \(moveUp), will be moving \(moveUp < 0 ? "down" : "up")")
var superScrollView: UIScrollView?
var superView: UIScrollView? = textFieldView.iq.superviewOf(type: UIScrollView.self)
// Getting UIScrollView whose scrolling is enabled. // (Bug ID: #285)
while let view: UIScrollView = superView {
if view.isScrollEnabled, !view.iq.ignoreScrollingAdjustment {
superScrollView = view
break
} else {
// Getting it's superScrollView. // (Enhancement ID: #21, #24)
superView = view.iq.superviewOf(type: UIScrollView.self)
}
}
// If there was a lastScrollView. // (Bug ID: #34)
if let lastConfiguration: IQScrollViewConfiguration = lastScrollViewConfiguration {
// If we can't find current superScrollView, then setting lastScrollView to it's original form.
if superScrollView == nil {
if lastConfiguration.hasChanged {
if lastConfiguration.scrollView.contentInset != lastConfiguration.startingContentInset {
showLog("Restoring contentInset to: \(lastConfiguration.startingContentInset)")
}
if lastConfiguration.scrollView.iq.restoreContentOffset,
!lastConfiguration.scrollView.contentOffset.equalTo(lastConfiguration.startingContentOffset) {
showLog("Restoring contentOffset to: \(lastConfiguration.startingContentOffset)")
}
activeConfiguration.animate(alongsideTransition: {
lastConfiguration.restore(for: textFieldView)
})
}
self.lastScrollViewConfiguration = nil
} else if superScrollView != lastConfiguration.scrollView {
// If both scrollView's are different,
// then reset lastScrollView to it's original frame and setting current scrollView as last scrollView.
if lastConfiguration.hasChanged {
if lastConfiguration.scrollView.contentInset != lastConfiguration.startingContentInset {
showLog("Restoring contentInset to: \(lastConfiguration.startingContentInset)")
}
if lastConfiguration.scrollView.iq.restoreContentOffset,
!lastConfiguration.scrollView.contentOffset.equalTo(lastConfiguration.startingContentOffset) {
showLog("Restoring contentOffset to: \(lastConfiguration.startingContentOffset)")
}
activeConfiguration.animate(alongsideTransition: {
lastConfiguration.restore(for: textFieldView)
})
}
if let superScrollView = superScrollView {
let configuration = IQScrollViewConfiguration(scrollView: superScrollView,
canRestoreContentOffset: true)
self.lastScrollViewConfiguration = configuration
showLog("""
Saving ScrollView New contentInset: \(configuration.startingContentInset)
and contentOffset: \(configuration.startingContentOffset)
""")
} else {
self.lastScrollViewConfiguration = nil
}
}
// Else the case where superScrollView == lastScrollView means we are on same scrollView
// after switching to different textField. So doing nothing, going ahead
} else if let superScrollView: UIScrollView = superScrollView {
// If there was no lastScrollView and we found a current scrollView. then setting it as lastScrollView.
let configuration = IQScrollViewConfiguration(scrollView: superScrollView, canRestoreContentOffset: true)
self.lastScrollViewConfiguration = configuration
showLog("""
Saving ScrollView New contentInset: \(configuration.startingContentInset)
and contentOffset: \(configuration.startingContentOffset)
""")
}
// Special case for ScrollView.
// If we found lastScrollView then setting it's contentOffset to show textField.
if let lastScrollViewConfiguration: IQScrollViewConfiguration = lastScrollViewConfiguration {
// Saving
var lastView: UIView = textFieldView
var superScrollView: UIScrollView? = lastScrollViewConfiguration.scrollView
while let scrollView: UIScrollView = superScrollView {
var isContinue: Bool = false
if moveUp > 0 {
isContinue = moveUp > (-scrollView.contentOffset.y - scrollView.contentInset.top)
} else if let tableView: UITableView = scrollView.iq.superviewOf(type: UITableView.self) {
// Special treatment for UITableView due to their cell reusing logic
isContinue = scrollView.contentOffset.y > 0
if isContinue,
let tableCell: UITableViewCell = textFieldView.iq.superviewOf(type: UITableViewCell.self),
let indexPath: IndexPath = tableView.indexPath(for: tableCell),
let previousIndexPath: IndexPath = tableView.previousIndexPath(of: indexPath) {
let previousCellRect: CGRect = tableView.rectForRow(at: previousIndexPath)
if !previousCellRect.isEmpty {
let superview: UIView? = rootController.view.superview
let previousCellRectInRootSuperview: CGRect = tableView.convert(previousCellRect,
to: superview)
moveUp = CGFloat.minimum(0, previousCellRectInRootSuperview.maxY - topLayoutGuide)
}
}
} else if let collectionView = scrollView.iq.superviewOf(type: UICollectionView.self) {
// Special treatment for UICollectionView due to their cell reusing logic
isContinue = scrollView.contentOffset.y > 0
if isContinue,
let collectionCell = textFieldView.iq.superviewOf(type: UICollectionViewCell.self),
let indexPath: IndexPath = collectionView.indexPath(for: collectionCell),
let previousIndexPath: IndexPath = collectionView.previousIndexPath(of: indexPath),
let attributes = collectionView.layoutAttributesForItem(at: previousIndexPath) {
let previousCellRect: CGRect = attributes.frame
if !previousCellRect.isEmpty {
let superview: UIView? = rootController.view.superview
let previousCellRectInRootSuperview: CGRect = collectionView.convert(previousCellRect,
to: superview)
moveUp = CGFloat.minimum(0, previousCellRectInRootSuperview.maxY - topLayoutGuide)
}
}
} else {
isContinue = textFieldViewRectInRootSuperview.minY < topLayoutGuide
if isContinue {
moveUp = CGFloat.minimum(0, textFieldViewRectInRootSuperview.minY - topLayoutGuide)
}
}
// Looping in upper hierarchy until we don't found any scrollView then
// in it's upper hierarchy till UIWindow object.
if isContinue {
var tempScrollView: UIScrollView? = scrollView.iq.superviewOf(type: UIScrollView.self)
var nextScrollView: UIScrollView?
while let view: UIScrollView = tempScrollView {
if view.isScrollEnabled, !view.iq.ignoreScrollingAdjustment {
nextScrollView = view
break
} else {
tempScrollView = view.iq.superviewOf(type: UIScrollView.self)
}
}
// Getting lastViewRect.
if let lastViewRect: CGRect = lastView.superview?.convert(lastView.frame, to: scrollView) {
// Calculating the expected Y offset from move and scrollView's contentOffset.
let minimumMovement: CGFloat = CGFloat.minimum(scrollView.contentOffset.y, -moveUp)
var suggestedOffsetY: CGFloat = scrollView.contentOffset.y - minimumMovement
// Rearranging the expected Y offset according to the view.
suggestedOffsetY = CGFloat.minimum(suggestedOffsetY, lastViewRect.minY)
// [_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
// nextScrollView == nil If processing scrollView is last scrollView in
// upper hierarchy (there is no other scrollView upper hierarchy.)
// [_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
// suggestedOffsetY >= 0 suggestedOffsetY must be greater than in
// order to keep distance from navigationBar (Bug ID: #92)
if isScrollableTextView,
nextScrollView == nil,
suggestedOffsetY >= 0 {
// Converting Rectangle according to window bounds.
if let superview: UIView = textFieldView.superview {
let currentTextFieldViewRect: CGRect = superview.convert(textFieldView.frame,
to: window)
// Calculating expected fix distance which needs to be managed from navigation bar
let expectedFixDistance: CGFloat = currentTextFieldViewRect.minY - topLayoutGuide
// Now if expectedOffsetY (scrollView.contentOffset.y + expectedFixDistance)
// is lower than current suggestedOffsetY, which means we're in a position where
// navigationBar up and hide, then reducing suggestedOffsetY with expectedOffsetY
// (scrollView.contentOffset.y + expectedFixDistance)
let expectedOffsetY: CGFloat = scrollView.contentOffset.y + expectedFixDistance
suggestedOffsetY = CGFloat.minimum(suggestedOffsetY, expectedOffsetY)
// Setting move to 0 because now we don't want to move any view anymore
// (All will be managed by our contentInset logic.
moveUp = 0
} else {
// Subtracting the Y offset from the move variable,
// because we are going to change scrollView's contentOffset.y to suggestedOffsetY.
moveUp -= (suggestedOffsetY-scrollView.contentOffset.y)
}
} else {
// Subtracting the Y offset from the move variable,
// because we are going to change scrollView's contentOffset.y to suggestedOffsetY.
moveUp -= (suggestedOffsetY-scrollView.contentOffset.y)
}
let newContentOffset: CGPoint = CGPoint(x: scrollView.contentOffset.x, y: suggestedOffsetY)
if !scrollView.contentOffset.equalTo(newContentOffset) {
showLog("""
old contentOffset: \(scrollView.contentOffset)
new contentOffset: \(newContentOffset)
""")
self.showLog("Remaining Move: \(moveUp)")
// Getting problem while using `setContentOffset:animated:`, So I used animation API.
activeConfiguration.animate(alongsideTransition: {
// (Bug ID: #1365, #1508, #1541)
let stackView: UIStackView? = textFieldView.iq.superviewOf(type: UIStackView.self,
belowView: scrollView)
// (Bug ID: #1901, #1996)
let animatedContentOffset: Bool = stackView != nil ||
scrollView is UICollectionView ||
scrollView is UITableView
if animatedContentOffset {
scrollView.setContentOffset(newContentOffset, animated: UIView.areAnimationsEnabled)
} else {
scrollView.contentOffset = newContentOffset
}
}, completion: {
if scrollView is UITableView || scrollView is UICollectionView {
// This will update the next/previous states
self.reloadInputViews()
}
})
}
}
// Getting next lastView & superScrollView.
lastView = scrollView
superScrollView = nextScrollView
} else {
moveUp = 0
break
}
}
// Updating contentInset
let lastScrollView = lastScrollViewConfiguration.scrollView
if let lastScrollViewRect: CGRect = lastScrollView.superview?.convert(lastScrollView.frame, to: window),
!lastScrollView.iq.ignoreContentInsetAdjustment {
var bottomInset: CGFloat = (kbSize.height)-(window.frame.height-lastScrollViewRect.maxY)
let keyboardAndSafeArea: CGFloat = keyboardDistance + rootConfiguration.beginSafeAreaInsets.bottom
var bottomScrollIndicatorInset: CGFloat = bottomInset - keyboardAndSafeArea
// Update the insets so that the scrollView doesn't shift incorrectly
// when the offset is near the bottom of the scroll view.
bottomInset = CGFloat.maximum(lastScrollViewConfiguration.startingContentInset.bottom, bottomInset)
let startingScrollInset: UIEdgeInsets = lastScrollViewConfiguration.startingScrollIndicatorInsets
bottomScrollIndicatorInset = CGFloat.maximum(startingScrollInset.bottom,
bottomScrollIndicatorInset)
bottomInset -= lastScrollView.safeAreaInsets.bottom
bottomScrollIndicatorInset -= lastScrollView.safeAreaInsets.bottom
var movedInsets: UIEdgeInsets = lastScrollView.contentInset
movedInsets.bottom = bottomInset
if lastScrollView.contentInset != movedInsets {
showLog("old ContentInset: \(lastScrollView.contentInset) new ContentInset: \(movedInsets)")
activeConfiguration.animate(alongsideTransition: {
lastScrollView.contentInset = movedInsets
lastScrollView.layoutIfNeeded() // (Bug ID: #1996)
var newScrollIndicatorInset: UIEdgeInsets
#if swift(>=5.1)
if #available(iOS 11.1, *) {
newScrollIndicatorInset = lastScrollView.verticalScrollIndicatorInsets
} else {
newScrollIndicatorInset = lastScrollView.scrollIndicatorInsets
}
#else
newScrollIndicatorInset = lastScrollView.scrollIndicatorInsets
#endif
newScrollIndicatorInset.bottom = bottomScrollIndicatorInset
lastScrollView.scrollIndicatorInsets = newScrollIndicatorInset
})
}
}
}
// Going ahead. No else if.
// Special case for UITextView
// (Readjusting textView.contentInset when textView hight is too big to fit on screen)
// _lastScrollView If not having inside any scrollView, now contentInset manages the full screen textView.
// [_textFieldView isKindOfClass:[UITextView class]] If is a UITextView type
if isScrollableTextView, let textView = textFieldView as? UIScrollView {
let keyboardYPosition: CGFloat = window.frame.height - originalKbSize.height
var rootSuperViewFrameInWindow: CGRect = window.frame
if let rootSuperview: UIView = rootController.view.superview {
rootSuperViewFrameInWindow = rootSuperview.convert(rootSuperview.bounds, to: window)
}
let keyboardOverlapping: CGFloat = rootSuperViewFrameInWindow.maxY - keyboardYPosition
let availableHeight: CGFloat = rootSuperViewFrameInWindow.height-topLayoutGuide-keyboardOverlapping
let textViewHeight: CGFloat = CGFloat.minimum(textView.frame.height, availableHeight)
if textView.frame.size.height-textView.contentInset.bottom>textViewHeight {
// If frame is not change by library in past, then saving user textView properties (Bug ID: #92)
if startingTextViewConfiguration == nil {
startingTextViewConfiguration = IQScrollViewConfiguration(scrollView: textView,
canRestoreContentOffset: false)
}
var newContentInset: UIEdgeInsets = textView.contentInset
newContentInset.bottom = textView.frame.size.height-textViewHeight
newContentInset.bottom -= textView.safeAreaInsets.bottom
if textView.contentInset != newContentInset {
self.showLog("""
\(textFieldView) Old UITextView.contentInset: \(textView.contentInset)
New UITextView.contentInset: \(newContentInset)
""")
activeConfiguration.animate(alongsideTransition: {
textView.contentInset = newContentInset
textView.layoutIfNeeded() // (Bug ID: #1996)
textView.scrollIndicatorInsets = newContentInset
})
}
}
}
// +Positive or zero.
if moveUp >= 0 {
rootViewOrigin.y = CGFloat.maximum(rootViewOrigin.y - moveUp, CGFloat.minimum(0, -originalKbSize.height))
if !rootController.view.frame.origin.equalTo(rootViewOrigin) {
showLog("Moving Upward")
activeConfiguration.animate(alongsideTransition: {
var rect: CGRect = rootController.view.frame
rect.origin = rootViewOrigin
rootController.view.frame = rect
// Animating content if needed (Bug ID: #204)
if self.layoutIfNeededOnUpdate {
// Animating content (Bug ID: #160)
rootController.view.setNeedsLayout()
rootController.view.layoutIfNeeded()
}
let classNameString: String = "\(type(of: rootController.self))"
self.showLog("Set \(classNameString) origin to: \(rootViewOrigin)")
})
}
movedDistance = (rootConfiguration.beginOrigin.y-rootViewOrigin.y)
} else { // -Negative
let disturbDistance: CGFloat = rootViewOrigin.y-rootConfiguration.beginOrigin.y
// disturbDistance Negative = frame disturbed.
// disturbDistance positive = frame not disturbed.
if disturbDistance <= 0 {
rootViewOrigin.y -= CGFloat.maximum(moveUp, disturbDistance)
if !rootController.view.frame.origin.equalTo(rootViewOrigin) {
showLog("Moving Downward")
// Setting adjusted rootViewRect
// Setting adjusted rootViewRect
activeConfiguration.animate(alongsideTransition: {
var rect: CGRect = rootController.view.frame
rect.origin = rootViewOrigin
rootController.view.frame = rect
// Animating content if needed (Bug ID: #204)
if self.layoutIfNeededOnUpdate {
// Animating content (Bug ID: #160)
rootController.view.setNeedsLayout()
rootController.view.layoutIfNeeded()
}
let classNameString: String = "\(type(of: rootController.self))"
self.showLog("Set \(classNameString) origin to: \(rootViewOrigin)")
})
}
movedDistance = (rootConfiguration.beginOrigin.y-rootViewOrigin.y)
}
}
let elapsedTime: CFTimeInterval = CACurrentMediaTime() - startTime
showLog("<<<<< \(#function) ended: \(elapsedTime) seconds <<<<<", indentation: -1)
}
// swiftlint:enable cyclomatic_complexity
// swiftlint:enable function_body_length
// swiftlint:disable cyclomatic_complexity
// swiftlint:disable function_body_length
internal func restorePosition() {
// Setting rootViewController frame to it's original position. // (Bug ID: #18)
guard let configuration: IQRootControllerConfiguration = activeConfiguration.rootControllerConfiguration else {
return
}
let startTime: CFTimeInterval = CACurrentMediaTime()
showLog(">>>>> \(#function) started >>>>>", indentation: 1)
activeConfiguration.animate(alongsideTransition: {
if configuration.hasChanged {
let classNameString: String = "\(type(of: configuration.rootController.self))"
self.showLog("Restoring \(classNameString) origin to: \(configuration.beginOrigin)")
}
configuration.restore()
// Animating content if needed (Bug ID: #204)
if self.layoutIfNeededOnUpdate {
// Animating content (Bug ID: #160)
configuration.rootController.view.setNeedsLayout()
configuration.rootController.view.layoutIfNeeded()
}
})
// Restoring the contentOffset of the lastScrollView
if let textFieldView: UIView = activeConfiguration.textFieldViewInfo?.textFieldView,
let lastConfiguration: IQScrollViewConfiguration = lastScrollViewConfiguration {
activeConfiguration.animate(alongsideTransition: {
if lastConfiguration.hasChanged {
if lastConfiguration.scrollView.contentInset != lastConfiguration.startingContentInset {
self.showLog("Restoring contentInset to: \(lastConfiguration.startingContentInset)")
}
if lastConfiguration.scrollView.iq.restoreContentOffset,
!lastConfiguration.scrollView.contentOffset.equalTo(lastConfiguration.startingContentOffset) {
self.showLog("Restoring contentOffset to: \(lastConfiguration.startingContentOffset)")
}
lastConfiguration.restore(for: textFieldView)
}
// This is temporary solution. Have to implement the save and restore scrollView state
var superScrollView: UIScrollView? = lastConfiguration.scrollView
while let scrollView: UIScrollView = superScrollView {
let width: CGFloat = CGFloat.maximum(scrollView.contentSize.width, scrollView.frame.width)
let height: CGFloat = CGFloat.maximum(scrollView.contentSize.height, scrollView.frame.height)
let contentSize: CGSize = CGSize(width: width, height: height)
let minimumY: CGFloat = contentSize.height - scrollView.frame.height
if minimumY < scrollView.contentOffset.y {
let newContentOffset: CGPoint = CGPoint(x: scrollView.contentOffset.x, y: minimumY)
if !scrollView.contentOffset.equalTo(newContentOffset) {
// (Bug ID: #1365, #1508, #1541)
let stackView: UIStackView? = textFieldView.iq.superviewOf(type: UIStackView.self,
belowView: scrollView)
// (Bug ID: #1901, #1996)
let animatedContentOffset: Bool = stackView != nil ||
scrollView is UICollectionView ||
scrollView is UITableView
if animatedContentOffset {
scrollView.setContentOffset(newContentOffset, animated: UIView.areAnimationsEnabled)
} else {
scrollView.contentOffset = newContentOffset
}
self.showLog("Restoring contentOffset to: \(newContentOffset)")
}
}
superScrollView = scrollView.iq.superviewOf(type: UIScrollView.self)
}
})
}
self.movedDistance = 0
let elapsedTime: CFTimeInterval = CACurrentMediaTime() - startTime
showLog("<<<<< \(#function) ended: \(elapsedTime) seconds <<<<<", indentation: -1)
}
// swiftlint:enable cyclomatic_complexity
// swiftlint:enable function_body_length
}

View File

@ -1,280 +0,0 @@
//
// IQKeyboardManager+Toolbar.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)
public extension IQKeyboardManager {
/**
Default tag for toolbar with Done button -1002.
*/
private static let kIQDoneButtonToolbarTag = -1002
/**
Default tag for toolbar with Previous/Next buttons -1005.
*/
private static let kIQPreviousNextButtonToolbarTag = -1005
// swiftlint:disable function_body_length
// swiftlint:disable cyclomatic_complexity
/**
Add toolbar if it is required to add on textFields and it's siblings.
*/
internal func addToolbarIfRequired() {
// 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(), !siblings.isEmpty,
let textField: UIView = activeConfiguration.textFieldViewInfo?.textFieldView,
textField.responds(to: #selector(setter: UITextField.inputAccessoryView)) else {
return
}
if let inputAccessoryView: UIView = textField.inputAccessoryView {
if inputAccessoryView.tag == IQKeyboardManager.kIQPreviousNextButtonToolbarTag ||
inputAccessoryView.tag == IQKeyboardManager.kIQDoneButtonToolbarTag {
// continue
} else {
let swiftUIAccessoryName: String = "InputAccessoryHost<InputAccessoryBar>"
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
guard classNameString.hasPrefix(swiftUIAccessoryName), inputAccessoryView.subviews.isEmpty else {
return
}
}
}
showLog(">>>>> \(#function) started >>>>>", indentation: 1)
let startTime: CFTimeInterval = CACurrentMediaTime()
showLog("Found \(siblings.count) responder sibling(s)")
let rightConfiguration: IQBarButtonItemConfiguration
if let configuration: IQBarButtonItemConfiguration = toolbarConfiguration.doneBarButtonConfiguration {
rightConfiguration = configuration
rightConfiguration.action = #selector(self.doneAction(_:))
} else {
rightConfiguration = IQBarButtonItemConfiguration(systemItem: .done, action: #selector(self.doneAction(_:)))
rightConfiguration.accessibilityLabel = "Done"
}
let isTableCollectionView: Bool
if textField.iq.superviewOf(type: UITableView.self) != nil ||
textField.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 textField is part of UITableView/UICollectionView then we should be exposing previous/next too
// Because at this time we don't know the previous or next cell if it contains another textField 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: IQToolbarPlaceholderConfiguration = toolbarConfiguration.placeholderConfiguration
if havePreviousNext {
let prevConfiguration: IQBarButtonItemConfiguration
if let configuration: IQBarButtonItemConfiguration = toolbarConfiguration.previousBarButtonConfiguration {
configuration.action = #selector(self.previousAction(_:))
prevConfiguration = configuration
} else {
prevConfiguration = IQBarButtonItemConfiguration(image: (UIImage.keyboardPreviousImage),
action: #selector(self.previousAction(_:)))
prevConfiguration.accessibilityLabel = "Previous"
}
let nextConfiguration: IQBarButtonItemConfiguration
if let configuration: IQBarButtonItemConfiguration = toolbarConfiguration.nextBarButtonConfiguration {
configuration.action = #selector(self.nextAction(_:))
nextConfiguration = configuration
} else {
nextConfiguration = IQBarButtonItemConfiguration(image: (UIImage.keyboardNextImage),
action: #selector(self.nextAction(_:)))
nextConfiguration.accessibilityLabel = "Next"
}
let titleText: String? = placeholderConfig.showPlaceholder ? textField.iq.drawingPlaceholder : nil
textField.iq.addToolbar(target: self,
previousConfiguration: prevConfiguration,
nextConfiguration: nextConfiguration,
rightConfiguration: rightConfiguration, title: titleText,
titleAccessibilityLabel: placeholderConfig.accessibilityLabel)
// (Bug ID: #78)
textField.inputAccessoryView?.tag = IQKeyboardManager.kIQPreviousNextButtonToolbarTag
if isTableCollectionView {
// (Bug ID: #56)
// In case of UITableView, the next/previous buttons should always be enabled.
textField.iq.toolbar.previousBarButton.isEnabled = true
textField.iq.toolbar.nextBarButton.isEnabled = true
} else {
// If firstTextField, then previous should not be enabled.
textField.iq.toolbar.previousBarButton.isEnabled = (siblings.first != textField)
// If lastTextField then next should not be enabled.
textField.iq.toolbar.nextBarButton.isEnabled = (siblings.last != textField)
}
} else {
let titleText: String? = placeholderConfig.showPlaceholder ? textField.iq.drawingPlaceholder : nil
textField.iq.addToolbar(target: self, rightConfiguration: rightConfiguration,
title: titleText,
titleAccessibilityLabel: placeholderConfig.accessibilityLabel)
textField.inputAccessoryView?.tag = IQKeyboardManager.kIQDoneButtonToolbarTag // (Bug ID: #78)
}
let toolbar: IQToolbar = textField.iq.toolbar
// Setting toolbar tintColor // (Enhancement ID: #30)
if toolbarConfiguration.useTextFieldTintColor {
toolbar.tintColor = textField.tintColor
} else {
toolbar.tintColor = toolbarConfiguration.tintColor
}
// Setting toolbar to keyboard.
if let textFieldView: UITextInput = textField as? UITextInput {
// Bar style according to keyboard appearance
switch textFieldView.keyboardAppearance {
case .dark?:
toolbar.barStyle = .black
toolbar.barTintColor = nil
default:
toolbar.barStyle = .default
toolbar.barTintColor = toolbarConfiguration.barTintColor
}
}
// Setting toolbar title font. // (Enhancement ID: #30)
if toolbarConfiguration.placeholderConfiguration.showPlaceholder,
!textField.iq.hidePlaceholder {
// Updating placeholder font to toolbar. //(Bug ID: #148, #272)
if toolbar.titleBarButton.title == nil ||
toolbar.titleBarButton.title != textField.iq.drawingPlaceholder {
toolbar.titleBarButton.title = textField.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
} else {
toolbar.titleBarButton.title = nil
}
// In case of UITableView (Special), the next/previous buttons has to be refreshed every-time. (Bug ID: #56)
// If firstTextField, then previous should not be enabled.
textField.iq.toolbar.previousBarButton.isEnabled = (siblings.first != textField)
// If lastTextField then next should not be enabled.
textField.iq.toolbar.nextBarButton.isEnabled = (siblings.last != textField)
let elapsedTime: CFTimeInterval = CACurrentMediaTime() - startTime
showLog("<<<<< \(#function) ended: \(elapsedTime) seconds <<<<<", indentation: -1)
}
// swiftlint:enable function_body_length
// swiftlint:enable cyclomatic_complexity
/** Remove any toolbar if it is IQToolbar. */
internal func removeToolbarIfRequired() { // (Bug ID: #18)
guard let siblings: [UIView] = responderViews(), !siblings.isEmpty,
let textField: UIView = activeConfiguration.textFieldViewInfo?.textFieldView,
textField.responds(to: #selector(setter: UITextField.inputAccessoryView)),
textField.inputAccessoryView == nil ||
textField.inputAccessoryView?.tag == IQKeyboardManager.kIQPreviousNextButtonToolbarTag ||
textField.inputAccessoryView?.tag == IQKeyboardManager.kIQDoneButtonToolbarTag else {
return
}
showLog(">>>>> \(#function) started >>>>>", indentation: 1)
let startTime: CFTimeInterval = CACurrentMediaTime()
showLog("Found \(siblings.count) responder sibling(s)")
for view in siblings {
if let toolbar: IQToolbar = view.inputAccessoryView as? IQToolbar {
// setInputAccessoryView: check (Bug ID: #307)
if view.responds(to: #selector(setter: UITextField.inputAccessoryView)),
toolbar.tag == IQKeyboardManager.kIQDoneButtonToolbarTag ||
toolbar.tag == IQKeyboardManager.kIQPreviousNextButtonToolbarTag {
if let textField: UITextField = view as? UITextField {
textField.inputAccessoryView = nil
} else if let textView: UITextView = view as? UITextView {
textView.inputAccessoryView = nil
}
view.reloadInputViews()
}
}
}
let elapsedTime: CFTimeInterval = CACurrentMediaTime() - startTime
showLog("<<<<< \(#function) ended: \(elapsedTime) seconds <<<<<", indentation: -1)
}
/** reloadInputViews to reload toolbar buttons enable/disable state on the fly Enhancement ID #434. */
@objc func reloadInputViews() {
// If enabled then adding toolbar.
if privateIsEnableAutoToolbar() {
self.addToolbarIfRequired()
} else {
self.removeToolbarIfRequired()
}
}
}

View File

@ -1,205 +0,0 @@
//
// IQKeyboardManager+ToolbarActions.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
// MARK: Previous next button actions
@available(iOSApplicationExtension, unavailable)
public extension IQKeyboardManager {
/**
Returns YES if can navigate to previous responder textField/textView, otherwise NO.
*/
@objc var canGoPrevious: Bool {
// If it is not first textField. then it's previous object canBecomeFirstResponder.
guard let textFields: [UIView] = responderViews(),
let textFieldRetain: UIView = activeConfiguration.textFieldViewInfo?.textFieldView,
let index: Int = textFields.firstIndex(of: textFieldRetain),
index > 0 else {
return false
}
return true
}
/**
Returns YES if can navigate to next responder textField/textView, otherwise NO.
*/
@objc var canGoNext: Bool {
// If it is not first textField. then it's previous object canBecomeFirstResponder.
guard let textFields: [UIView] = responderViews(),
let textFieldRetain: UIView = activeConfiguration.textFieldViewInfo?.textFieldView,
let index: Int = textFields.firstIndex(of: textFieldRetain),
index < textFields.count-1 else {
return false
}
return true
}
/**
Navigate to previous responder textField/textView.
*/
@discardableResult
@objc func goPrevious() -> Bool {
// If it is not first textField. then it's previous object becomeFirstResponder.
guard let textFields: [UIView] = responderViews(),
let textFieldRetain: UIView = activeConfiguration.textFieldViewInfo?.textFieldView,
let index: Int = textFields.firstIndex(of: textFieldRetain),
index > 0 else {
return false
}
let nextTextField: UIView = textFields[index-1]
let isAcceptAsFirstResponder: Bool = nextTextField.becomeFirstResponder()
// If it refuses then becoming previous textFieldView as first responder again. (Bug ID: #96)
if !isAcceptAsFirstResponder {
showLog("Refuses to become first responder: \(nextTextField)")
}
return isAcceptAsFirstResponder
}
/**
Navigate to next responder textField/textView.
*/
@discardableResult
@objc func goNext() -> Bool {
// If it is not first textField. then it's previous object becomeFirstResponder.
guard let textFields: [UIView] = responderViews(),
let textFieldRetain: UIView = activeConfiguration.textFieldViewInfo?.textFieldView,
let index: Int = textFields.firstIndex(of: textFieldRetain),
index < textFields.count-1 else {
return false
}
let nextTextField: UIView = textFields[index+1]
let isAcceptAsFirstResponder: Bool = nextTextField.becomeFirstResponder()
// If it refuses then becoming previous textFieldView as first responder again. (Bug ID: #96)
if !isAcceptAsFirstResponder {
showLog("Refuses to become first responder: \(nextTextField)")
}
return isAcceptAsFirstResponder
}
/** previousAction. */
@objc internal func previousAction (_ barButton: IQBarButtonItem) {
// If user wants to play input Click sound.
if playInputClicks {
// Play Input Click Sound.
UIDevice.current.playInputClick()
}
guard canGoPrevious,
let textFieldRetain: UIView = activeConfiguration.textFieldViewInfo?.textFieldView else {
return
}
let isAcceptAsFirstResponder: Bool = goPrevious()
var invocation: IQInvocation? = barButton.invocation
var sender: UIView = textFieldRetain
// Handling search bar special case
do {
if let searchBar: UIView = textFieldRetain.iq.textFieldSearchBar() {
invocation = searchBar.iq.toolbar.previousBarButton.invocation
sender = searchBar
}
}
if isAcceptAsFirstResponder {
invocation?.invoke(from: sender)
}
}
/** nextAction. */
@objc internal func nextAction (_ barButton: IQBarButtonItem) {
// If user wants to play input Click sound.
if playInputClicks {
// Play Input Click Sound.
UIDevice.current.playInputClick()
}
guard canGoNext,
let textFieldRetain: UIView = activeConfiguration.textFieldViewInfo?.textFieldView else {
return
}
let isAcceptAsFirstResponder: Bool = goNext()
var invocation: IQInvocation? = barButton.invocation
var sender: UIView = textFieldRetain
// Handling search bar special case
do {
if let searchBar: UIView = textFieldRetain.iq.textFieldSearchBar() {
invocation = searchBar.iq.toolbar.nextBarButton.invocation
sender = searchBar
}
}
if isAcceptAsFirstResponder {
invocation?.invoke(from: sender)
}
}
/** doneAction. Resigning current textField. */
@objc internal func doneAction (_ barButton: IQBarButtonItem) {
// If user wants to play input Click sound.
if playInputClicks {
// Play Input Click Sound.
UIDevice.current.playInputClick()
}
guard let textFieldRetain: UIView = activeConfiguration.textFieldViewInfo?.textFieldView else {
return
}
// Resign textFieldView.
let isResignedFirstResponder: Bool = resignFirstResponder()
var invocation: IQInvocation? = barButton.invocation
var sender: UIView = textFieldRetain
// Handling search bar special case
do {
if let searchBar: UIView = textFieldRetain.iq.textFieldSearchBar() {
invocation = searchBar.iq.toolbar.doneBarButton.invocation
sender = searchBar
}
}
if isResignedFirstResponder {
invocation?.invoke(from: sender)
}
}
}

View File

@ -1,148 +0,0 @@
//
// IQKeyboardManager+UIKeyboardNotification.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
// MARK: UIKeyboard Notifications
@available(iOSApplicationExtension, unavailable)
internal extension IQKeyboardManager {
func handleKeyboardTextFieldViewVisible() {
if self.activeConfiguration.rootControllerConfiguration == nil { // (Bug ID: #5)
let rootConfiguration: IQRootControllerConfiguration? = self.activeConfiguration.rootControllerConfiguration
if let gestureConfiguration = self.rootConfigurationWhilePopGestureActive,
gestureConfiguration.rootController == rootConfiguration?.rootController {
self.activeConfiguration.rootControllerConfiguration = gestureConfiguration
}
self.rootConfigurationWhilePopGestureActive = nil
if let configuration = self.activeConfiguration.rootControllerConfiguration {
let classNameString: String = "\(type(of: configuration.rootController.self))"
self.showLog("Saving \(classNameString) beginning origin: \(configuration.beginOrigin)")
}
}
setupTextFieldView()
if !privateIsEnabled() {
restorePosition()
} else {
adjustPosition()
}
}
func handleKeyboardTextFieldViewChanged() {
setupTextFieldView()
if !privateIsEnabled() {
restorePosition()
} else {
adjustPosition()
}
}
func handleKeyboardTextFieldViewHide() {
self.restorePosition()
self.banishTextFieldViewSetup()
if let configuration = self.activeConfiguration.rootControllerConfiguration,
configuration.rootController.navigationController?.interactivePopGestureRecognizer?.state == .began {
self.rootConfigurationWhilePopGestureActive = configuration
}
self.lastScrollViewConfiguration = nil
}
}
@available(iOSApplicationExtension, unavailable)
internal extension IQKeyboardManager {
func setupTextFieldView() {
guard let textFieldView = activeConfiguration.textFieldViewInfo?.textFieldView else {
return
}
do {
if let startingConfiguration = startingTextViewConfiguration,
startingConfiguration.hasChanged {
if startingConfiguration.scrollView.contentInset != startingConfiguration.startingContentInset {
showLog("Restoring textView.contentInset to: \(startingConfiguration.startingContentInset)")
}
activeConfiguration.animate(alongsideTransition: {
startingConfiguration.restore(for: textFieldView)
})
}
startingTextViewConfiguration = nil
}
if keyboardConfiguration.overrideAppearance,
let textInput: UITextInput = textFieldView as? UITextInput,
textInput.keyboardAppearance != keyboardConfiguration.appearance {
// Setting textField keyboard appearance and reloading inputViews.
if let textFieldView: UITextField = textFieldView as? UITextField {
textFieldView.keyboardAppearance = keyboardConfiguration.appearance
} else if let textFieldView: UITextView = textFieldView as? UITextView {
textFieldView.keyboardAppearance = keyboardConfiguration.appearance
}
textFieldView.reloadInputViews()
}
// If autoToolbar enable, then add toolbar on all the UITextField/UITextView's if required.
reloadInputViews()
resignFirstResponderGesture.isEnabled = privateResignOnTouchOutside()
textFieldView.window?.addGestureRecognizer(resignFirstResponderGesture) // (Enhancement ID: #14)
}
func banishTextFieldViewSetup() {
guard let textFieldView = activeConfiguration.textFieldViewInfo?.textFieldView else {
return
}
// Removing gesture recognizer (Enhancement ID: #14)
textFieldView.window?.removeGestureRecognizer(resignFirstResponderGesture)
do {
if let startingConfiguration = startingTextViewConfiguration,
startingConfiguration.hasChanged {
if startingConfiguration.scrollView.contentInset != startingConfiguration.startingContentInset {
showLog("Restoring textView.contentInset to: \(startingConfiguration.startingContentInset)")
}
activeConfiguration.animate(alongsideTransition: {
startingConfiguration.restore(for: textFieldView)
})
}
startingTextViewConfiguration = nil
}
}
}

View File

@ -1,46 +0,0 @@
//
// IQKeyboardManager+UITextFieldViewNotification.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
// MARK: UITextField/UITextView Notifications
@available(iOSApplicationExtension, unavailable)
@MainActor
internal extension IQKeyboardManager {
@MainActor
private struct AssociatedKeys {
static var rootConfigWhilePopActive: Int = 0
}
var rootConfigurationWhilePopGestureActive: IQRootControllerConfiguration? {
get {
return objc_getAssociatedObject(self,
&AssociatedKeys.rootConfigWhilePopActive) as? IQRootControllerConfiguration
}
set(newValue) {
objc_setAssociatedObject(self, &AssociatedKeys.rootConfigWhilePopActive,
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}

View File

@ -1,331 +0,0 @@
//
// IQKeyboardManager.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
import CoreGraphics
import QuartzCore
// MARK: IQToolbar tags
// swiftlint:disable line_length
// A generic version of KeyboardManagement. (OLD DOCUMENTATION) LINK
// https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html
// https://developer.apple.com/documentation/uikit/keyboards_and_input/adjusting_your_layout_with_keyboard_layout_guide
// swiftlint:enable line_length
/**
Code-less drop-in universal library allows to prevent issues of keyboard sliding up and cover UITextField/UITextView.
Neither need to write any code nor any setup required and much more.
*/
@available(iOSApplicationExtension, unavailable)
@MainActor
@objc public final class IQKeyboardManager: NSObject {
/**
Returns the default singleton instance.
*/
@objc public static let shared: IQKeyboardManager = .init()
// MARK: UIKeyboard handling
/**
Enable/disable managing distance between keyboard and textField.
Default is YES(Enabled when class loads in `+(void)load` method).
*/
@objc public var enable: Bool = false {
didSet {
// If not enable, enable it.
if enable, !oldValue {
// If keyboard is currently showing.
if activeConfiguration.keyboardInfo.keyboardShowing {
adjustPosition()
} else {
restorePosition()
}
showLog("Enabled")
} else if !enable, oldValue { // If not disable, disable it.
restorePosition()
showLog("Disabled")
}
}
}
/**
To set keyboard distance from textField. can't be less than zero. Default is 10.0.
*/
@objc public var keyboardDistanceFromTextField: CGFloat = 10.0
// MARK: IQToolbar handling
/**
Automatic add the IQToolbar functionality. Default is YES.
*/
@objc public var enableAutoToolbar: Bool = true {
didSet {
reloadInputViews()
showLog("enableAutoToolbar: \(enableAutoToolbar ? "Yes" : "NO")")
}
}
internal var activeConfiguration: IQActiveConfiguration = .init()
/**
Configurations related to the toolbar display over the keyboard.
*/
@objc public let toolbarConfiguration: IQToolbarConfiguration = .init()
/**
Configuration related to keyboard appearance
*/
@objc public let keyboardConfiguration: IQKeyboardConfiguration = .init()
// MARK: UITextField/UITextView Next/Previous/Resign handling
/**
Resigns Keyboard on touching outside of UITextField/View. Default is NO.
*/
@objc public var resignOnTouchOutside: Bool = false {
didSet {
resignFirstResponderGesture.isEnabled = privateResignOnTouchOutside()
showLog("resignOnTouchOutside: \(resignOnTouchOutside ? "Yes" : "NO")")
}
}
/** TapGesture to resign keyboard on view's touch.
It's a readonly property and exposed only for adding/removing dependencies
if your added gesture does have collision with this one
*/
@objc public lazy var resignFirstResponderGesture: UITapGestureRecognizer = {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.tapRecognized(_:)))
tapGesture.cancelsTouchesInView = false
tapGesture.delegate = self
return tapGesture
}()
/*******************************************/
/**
Resigns currently first responder field.
*/
@discardableResult
@objc public func resignFirstResponder() -> Bool {
guard let textFieldRetain: UIView = activeConfiguration.textFieldViewInfo?.textFieldView else {
return false
}
// Resigning first responder
guard textFieldRetain.resignFirstResponder() else {
showLog("Refuses to resign first responder: \(textFieldRetain)")
// If it refuses then becoming it as first responder again. (Bug ID: #96)
// If it refuses to resign then becoming it first responder again for getting notifications callback.
textFieldRetain.becomeFirstResponder()
return false
}
return true
}
// MARK: UISound handling
/**
If YES, then it plays inputClick sound on next/previous/done click.
*/
@objc public var playInputClicks: Bool = true
// MARK: UIAnimation handling
/**
If YES, then calls 'setNeedsLayout' and 'layoutIfNeeded' on any frame update of to viewController's view.
*/
@objc public var layoutIfNeededOnUpdate: Bool = false
// MARK: Class Level disabling methods
/**
Disable distance handling within the scope of disabled distance handling viewControllers classes.
Within this scope, 'enabled' property is ignored. Class should be kind of UIViewController.
*/
@objc public var disabledDistanceHandlingClasses: [UIViewController.Type] = []
/**
Enable distance handling within the scope of enabled distance handling viewControllers classes.
Within this scope, 'enabled' property is ignored. Class should be kind of UIViewController.
If same Class is added in disabledDistanceHandlingClasses list,
then enabledDistanceHandlingClasses will be ignored.
*/
@objc public var enabledDistanceHandlingClasses: [UIViewController.Type] = []
/**
Disable automatic toolbar creation within the scope of disabled toolbar viewControllers classes.
Within this scope, 'enableAutoToolbar' property is ignored. Class should be kind of UIViewController.
*/
@objc public var disabledToolbarClasses: [UIViewController.Type] = []
/**
Enable automatic toolbar creation within the scope of enabled toolbar viewControllers classes.
Within this scope, 'enableAutoToolbar' property is ignored. Class should be kind of UIViewController.
If same Class is added in disabledToolbarClasses list, then enabledToolbarClasses will be ignore.
*/
@objc public var enabledToolbarClasses: [UIViewController.Type] = []
/**
Allowed subclasses of UIView to add all inner textField,
this will allow to navigate between textField contains in different superview.
Class should be kind of UIView.
*/
@objc public var toolbarPreviousNextAllowedClasses: [UIView.Type] = []
/**
Disabled classes to ignore resignOnTouchOutside' property, Class should be kind of UIViewController.
*/
@objc public var disabledTouchResignedClasses: [UIViewController.Type] = []
/**
Enabled classes to forcefully enable 'resignOnTouchOutside' property.
Class should be kind of UIViewController
. If same Class is added in disabledTouchResignedClasses list, then enabledTouchResignedClasses will be ignored.
*/
@objc public var enabledTouchResignedClasses: [UIViewController.Type] = []
/**
if resignOnTouchOutside is enabled then you can customize the behavior
to not recognize gesture touches on some specific view subclasses.
Class should be kind of UIView. Default is [UIControl, UINavigationBar]
*/
@objc public var touchResignedGestureIgnoreClasses: [UIView.Type] = []
// MARK: Third Party Library support
/// Add TextField/TextView Notifications customized Notifications.
/// For example while using YYTextView https://github.com/ibireme/YYText
/**************************************************************************************/
// MARK: Initialization/De-initialization
/* Singleton Object Initialization. */
override init() {
super.init()
self.addActiveConfigurationObserver()
// Creating gesture for resignOnTouchOutside. (Enhancement ID: #14)
resignFirstResponderGesture.isEnabled = resignOnTouchOutside
disabledDistanceHandlingClasses.append(UITableViewController.self)
disabledDistanceHandlingClasses.append(UIInputViewController.self)
disabledDistanceHandlingClasses.append(UIAlertController.self)
disabledToolbarClasses.append(UIAlertController.self)
disabledToolbarClasses.append(UIInputViewController.self)
disabledTouchResignedClasses.append(UIAlertController.self)
disabledTouchResignedClasses.append(UIInputViewController.self)
toolbarPreviousNextAllowedClasses.append(UITableView.self)
toolbarPreviousNextAllowedClasses.append(UICollectionView.self)
toolbarPreviousNextAllowedClasses.append(IQPreviousNextView.self)
touchResignedGestureIgnoreClasses.append(UIControl.self)
touchResignedGestureIgnoreClasses.append(UINavigationBar.self)
NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive(_:)),
name: UIApplication.didBecomeActiveNotification, object: nil)
// (Bug ID: #550)
// Loading IQToolbar, 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 textField: UIView = UITextField()
textField.iq.addDone(target: nil, action: #selector(self.doneAction(_:)))
textField.iq.addPreviousNextDone(target: nil, previousAction: #selector(self.previousAction(_:)),
nextAction: #selector(self.nextAction(_:)),
doneAction: #selector(self.doneAction(_:)))
}
}
deinit {
// Disable the keyboard manager.
enable = false
}
// MARK: Public Methods
/* Refreshes textField/textView position if any external changes is explicitly made by user. */
@objc public func reloadLayoutIfNeeded() {
guard privateIsEnabled(),
activeConfiguration.keyboardInfo.keyboardShowing,
activeConfiguration.isReady else {
return
}
adjustPosition()
}
}
@available(iOSApplicationExtension, unavailable)
extension IQKeyboardManager: UIGestureRecognizerDelegate {
/** Resigning on tap gesture. (Enhancement ID: #14)*/
@objc private func tapRecognized(_ gesture: UITapGestureRecognizer) {
if gesture.state == .ended {
// Resigning currently responder textField.
resignFirstResponder()
}
}
/** Note: returning YES is guaranteed to allow simultaneous recognition.
returning NO is not guaranteed to prevent simultaneous recognition,
as the other gesture's delegate may return YES.
*/
@objc public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith
otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
/**
To not detect touch events in a subclass of UIControl,
these may have added their own selector for specific work
*/
@objc public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldReceive touch: UITouch) -> Bool {
// (Bug ID: #145)
// Should not recognize gesture if the clicked view is either UIControl or UINavigationBar(<Back button etc...)
for ignoreClass in touchResignedGestureIgnoreClasses where touch.view?.isKind(of: ignoreClass) ?? false {
return false
}
return true
}
}

View File

@ -1,59 +0,0 @@
//
// IQKeyboardManagerCompatible.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 Foundation
/// Wrapper for IQKeyboardManager compatible types. This type provides an extension point for
/// convenience methods in IQKeyboardManager.
@available(iOSApplicationExtension, unavailable)
public struct IQKeyboardManagerWrapper<Base> {
public let base: Base
public init(_ base: Base) {
self.base = base
}
}
// swiftlint:disable identifier_name
/// Represents an object type that is compatible with IQKeyboardManager. You can use `iq` property to get a
/// value in the namespace of IQKeyboardManager.
@available(iOSApplicationExtension, unavailable)
public protocol IQKeyboardManagerCompatible {
/// Type being extended.
associatedtype Base
/// Instance IQKeyboardManager extension point.
var iq: IQKeyboardManagerWrapper<Base> { get set }
}
// swiftlint:disable unused_setter_value
@available(iOSApplicationExtension, unavailable)
public extension IQKeyboardManagerCompatible {
/// Instance IQKeyboardManager extension point.
var iq: IQKeyboardManagerWrapper<Self> {
get { IQKeyboardManagerWrapper(self) }
set {}
}
}
// swiftlint:enable unused_setter_value
// swiftlint:enable identifier_name

View File

@ -1,228 +0,0 @@
//
// IQUIView+IQKeyboardToolbarDeprecated.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
// swiftlint:disable unused_setter_value
// swiftlint:disable line_length
// swiftlint:disable function_parameter_count
@available(iOSApplicationExtension, unavailable)
@MainActor
@objc public extension UIView {
@available(*, unavailable, renamed: "iq.toolbar")
var keyboardToolbar: IQToolbar {
get { fatalError() }
set {}
}
@available(*, unavailable, renamed: "iq.hidePlaceholder")
var shouldHideToolbarPlaceholder: Bool {
get { false }
set {}
}
@available(*, unavailable, renamed: "iq.placeholder")
var toolbarPlaceholder: String? {
get { nil }
set {}
}
@available(*, unavailable, renamed: "iq.drawingPlaceholder")
var drawingToolbarPlaceholder: String? {
get { nil }
set {}
}
@available(*, unavailable, renamed: "iq.addToolbar(target:previousConfiguration:nextConfiguration:rightConfiguration:title:titleAccessibilityLabel:)")
func addKeyboardToolbarWithTarget(target: AnyObject?,
titleText: String?,
titleAccessibilityLabel: String? = nil,
rightBarButtonConfiguration: IQBarButtonItemConfiguration?,
previousBarButtonConfiguration: IQBarButtonItemConfiguration? = nil,
nextBarButtonConfiguration: IQBarButtonItemConfiguration? = nil) {
}
@available(*, unavailable, renamed: "iq.addDone(target:action:showPlaceholder:titleAccessibilityLabel:)")
func addDoneOnKeyboardWithTarget(_ target: AnyObject?,
action: Selector,
shouldShowPlaceholder: Bool = false,
titleAccessibilityLabel: String? = nil) {
}
@available(*, unavailable, renamed: "iq.addDone(target:action:title:titleAccessibilityLabel:)")
func addDoneOnKeyboardWithTarget(_ target: AnyObject?,
action: Selector,
titleText: String?,
titleAccessibilityLabel: String? = nil) {
}
@available(*, unavailable, renamed: "iq.addRightButton(target:configuration:showPlaceholder:titleAccessibilityLabel:)")
func addRightButtonOnKeyboardWithImage(_ image: UIImage,
target: AnyObject?,
action: Selector,
shouldShowPlaceholder: Bool = false,
titleAccessibilityLabel: String? = nil) {
}
@available(*, unavailable, renamed: "iq.addRightButton(target:configuration:title:titleAccessibilityLabel:)")
func addRightButtonOnKeyboardWithImage(_ image: UIImage,
target: AnyObject?,
action: Selector,
titleText: String?,
titleAccessibilityLabel: String? = nil) {
}
@available(*, unavailable, renamed: "iq.addRightButton(target:configuration:showPlaceholder:titleAccessibilityLabel:)")
func addRightButtonOnKeyboardWithText(_ text: String,
target: AnyObject?,
action: Selector,
shouldShowPlaceholder: Bool = false,
titleAccessibilityLabel: String? = nil) {
}
@available(*, unavailable, renamed: "iq.addRightButton(target:configuration:title:titleAccessibilityLabel:)")
func addRightButtonOnKeyboardWithText(_ text: String,
target: AnyObject?,
action: Selector,
titleText: String?,
titleAccessibilityLabel: String? = nil) {
}
@available(*, unavailable, renamed: "iq.addRightLeft(target:rightConfiguration:leftConfiguration:showPlaceholder:titleAccessibilityLabel:)")
func addCancelDoneOnKeyboardWithTarget(_ target: AnyObject?,
cancelAction: Selector,
doneAction: Selector,
shouldShowPlaceholder: Bool = false,
titleAccessibilityLabel: String? = nil) {
}
@available(*, unavailable, renamed: "iq.addRightLeft(target:rightConfiguration:leftConfiguration:showPlaceholder:titleAccessibilityLabel:)")
func addRightLeftOnKeyboardWithTarget(_ target: AnyObject?,
leftButtonTitle: String,
rightButtonTitle: String,
leftButtonAction: Selector,
rightButtonAction: Selector,
shouldShowPlaceholder: Bool = false,
titleAccessibilityLabel: String? = nil) {
}
@available(*, unavailable, renamed: "iq.addRightLeft(target:rightConfiguration:leftConfiguration:showPlaceholder:titleAccessibilityLabel:)")
func addRightLeftOnKeyboardWithTarget(_ target: AnyObject?,
leftButtonImage: UIImage,
rightButtonImage: UIImage,
leftButtonAction: Selector,
rightButtonAction: Selector,
shouldShowPlaceholder: Bool = false,
titleAccessibilityLabel: String? = nil) {
}
@available(*, unavailable, renamed: "iq.addRightLeft(target:rightConfiguration:leftConfiguration:title:titleAccessibilityLabel:)")
func addCancelDoneOnKeyboardWithTarget(_ target: AnyObject?,
cancelAction: Selector,
doneAction: Selector,
titleText: String?,
titleAccessibilityLabel: String? = nil) {
}
@available(*, unavailable, renamed: "iq.addRightLeft(target:rightConfiguration:leftConfiguration:title:titleAccessibilityLabel:)")
func addRightLeftOnKeyboardWithTarget(_ target: AnyObject?,
leftButtonTitle: String,
rightButtonTitle: String,
leftButtonAction: Selector,
rightButtonAction: Selector,
titleText: String?,
titleAccessibilityLabel: String? = nil) {
}
@available(*, unavailable, renamed: "iq.addRightLeft(target:rightConfiguration:leftConfiguration:title:titleAccessibilityLabel:)")
func addRightLeftOnKeyboardWithTarget(_ target: AnyObject?,
leftButtonImage: UIImage,
rightButtonImage: UIImage,
leftButtonAction: Selector,
rightButtonAction: Selector,
titleText: String?,
titleAccessibilityLabel: String? = nil) {
}
@available(*, unavailable, renamed: "iq.addPreviousNextDone(target:previousAction:nextAction:doneAction:showPlaceholder:titleAccessibilityLabel:)")
func addPreviousNextDone(_ target: AnyObject?,
previousAction: Selector,
nextAction: Selector,
doneAction: Selector,
shouldShowPlaceholder: Bool = false,
titleAccessibilityLabel: String? = nil) {
}
@available(*, unavailable, renamed: "iq.addPreviousNextDone(target:previousAction:nextAction:doneAction:title:titleAccessibilityLabel:)")
func addPreviousNextDoneOnKeyboardWithTarget(_ target: AnyObject?,
previousAction: Selector,
nextAction: Selector,
doneAction: Selector,
titleText: String?,
titleAccessibilityLabel: String? = nil) {
}
@available(*, unavailable, renamed: "iq.addPreviousNextRight(target:previousConfiguration:nextConfiguration:rightConfiguration:showPlaceholder:titleAccessibilityLabel:)")
func addPreviousNextRightOnKeyboardWithTarget(_ target: AnyObject?,
rightButtonImage: UIImage,
previousAction: Selector,
nextAction: Selector,
rightButtonAction: Selector,
shouldShowPlaceholder: Bool = false,
titleAccessibilityLabel: String? = nil) {
}
@available(*, unavailable, renamed: "iq.addPreviousNextRight(target:previousConfiguration:nextConfiguration:rightConfiguration:showPlaceholder:titleAccessibilityLabel:)")
func addPreviousNextRightOnKeyboardWithTarget(_ target: AnyObject?,
rightButtonTitle: String,
previousAction: Selector,
nextAction: Selector,
rightButtonAction: Selector,
shouldShowPlaceholder: Bool = false,
titleAccessibilityLabel: String? = nil) {
}
@available(*, unavailable, renamed: "iq.addPreviousNextRight(target:previousConfiguration:nextConfiguration:rightConfiguration:title:titleAccessibilityLabel:)")
func addPreviousNextRightOnKeyboardWithTarget(_ target: AnyObject?,
rightButtonImage: UIImage,
previousAction: Selector,
nextAction: Selector,
rightButtonAction: Selector,
titleText: String?,
titleAccessibilityLabel: String? = nil) {
}
@available(*, unavailable, renamed: "iq.addPreviousNextRight(target:previousConfiguration:nextConfiguration:rightConfiguration:title:titleAccessibilityLabel:)")
func addPreviousNextRightOnKeyboardWithTarget(_ target: AnyObject?,
rightButtonTitle: String,
previousAction: Selector,
nextAction: Selector,
rightButtonAction: Selector,
titleText: String?,
titleAccessibilityLabel: String? = nil) {
}
}
// swiftlint:enable unused_setter_value
// swiftlint:enable line_length
// swiftlint:enable function_parameter_count

View File

@ -1,115 +0,0 @@
//
// IQKeyboardListener.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
public class IQKeyboardListener {
private var sizeObservers: [AnyHashable: SizeCompletion] = [:]
private(set) var keyboardInfo: IQKeyboardInfo {
didSet {
if keyboardInfo != oldValue {
sendEvent()
}
}
}
public var keyboardShowing: Bool {
keyboardInfo.keyboardShowing
}
public var frame: CGRect {
keyboardInfo.frame
}
public init() {
keyboardInfo = IQKeyboardInfo(notification: nil, name: .didHide)
// Registering for keyboard notification.
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(_:)),
name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidShow(_:)),
name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(_:)),
name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidHide(_:)),
name: UIResponder.keyboardDidHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillChangeFrame(_:)),
name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidChangeFrame(_:)),
name: UIResponder.keyboardDidChangeFrameNotification, object: nil)
}
@objc private func keyboardWillShow(_ notification: Notification) {
keyboardInfo = IQKeyboardInfo(notification: notification, name: .willShow)
}
@objc private func keyboardDidShow(_ notification: Notification) {
keyboardInfo = IQKeyboardInfo(notification: notification, name: .didShow)
}
@objc private func keyboardWillHide(_ notification: Notification?) {
keyboardInfo = IQKeyboardInfo(notification: notification, name: .willHide)
}
@objc private func keyboardDidHide(_ notification: Notification) {
keyboardInfo = IQKeyboardInfo(notification: notification, name: .didHide)
}
@objc private func keyboardWillChangeFrame(_ notification: Notification) {
keyboardInfo = IQKeyboardInfo(notification: notification, name: .willChangeFrame)
}
@objc private func keyboardDidChangeFrame(_ notification: Notification) {
keyboardInfo = IQKeyboardInfo(notification: notification, name: .didChangeFrame)
}
public func animate(alongsideTransition transition: @escaping () -> Void, completion: (() -> Void)? = nil) {
keyboardInfo.animate(alongsideTransition: transition, completion: completion)
}
}
@available(iOSApplicationExtension, unavailable)
public extension IQKeyboardListener {
typealias SizeCompletion = (_ name: IQKeyboardInfo.Name, _ size: CGSize) -> Void
func registerSizeChange(identifier: AnyHashable, changeHandler: @escaping SizeCompletion) {
sizeObservers[identifier] = changeHandler
}
func unregisterSizeChange(identifier: AnyHashable) {
sizeObservers[identifier] = nil
}
private func sendEvent() {
let size: CGSize = keyboardInfo.frame.size
for block in sizeObservers.values {
block(keyboardInfo.name, size)
}
}
}

View File

@ -1,126 +0,0 @@
//
// IQTextFieldViewListener.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
public class IQTextFieldViewListener {
private var textFieldViewObservers: [AnyHashable: TextFieldViewCompletion] = [:]
#if swift(>=5.7)
private(set) var lastTextFieldViewInfo: IQTextFieldViewInfo?
#endif
private(set) var textFieldViewInfo: IQTextFieldViewInfo?
public var textFieldView: UIView? {
return textFieldViewInfo?.textFieldView
}
public init() {
// Registering for keyboard notification.
NotificationCenter.default.addObserver(self, selector: #selector(self.didBeginEditing(_:)),
name: UITextField.textDidBeginEditingNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.didEndEditing(_:)),
name: UITextField.textDidEndEditingNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.didBeginEditing(_:)),
name: UITextView.textDidBeginEditingNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.didEndEditing(_:)),
name: UITextView.textDidEndEditingNotification, object: nil)
}
@objc private func didBeginEditing(_ notification: Notification) {
guard let info: IQTextFieldViewInfo = IQTextFieldViewInfo(notification: notification,
name: .beginEditing) else {
return
}
#if swift(>=5.7)
if #available(iOS 16.0, *),
let lastTextFieldViewInfo = lastTextFieldViewInfo,
let textView: UITextView = lastTextFieldViewInfo.textFieldView as? UITextView,
textView.findInteraction?.isFindNavigatorVisible == true {
// // This means the this didBeginEditing call comes due to find interaction
textFieldViewInfo = lastTextFieldViewInfo
sendEvent(info: lastTextFieldViewInfo)
} else if textFieldViewInfo != info {
textFieldViewInfo = info
lastTextFieldViewInfo = nil
sendEvent(info: info)
} else {
lastTextFieldViewInfo = nil
}
#else
if textFieldViewInfo != info {
textFieldViewInfo = info
sendEvent(info: info)
}
#endif
}
@objc private func didEndEditing(_ notification: Notification) {
guard let info: IQTextFieldViewInfo = IQTextFieldViewInfo(notification: notification, name: .endEditing) else {
return
}
if textFieldViewInfo != info {
#if swift(>=5.7)
if #available(iOS 16.0, *),
let textView: UITextView = info.textFieldView as? UITextView,
textView.isFindInteractionEnabled {
lastTextFieldViewInfo = textFieldViewInfo
} else {
lastTextFieldViewInfo = nil
}
#endif
textFieldViewInfo = info
sendEvent(info: info)
textFieldViewInfo = nil
}
}
}
@available(iOSApplicationExtension, unavailable)
public extension IQTextFieldViewListener {
typealias TextFieldViewCompletion = (_ info: IQTextFieldViewInfo) -> Void
func registerTextFieldViewChange(identifier: AnyHashable, changeHandler: @escaping TextFieldViewCompletion) {
textFieldViewObservers[identifier] = changeHandler
}
func unregisterSizeChange(identifier: AnyHashable) {
textFieldViewObservers[identifier] = nil
}
private func sendEvent(info: IQTextFieldViewInfo) {
for block in textFieldViewObservers.values {
block(info)
}
}
}

View File

@ -1,135 +0,0 @@
//
// IQKeyboardInfo.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
public struct IQKeyboardInfo: Equatable {
nonisolated public static func == (lhs: Self, rhs: Self) -> Bool {
lhs.frame.equalTo(rhs.frame)
}
@objc public enum Name: Int {
case willShow
case didShow
case willHide
case didHide
case willChangeFrame
case didChangeFrame
}
public let name: Name
/** To save keyboard frame. */
public let frame: CGRect
/** To save keyboard animation duration. */
public let animationDuration: TimeInterval
/** To mimic the keyboard animation */
public let animationCurve: UIView.AnimationCurve
public var keyboardShowing: Bool {
frame.height > 0
}
public init(notification: Notification?, name: Name) {
self.name = name
let screenBounds: CGRect
if #available(iOS 13.0, *), let screen: UIScreen = notification?.object as? UIScreen {
screenBounds = screen.bounds
} else {
screenBounds = UIScreen.main.bounds
}
if let info: [AnyHashable: Any] = notification?.userInfo {
// Getting keyboard animation.
if let curveValue: Int = info[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int,
let curve: UIView.AnimationCurve = UIView.AnimationCurve(rawValue: curveValue) {
animationCurve = curve
} else {
animationCurve = .easeOut
}
// Getting keyboard animation duration
if let duration: TimeInterval = info[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval,
duration != 0.0 {
animationDuration = duration
} else {
animationDuration = 0.25
}
// Getting UIKeyboardSize.
if var kbFrame: CGRect = info[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {
// If this is floating keyboard
if kbFrame.width < screenBounds.width,
kbFrame.maxY < screenBounds.height {
kbFrame.size = CGSize(width: kbFrame.size.width, height: 0)
} else {
// (Bug ID: #469) (Bug ID: #381) (Bug ID: #1506)
// Calculating actual keyboard covered size respect to window,
// keyboard frame may be different when hardware keyboard is attached
let keyboardHeight = CGFloat.maximum(screenBounds.height - kbFrame.minY, 0)
kbFrame.size = CGSize(width: kbFrame.size.width, height: keyboardHeight)
}
frame = kbFrame
} else {
frame = CGRect(x: 0, y: screenBounds.height, width: screenBounds.width, height: 0)
}
} else {
animationCurve = .easeOut
animationDuration = 0.25
frame = CGRect(x: 0, y: screenBounds.height, width: screenBounds.width, height: 0)
}
}
public func animate(alongsideTransition transition: @escaping () -> Void, completion: (() -> Void)? = nil) {
// if let timing = UIView.AnimationCurve.RawValue(exactly: animationCurve.rawValue),
// let curve = UIView.AnimationCurve(rawValue: timing) {
// let animator = UIViewPropertyAnimator(duration: animationDuration, curve: curve) {
// transition()
// }
// animator.addCompletion { _ in
// completion?()
// }
// animator.isUserInteractionEnabled = true
// animator.startAnimation()
// } else {
var animationOptions: UIView.AnimationOptions = .init(rawValue: UInt(animationCurve.rawValue << 16))
animationOptions.formUnion(.allowUserInteraction)
animationOptions.formUnion(.beginFromCurrentState)
UIView.animate(withDuration: animationDuration, delay: 0,
options: animationOptions,
animations: transition,
completion: { _ in
completion?()
})
// }
}
}

View File

@ -1,57 +0,0 @@
//
// IQTextFieldViewInfo.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
public struct IQTextFieldViewInfo: Equatable {
nonisolated public static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.textFieldView == rhs.textFieldView &&
lhs.name == rhs.name
}
@MainActor
@objc public enum Name: Int {
case beginEditing
case endEditing
}
public let name: Name
public let textFieldView: UIView
public init?(notification: Notification?, name: Name) {
guard let view: UIView = notification?.object as? UIView else {
return nil
}
guard !view.iq.isAlertViewTextField() else {
return nil
}
self.name = name
textFieldView = view
}
}

View File

@ -1,154 +0,0 @@
//
// IQKeyboardReturnKeyHandler+TextFieldDelegate.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
// MARK: UITextFieldDelegate
@available(iOSApplicationExtension, unavailable)
extension IQKeyboardReturnKeyHandler: UITextFieldDelegate {
@objc public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
if delegate == nil {
if let unwrapDelegate: UITextFieldDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate {
if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldBeginEditing(_:))) {
return unwrapDelegate.textFieldShouldBeginEditing?(textField) ?? false
}
}
}
return true
}
@objc public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
if delegate == nil {
if let unwrapDelegate: UITextFieldDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate {
if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldEndEditing(_:))) {
return unwrapDelegate.textFieldShouldEndEditing?(textField) ?? false
}
}
}
return true
}
@objc public func textFieldDidBeginEditing(_ textField: UITextField) {
updateReturnKeyTypeOnTextField(textField)
var aDelegate: UITextFieldDelegate? = delegate
if aDelegate == nil {
if let model: IQTextFieldViewInfoModel = textFieldViewCachedInfo(textField) {
aDelegate = model.textFieldDelegate
}
}
aDelegate?.textFieldDidBeginEditing?(textField)
}
@objc public func textFieldDidEndEditing(_ textField: UITextField) {
var aDelegate: UITextFieldDelegate? = delegate
if aDelegate == nil {
if let model: IQTextFieldViewInfoModel = textFieldViewCachedInfo(textField) {
aDelegate = model.textFieldDelegate
}
}
aDelegate?.textFieldDidEndEditing?(textField)
}
@objc public func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {
var aDelegate: UITextFieldDelegate? = delegate
if aDelegate == nil {
if let model: IQTextFieldViewInfoModel = textFieldViewCachedInfo(textField) {
aDelegate = model.textFieldDelegate
}
}
aDelegate?.textFieldDidEndEditing?(textField, reason: reason)
}
@objc public func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
if delegate == nil {
if let unwrapDelegate: UITextFieldDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate {
let selector: Selector = #selector(UITextFieldDelegate.textField(_:shouldChangeCharactersIn:
replacementString:))
if unwrapDelegate.responds(to: selector) {
return unwrapDelegate.textField?(textField,
shouldChangeCharactersIn: range,
replacementString: string) ?? false
}
}
}
return true
}
@objc public func textFieldShouldClear(_ textField: UITextField) -> Bool {
if delegate == nil {
if let unwrapDelegate: UITextFieldDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate {
if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldClear(_:))) {
return unwrapDelegate.textFieldShouldClear?(textField) ?? false
}
}
}
return true
}
@objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
var isReturn: Bool = true
if delegate == nil {
if let unwrapDelegate: UITextFieldDelegate = textFieldViewCachedInfo(textField)?.textFieldDelegate {
if unwrapDelegate.responds(to: #selector(UITextFieldDelegate.textFieldShouldReturn(_:))) {
isReturn = unwrapDelegate.textFieldShouldReturn?(textField) ?? false
}
}
}
if isReturn {
goToNextResponderOrResign(textField)
return true
} else {
return goToNextResponderOrResign(textField)
}
}
}

View File

@ -1,347 +0,0 @@
//
// IQKeyboardReturnKeyHandler+TextViewDelegate.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
// MARK: UITextViewDelegate
@available(iOSApplicationExtension, unavailable)
extension IQKeyboardReturnKeyHandler: UITextViewDelegate {
@objc public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
if delegate == nil {
if let unwrapDelegate: UITextViewDelegate = textFieldViewCachedInfo(textView)?.textViewDelegate {
if unwrapDelegate.responds(to: #selector(UITextViewDelegate.textViewShouldBeginEditing(_:))) {
return unwrapDelegate.textViewShouldBeginEditing?(textView) ?? false
}
}
}
return true
}
@objc public func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
if delegate == nil {
if let unwrapDelegate: UITextViewDelegate = textFieldViewCachedInfo(textView)?.textViewDelegate {
if unwrapDelegate.responds(to: #selector(UITextViewDelegate.textViewShouldEndEditing(_:))) {
return unwrapDelegate.textViewShouldEndEditing?(textView) ?? false
}
}
}
return true
}
@objc public func textViewDidBeginEditing(_ textView: UITextView) {
updateReturnKeyTypeOnTextField(textView)
var aDelegate: UITextViewDelegate? = delegate
if aDelegate == nil {
if let model: IQTextFieldViewInfoModel = textFieldViewCachedInfo(textView) {
aDelegate = model.textViewDelegate
}
}
aDelegate?.textViewDidBeginEditing?(textView)
}
@objc public func textViewDidEndEditing(_ textView: UITextView) {
var aDelegate: UITextViewDelegate? = delegate
if aDelegate == nil {
if let model: IQTextFieldViewInfoModel = textFieldViewCachedInfo(textView) {
aDelegate = model.textViewDelegate
}
}
aDelegate?.textViewDidEndEditing?(textView)
}
@objc public func textView(_ textView: UITextView,
shouldChangeTextIn range: NSRange,
replacementText text: String) -> Bool {
var isReturn = true
if delegate == nil {
if let unwrapDelegate: UITextViewDelegate = textFieldViewCachedInfo(textView)?.textViewDelegate {
let selector: Selector = #selector(UITextViewDelegate.textView(_:shouldChangeTextIn:replacementText:))
if unwrapDelegate.responds(to: selector) {
isReturn = (unwrapDelegate.textView?(textView,
shouldChangeTextIn: range,
replacementText: text)) ?? false
}
}
}
if isReturn, text == "\n" {
isReturn = goToNextResponderOrResign(textView)
}
return isReturn
}
@objc public func textViewDidChange(_ textView: UITextView) {
var aDelegate: UITextViewDelegate? = delegate
if aDelegate == nil {
if let model: IQTextFieldViewInfoModel = textFieldViewCachedInfo(textView) {
aDelegate = model.textViewDelegate
}
}
aDelegate?.textViewDidChange?(textView)
}
@objc public func textViewDidChangeSelection(_ textView: UITextView) {
var aDelegate: UITextViewDelegate? = delegate
if aDelegate == nil {
if let model: IQTextFieldViewInfoModel = textFieldViewCachedInfo(textView) {
aDelegate = model.textViewDelegate
}
}
aDelegate?.textViewDidChangeSelection?(textView)
}
@objc public func textView(_ aTextView: UITextView,
shouldInteractWith URL: URL,
in characterRange: NSRange,
interaction: UITextItemInteraction) -> Bool {
if delegate == nil {
if let unwrapDelegate: 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
}
}
}
return true
}
@objc public func textView(_ aTextView: UITextView,
shouldInteractWith textAttachment: NSTextAttachment,
in characterRange: NSRange,
interaction: UITextItemInteraction) -> Bool {
if delegate == nil {
if let unwrapDelegate: 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
}
}
}
return true
}
@available(iOS, deprecated: 10.0)
@objc public func textView(_ aTextView: UITextView,
shouldInteractWith URL: URL,
in characterRange: NSRange) -> Bool {
if delegate == nil {
if let unwrapDelegate: UITextViewDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate {
if unwrapDelegate.responds(to: #selector(textView as (UITextView, URL, NSRange) -> Bool)) {
return unwrapDelegate.textView?(aTextView,
shouldInteractWith: URL,
in: characterRange) ?? false
}
}
}
return true
}
@available(iOS, deprecated: 10.0)
@objc public func textView(_ aTextView: UITextView,
shouldInteractWith textAttachment: NSTextAttachment,
in characterRange: NSRange) -> Bool {
if delegate == nil {
if let unwrapDelegate: UITextViewDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate {
if unwrapDelegate.responds(to: #selector(textView as (UITextView, NSTextAttachment, NSRange) -> Bool)) {
return unwrapDelegate.textView?(aTextView,
shouldInteractWith: textAttachment,
in: characterRange) ?? false
}
}
}
return true
}
}
#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: UITextViewDelegate = textFieldViewCachedInfo(aTextView)?.textViewDelegate {
let selector: Selector = #selector(textView as
(UITextView, NSRange, [UIMenuElement]) -> UIMenu?)
if unwrapDelegate.responds(to: selector) {
return unwrapDelegate.textView?(aTextView,
editMenuForTextIn: range,
suggestedActions: suggestedActions)
}
}
}
return nil
}
public func textView(_ aTextView: UITextView, willPresentEditMenuWith animator: UIEditMenuInteractionAnimating) {
var aDelegate: UITextViewDelegate? = delegate
if aDelegate == nil {
if let model: IQTextFieldViewInfoModel = textFieldViewCachedInfo(aTextView) {
aDelegate = model.textViewDelegate
}
}
aDelegate?.textView?(aTextView, willPresentEditMenuWith: animator)
}
public func textView(_ aTextView: UITextView, willDismissEditMenuWith animator: UIEditMenuInteractionAnimating) {
var aDelegate: UITextViewDelegate? = delegate
if aDelegate == nil {
if let model: IQTextFieldViewInfoModel = textFieldViewCachedInfo(aTextView) {
aDelegate = model.textViewDelegate
}
}
aDelegate?.textView?(aTextView, willDismissEditMenuWith: animator)
}
}
#endif
#if swift(>=5.9)
@available(iOS 17.0, *)
@available(iOSApplicationExtension, unavailable)
extension IQKeyboardReturnKeyHandler {
public func textView(_ aTextView: UITextView,
primaryActionFor textItem: UITextItem,
defaultAction: UIAction) -> UIAction? {
if delegate == 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)
}
}
}
return nil
}
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)
}
}
}
return nil
}
public func textView(_ textView: UITextView,
textItemMenuWillDisplayFor textItem: UITextItem,
animator: UIContextMenuInteractionAnimating) {
var aDelegate: UITextViewDelegate? = delegate
if aDelegate == nil {
if let model = textFieldViewCachedInfo(textView) {
aDelegate = model.textViewDelegate
}
}
aDelegate?.textView?(textView, textItemMenuWillDisplayFor: textItem, animator: animator)
}
public func textView(_ textView: UITextView,
textItemMenuWillEndFor textItem: UITextItem,
animator: UIContextMenuInteractionAnimating) {
var aDelegate: UITextViewDelegate? = delegate
if aDelegate == nil {
if let model = textFieldViewCachedInfo(textView) {
aDelegate = model.textViewDelegate
}
}
aDelegate?.textView?(textView, textItemMenuWillEndFor: textItem, animator: animator)
}
}
#endif

View File

@ -1,261 +0,0 @@
//
// IQKeyboardReturnKeyHandler.swift
// https://github.com/hackiftekhar/IQKeyboardManager
// Copyright (c) 2013-24 Iftekhar Qurashi.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import UIKit
/**
Manages the return key to work like next/done in a view hierarchy.
*/
@available(iOSApplicationExtension, unavailable)
@MainActor
@objc public final class IQKeyboardReturnKeyHandler: NSObject {
// MARK: Settings
/**
Delegate of textField/textView.
*/
@objc public weak var delegate: (UITextFieldDelegate & UITextViewDelegate)?
/**
Set the last textfield return key type. Default is UIReturnKeyDefault.
*/
@objc public var lastTextFieldReturnKeyType: UIReturnKeyType = UIReturnKeyType.default {
didSet {
for model in textFieldInfoCache {
if let view: UIView = model.textFieldView {
updateReturnKeyTypeOnTextField(view)
}
}
}
}
// MARK: Initialization/De-initialization
@objc public override init() {
super.init()
}
/**
Add all the textFields available in UIViewController's view.
*/
@objc public init(controller: UIViewController) {
super.init()
addResponderFromView(controller.view)
}
deinit {
// for model in textFieldInfoCache {
// model.restore()
// }
textFieldInfoCache.removeAll()
}
// MARK: Private variables
private var textFieldInfoCache: [IQTextFieldViewInfoModel] = []
// MARK: Private Functions
internal func textFieldViewCachedInfo(_ textField: UIView) -> IQTextFieldViewInfoModel? {
for model in textFieldInfoCache {
if let view: UIView = model.textFieldView {
if view == textField {
return model
}
}
}
return nil
}
internal func updateReturnKeyTypeOnTextField(_ view: UIView) {
var superConsideredView: UIView?
// If find any consider responderView in it's upper hierarchy then will get deepResponderView. (Bug ID: #347)
for allowedClasse in IQKeyboardManager.shared.toolbarPreviousNextAllowedClasses {
superConsideredView = view.iq.superviewOf(type: allowedClasse)
if superConsideredView != nil {
break
}
}
var textFields: [UIView] = []
// If there is a tableView in view's hierarchy, then fetching all it's subview that responds.
if let unwrappedTableView: UIView = superConsideredView { // (Enhancement ID: #22)
textFields = unwrappedTableView.iq.deepResponderViews()
} else { // Otherwise fetching all the siblings
textFields = view.iq.responderSiblings()
// Sorting textFields according to behavior
switch IQKeyboardManager.shared.toolbarConfiguration.manageBehavior {
// If needs to sort it by tag
case .byTag: textFields = textFields.sortedByTag()
// If needs to sort it by Position
case .byPosition: textFields = textFields.sortedByPosition()
default: break
}
}
if let lastView: UIView = textFields.last {
if let textField: UITextField = view as? UITextField {
// If it's the last textField in responder view, else next
textField.returnKeyType = (view == lastView) ? lastTextFieldReturnKeyType: UIReturnKeyType.next
} else if let textView: UITextView = view as? UITextView {
// If it's the last textField in responder view, else next
textView.returnKeyType = (view == lastView) ? lastTextFieldReturnKeyType: UIReturnKeyType.next
}
}
}
// MARK: Registering/Unregistering textFieldView
/**
Should pass UITextField/UITextView instance. Assign textFieldView delegate to self, change it's returnKeyType.
@param view UITextField/UITextView object to register.
*/
@objc public func addTextFieldView(_ view: UIView) {
if let textField: UITextField = view as? UITextField {
let model = IQTextFieldViewInfoModel(textField: textField)
textFieldInfoCache.append(model)
textField.delegate = self
} else if let textView: UITextView = view as? UITextView {
let model = IQTextFieldViewInfoModel(textView: textView)
textFieldInfoCache.append(model)
textView.delegate = self
}
}
/**
Should pass UITextField/UITextView instance. Restore it's textFieldView delegate and it's returnKeyType.
@param view UITextField/UITextView object to unregister.
*/
@objc public func removeTextFieldView(_ view: UIView) {
if let model: IQTextFieldViewInfoModel = textFieldViewCachedInfo(view) {
model.restore()
if let index: Int = textFieldInfoCache.firstIndex(where: { $0.textFieldView == view}) {
textFieldInfoCache.remove(at: index)
}
}
}
/**
Add all the UITextField/UITextView responderView's.
@param view UIView object to register all it's responder subviews.
*/
@objc public func addResponderFromView(_ view: UIView) {
let textFields: [UIView] = view.iq.deepResponderViews()
for textField in textFields {
addTextFieldView(textField)
}
}
/**
Remove all the UITextField/UITextView responderView's.
@param view UIView object to unregister all it's responder subviews.
*/
@objc public func removeResponderFromView(_ view: UIView) {
let textFields: [UIView] = view.iq.deepResponderViews()
for textField in textFields {
removeTextFieldView(textField)
}
}
@discardableResult
internal func goToNextResponderOrResign(_ view: UIView) -> Bool {
var superConsideredView: UIView?
// If find any consider responderView in it's upper hierarchy then will get deepResponderView. (Bug ID: #347)
for allowedClass in IQKeyboardManager.shared.toolbarPreviousNextAllowedClasses {
superConsideredView = view.iq.superviewOf(type: allowedClass)
if superConsideredView != nil {
break
}
}
var textFields: [UIView] = []
// If there is a tableView in view's hierarchy, then fetching all it's subview that responds.
if let unwrappedTableView: UIView = superConsideredView { // (Enhancement ID: #22)
textFields = unwrappedTableView.iq.deepResponderViews()
} else { // Otherwise fetching all the siblings
textFields = view.iq.responderSiblings()
// Sorting textFields according to behavior
switch IQKeyboardManager.shared.toolbarConfiguration.manageBehavior {
// If needs to sort it by tag
case .byTag: textFields = textFields.sortedByTag()
// If needs to sort it by Position
case .byPosition: textFields = textFields.sortedByPosition()
default:
break
}
}
// Getting index of current textField.
if let index: Int = textFields.firstIndex(of: view) {
// If it is not last textField. then it's next object becomeFirstResponder.
if index < (textFields.count - 1) {
let nextTextField: UIView = textFields[index+1]
nextTextField.becomeFirstResponder()
return false
} else {
view.resignFirstResponder()
return true
}
} else {
return true
}
}
}

View File

@ -1,56 +0,0 @@
//
// IQTextFieldViewInfoModel.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
internal final class IQTextFieldViewInfoModel: NSObject {
weak var textFieldDelegate: UITextFieldDelegate?
weak var textViewDelegate: UITextViewDelegate?
weak var textFieldView: UIView?
let originalReturnKeyType: UIReturnKeyType
init(textField: UITextField) {
self.textFieldView = textField
self.textFieldDelegate = textField.delegate
self.originalReturnKeyType = textField.returnKeyType
}
init(textView: UITextView) {
self.textFieldView = textView
self.textViewDelegate = textView.delegate
self.originalReturnKeyType = textView.returnKeyType
}
func restore() {
if let textField = textFieldView as? UITextField {
textField.returnKeyType = originalReturnKeyType
textField.delegate = textFieldDelegate
} else if let textView = textFieldView as? UITextView {
textView.returnKeyType = originalReturnKeyType
textView.delegate = textViewDelegate
}
}
}

View File

@ -1,57 +0,0 @@
//
// IQNSArray+Sort.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
/**
UIView.subviews sorting category.
*/
@available(iOSApplicationExtension, unavailable)
@MainActor
internal extension Array where Element: UIView {
/**
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

@ -1,48 +0,0 @@
//
// IQUICollectionView+Additions.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
internal extension UICollectionView {
func previousIndexPath(of indexPath: IndexPath) -> IndexPath? {
var previousRow: Int = indexPath.row - 1
var previousSection: Int = indexPath.section
// Fixing indexPath
if previousRow < 0 {
previousSection -= 1
if previousSection >= 0 {
previousRow = self.numberOfItems(inSection: previousSection) - 1
}
}
if previousRow >= 0, previousSection >= 0 {
return IndexPath(item: previousRow, section: previousSection)
} else {
return nil
}
}
}

View File

@ -1,116 +0,0 @@
//
// IQUIScrollView+Additions.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
private struct AssociatedKeys {
static var ignoreScrollingAdjustment: Int = 0
static var ignoreContentInsetAdjustment: Int = 0
static var restoreContentOffset: Int = 0
}
// swiftlint:disable identifier_name
// swiftlint:disable unused_setter_value
@available(iOSApplicationExtension, unavailable)
extension UIScrollView: IQKeyboardManagerCompatible {
// This property is explicitly written otherwise we were having
// compilation error when archiving
public var iq: IQKeyboardManagerWrapper<UIView> {
get { IQKeyboardManagerWrapper(self) }
set {}
}
}
// swiftlint:enable unused_setter_value
// swiftlint:enable identifier_name
@available(iOSApplicationExtension, unavailable)
@MainActor
public extension IQKeyboardManagerWrapper where Base: UIScrollView {
/**
If YES, then scrollview will ignore scrolling (simply not scroll it) for adjusting textfield position.
Default is NO.
*/
var ignoreScrollingAdjustment: Bool {
get {
return objc_getAssociatedObject(base, &AssociatedKeys.ignoreScrollingAdjustment) as? Bool ?? false
}
set(newValue) {
objc_setAssociatedObject(base, &AssociatedKeys.ignoreScrollingAdjustment,
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
/**
If YES, then scrollview will ignore content inset adjustment (simply not updating it) when keyboard is shown.
Default is NO.
*/
var ignoreContentInsetAdjustment: Bool {
get {
return objc_getAssociatedObject(base, &AssociatedKeys.ignoreContentInsetAdjustment) as? Bool ?? false
}
set(newValue) {
objc_setAssociatedObject(base, &AssociatedKeys.ignoreContentInsetAdjustment,
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
/**
To set customized distance from keyboard for textField/textView. Can't be less than zero
*/
var restoreContentOffset: Bool {
get {
return objc_getAssociatedObject(base, &AssociatedKeys.restoreContentOffset) as? Bool ?? false
}
set(newValue) {
objc_setAssociatedObject(base, &AssociatedKeys.restoreContentOffset,
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
// swiftlint:disable unused_setter_value
@available(iOSApplicationExtension, unavailable)
@objc public extension UIScrollView {
@available(*, unavailable, renamed: "iq.ignoreScrollingAdjustment")
var shouldIgnoreScrollingAdjustment: Bool {
get { false }
set { }
}
@available(*, unavailable, renamed: "iq.ignoreContentInsetAdjustment")
var shouldIgnoreContentInsetAdjustment: Bool {
get { false }
set { }
}
@available(*, unavailable, renamed: "iq.restoreContentOffset")
var shouldRestoreScrollViewContentOffset: Bool {
get { false }
set { }
}
}
// swiftlint:enable unused_setter_value

View File

@ -1,48 +0,0 @@
//
// IQUITableView+Additions.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
internal extension UITableView {
func previousIndexPath(of indexPath: IndexPath) -> IndexPath? {
var previousRow: Int = indexPath.row - 1
var previousSection: Int = indexPath.section
// Fixing indexPath
if previousRow < 0 {
previousSection -= 1
if previousSection >= 0 {
previousRow = self.numberOfRows(inSection: previousSection) - 1
}
}
if previousRow >= 0, previousSection >= 0 {
return IndexPath(row: previousRow, section: previousSection)
} else {
return nil
}
}
}

View File

@ -1,137 +0,0 @@
//
// IQUITextFieldView+Additions.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
private struct AssociatedKeys {
static var distanceFromKeyboard: Int = 0
static var ignoreSwitchingByNextPrevious: Int = 0
static var enableMode: Int = 0
static var resignOnTouchOutsideMode: Int = 0
}
@available(iOSApplicationExtension, unavailable)
extension UIView: IQKeyboardManagerCompatible {
public static let defaultKeyboardDistance: CGFloat = CGFloat.greatestFiniteMagnitude
}
@available(iOSApplicationExtension, unavailable)
@available(*, unavailable, renamed: "UIView.defaultKeyboardDistance")
public let kIQUseDefaultKeyboardDistance = CGFloat.greatestFiniteMagnitude
/**
UIView category for managing UITextField/UITextView
*/
@available(iOSApplicationExtension, unavailable)
@MainActor
public extension IQKeyboardManagerWrapper where Base: UIView {
/**
To set customized distance from keyboard for textField/textView. Can't be less than zero
*/
var distanceFromKeyboard: CGFloat {
get {
if let value = objc_getAssociatedObject(base, &AssociatedKeys.distanceFromKeyboard) as? CGFloat {
return value
} else {
return UIView.defaultKeyboardDistance
}
}
set(newValue) {
objc_setAssociatedObject(base, &AssociatedKeys.distanceFromKeyboard,
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
/**
If ignoreSwitchingByNextPrevious is true then library will ignore this textField/textView
while moving to other textField/textView using keyboard toolbar next previous buttons.
Default is false
*/
var ignoreSwitchingByNextPrevious: Bool {
get {
return objc_getAssociatedObject(base, &AssociatedKeys.ignoreSwitchingByNextPrevious) as? Bool ?? false
}
set(newValue) {
objc_setAssociatedObject(base, &AssociatedKeys.ignoreSwitchingByNextPrevious,
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
/**
Override Enable/disable managing distance between keyboard and textField behavior for this particular textField.
*/
var enableMode: IQEnableMode {
get {
return objc_getAssociatedObject(base, &AssociatedKeys.enableMode) as? IQEnableMode ?? .default
}
set(newValue) {
objc_setAssociatedObject(base, &AssociatedKeys.enableMode, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
/**
Override resigns Keyboard on touching outside of UITextField/View behavior for this particular textField.
*/
var resignOnTouchOutsideMode: IQEnableMode {
get {
return objc_getAssociatedObject(base, &AssociatedKeys.resignOnTouchOutsideMode) as? IQEnableMode ?? .default
}
set(newValue) {
objc_setAssociatedObject(base, &AssociatedKeys.resignOnTouchOutsideMode,
newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
// swiftlint:disable unused_setter_value
@available(iOSApplicationExtension, unavailable)
@objc public extension UIView {
@available(*, unavailable, renamed: "iq.distanceFromKeyboard")
var keyboardDistanceFromTextField: CGFloat {
get { 0 }
set { }
}
@available(*, unavailable, renamed: "iq.ignoreSwitchingByNextPrevious")
var ignoreSwitchingByNextPrevious: Bool {
get { false }
set { }
}
@available(*, unavailable, renamed: "iq.enableMode")
var enableMode: IQEnableMode {
get { .default }
set { }
}
@available(*, unavailable, renamed: "iq.resignOnTouchOutsideMode")
var shouldResignOnTouchOutsideMode: IQEnableMode {
get { .default }
set { }
}
}
// swiftlint:enable unused_setter_value

View File

@ -1,354 +0,0 @@
//
// IQUIView+Hierarchy.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
/**
UIView hierarchy category.
*/
@available(iOSApplicationExtension, unavailable)
@MainActor
public extension IQKeyboardManagerWrapper where Base: UIView {
// MARK: viewControllers
/**
Returns the UIViewController object that manages the receiver.
*/
func viewContainingController() -> UIViewController? {
var nextResponder: UIResponder? = base
repeat {
nextResponder = nextResponder?.next
if let viewController: UIViewController = nextResponder as? UIViewController {
return viewController
}
} while nextResponder != nil
return nil
}
/**
Returns the topMost UIViewController object in hierarchy.
*/
func topMostController() -> UIViewController? {
var controllersHierarchy: [UIViewController] = []
if var topController: UIViewController = base.window?.rootViewController {
controllersHierarchy.append(topController)
while let presented: UIViewController = topController.presentedViewController {
topController = presented
controllersHierarchy.append(presented)
}
var matchController: UIResponder? = viewContainingController()
while let mController: UIViewController = matchController as? UIViewController,
!controllersHierarchy.contains(mController) {
repeat {
matchController = matchController?.next
} while matchController != nil && matchController is UIViewController == false
}
return matchController as? UIViewController
} else {
return viewContainingController()
}
}
/**
Returns the UIViewController object that is actually the parent of this object.
Most of the time it's the viewController object which actually contains it,
but result may be different if it's viewController is added as childViewController of another viewController.
*/
func parentContainerViewController() -> UIViewController? {
var matchController: UIViewController? = viewContainingController()
var parentContainerViewController: UIViewController?
if var navController: UINavigationController = matchController?.navigationController {
while let parentNav: UINavigationController = navController.navigationController {
navController = parentNav
}
var parentController: UIViewController = navController
while let parent: UIViewController = parentController.parent,
!(parent is UINavigationController) &&
!(parent is UITabBarController) &&
!(parent is UISplitViewController) {
parentController = parent
}
if navController == parentController {
parentContainerViewController = navController.topViewController
} else {
parentContainerViewController = parentController
}
} else if let tabController: UITabBarController = matchController?.tabBarController {
let selectedController = tabController.selectedViewController
if let navController: UINavigationController = selectedController as? UINavigationController {
parentContainerViewController = navController.topViewController
} else {
parentContainerViewController = tabController.selectedViewController
}
} else {
while let parent: UIViewController = matchController?.parent,
!(parent is UINavigationController) &&
!(parent is UITabBarController) &&
!(parent is UISplitViewController) {
matchController = parent
}
parentContainerViewController = matchController
}
if let controller: UIViewController = parentContainerViewController?.iq_parentContainerViewController() {
return controller
} else {
return parentContainerViewController
}
}
// MARK: Superviews/Subviews/Siblings
/**
Returns the superView of provided class type.
@param classType class type of the object which is to be search in above hierarchy and return
@param belowView view object in upper hierarchy where method should stop searching and return nil
*/
func superviewOf<T: UIView>(type classType: T.Type, belowView: UIView? = nil) -> T? {
var superView: UIView? = base.superview
while let unwrappedSuperView: UIView = superView {
if unwrappedSuperView.isKind(of: classType) {
// If it's UIScrollView, then validating for special cases
if unwrappedSuperView is UIScrollView {
let classNameString: String = "\(type(of: unwrappedSuperView.self))"
// If it's not UITableViewWrapperView class,
// this is internal class which is actually manage in UITableview.
// The speciality of this class is that it's superview is UITableView.
// If it's not UITableViewCellScrollView class,
// this is internal class which is actually manage in UITableviewCell.
// The speciality of this class is that it's superview is UITableViewCell.
// If it's not _UIQueuingScrollView class,
// actually we validate for _ prefix which usually used by Apple internal classes
if !(unwrappedSuperView.superview is UITableView),
!(unwrappedSuperView.superview is UITableViewCell),
!classNameString.hasPrefix("_") {
return superView as? T
}
} else {
return superView as? T
}
} else if unwrappedSuperView == belowView {
return nil
}
superView = unwrappedSuperView.superview
}
return nil
}
}
@available(iOSApplicationExtension, unavailable)
@MainActor
internal extension IQKeyboardManagerWrapper where Base: UIView {
/**
Returns all siblings of the receiver which canBecomeFirstResponder.
*/
func responderSiblings() -> [UIView] {
// Array of (UITextField/UITextView's).
var tempTextFields: [UIView] = []
// Getting all siblings
if let siblings: [UIView] = base.superview?.subviews {
for textField in siblings {
if textField == base || !textField.iq.ignoreSwitchingByNextPrevious,
textField.iq.canBecomeFirstResponder() {
tempTextFields.append(textField)
}
}
}
return tempTextFields
}
/**
Returns all deep subViews of the receiver which canBecomeFirstResponder.
*/
func deepResponderViews() -> [UIView] {
// Array of (UITextField/UITextView's).
var textfields: [UIView] = []
for textField in base.subviews {
if textField == base || !textField.iq.ignoreSwitchingByNextPrevious,
textField.iq.canBecomeFirstResponder() {
textfields.append(textField)
}
// Sometimes there are hidden or disabled views and textField inside them still recorded,
// so we added some more validations here (Bug ID: #458)
// Uncommented else (Bug ID: #625)
else if textField.subviews.count != 0, base.isUserInteractionEnabled, !base.isHidden, base.alpha != 0.0 {
for deepView in textField.iq.deepResponderViews() {
textfields.append(deepView)
}
}
}
// subviews are returning in opposite order. Sorting according the frames 'y'.
return textfields.sorted(by: { (view1: UIView, view2: UIView) -> 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
}
})
}
private func canBecomeFirstResponder() -> Bool {
var canBecomeFirstResponder: Bool = false
if base.conforms(to: UITextInput.self) {
// Setting toolbar to keyboard.
if let textView: UITextView = base as? UITextView {
canBecomeFirstResponder = textView.isEditable
} else if let textField: UITextField = base as? UITextField {
canBecomeFirstResponder = textField.isEnabled
}
}
if canBecomeFirstResponder {
canBecomeFirstResponder = base.isUserInteractionEnabled &&
!base.isHidden &&
base.alpha != 0.0 &&
!isAlertViewTextField() &&
textFieldSearchBar() == nil
}
return canBecomeFirstResponder
}
// MARK: Special TextFields
/**
Returns searchBar if receiver object is UISearchBarTextField, otherwise return nil.
*/
func textFieldSearchBar() -> UISearchBar? {
var responder: UIResponder? = base.next
while let bar: UIResponder = responder {
if let searchBar: UISearchBar = bar as? UISearchBar {
return searchBar
} else if bar is UIViewController {
break
}
responder = bar.next
}
return nil
}
/**
Returns YES if the receiver object is UIAlertSheetTextField, otherwise return NO.
*/
func isAlertViewTextField() -> Bool {
var alertViewController: UIResponder? = viewContainingController()
var isAlertViewTextField: Bool = false
while let controller: UIResponder = alertViewController, !isAlertViewTextField {
if controller is UIAlertController {
isAlertViewTextField = true
break
}
alertViewController = controller.next
}
return isAlertViewTextField
}
func depth() -> Int {
var depth: Int = 0
if let superView: UIView = base.superview {
depth = superView.iq.depth()+1
}
return depth
}
}
@available(iOSApplicationExtension, unavailable)
@objc public extension UIView {
@available(*, unavailable, renamed: "iq.viewContainingController()")
func viewContainingController() -> UIViewController? { nil }
@available(*, unavailable, renamed: "iq.topMostController()")
func topMostController() -> UIViewController? { nil }
@available(*, unavailable, renamed: "iq.parentContainerViewController()")
func parentContainerViewController() -> UIViewController? { nil }
@available(*, unavailable, renamed: "iq.superviewOf(type:belowView:)")
func superviewOfClassType(_ classType: UIView.Type, belowView: UIView? = nil) -> UIView? { nil }
}

View File

@ -1,48 +0,0 @@
//
// IQUIViewController+Additions.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 extension UIViewController {
/**
This method is provided to override by viewController's
if the library lifts a viewController which you doesn't want to lift.
This may happen if you have implemented side menu feature
in your app and the library try to lift the side menu controller.
Overriding this method in side menu class to return correct controller should fix the problem.
*/
open func iq_parentContainerViewController() -> UIViewController? {
return self
}
}
@available(iOSApplicationExtension, unavailable)
@objc extension UIViewController {
@available(*, unavailable, renamed: "iq_parentContainerViewController()")
open func parentIQContainerViewController() -> UIViewController? {
return self
}
}

View File

@ -1,60 +0,0 @@
//
// UIImage+NextPrevious.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
/**
UIImage category methods to get next/prev images
*/
@available(iOSApplicationExtension, unavailable)
@objc public extension UIImage {
static let keyboardPreviousImage: UIImage = {
// swiftlint:disable line_length
let base64Data: String = "iVBORw0KGgoAAAANSUhEUgAAAD8AAAAkCAYAAAA+TuKHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABWWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgpMwidZAAAGmklEQVRoBd1ZWWwbRRie2bVz27s2adPGxzqxqAQCIRA3CDVJGxpKaEtRoSAVISQQggdeQIIHeIAHkOCBFyQeKlARhaYHvUJa0ksVoIgKUKFqKWqdeG2nR1Lsdeo0h73D54iku7NO6ySOk3alyPN//+zM/81/7MyEkDl66j2eJXWK8vocTT82rTgXk/t8vqBNEI9QSp9zOeVkPJnomgs7ik5eUZQ6OxGOEEq9WcKUksdlWbqU0LRfi70ARSXv8Xi8dkE8CsJ+I1FK6BNYgCgW4A8jPtvtopFHqNeWCLbDIF6fkxQjK91O1z9IgRM59bMAFoV8YEFgka1EyBJfMhkH5L9ACFstS9IpRMDJyfoVEp918sGamoVCme0QyN3GG87wAKcTOBYA4hrJKf+VSCb+nsBnqYHVnr2ntra2mpWWH0BVu52fhRH2XSZDmsA/xensokC21Pv9T3J4wcWrq17gob1er7tEhMcJuYsfGoS3hdTweuBpxaM0iCJph8fLuX7DJMPWnI2GOzi8YOKseD4gB+RSQezMRRx5vRPEn88Sz7IIx8KHgT3FCBniWJUyke6o8/uXc3jBxIKTd7vdTsFJfkSo38NbCY/vPRsOPwt81KgLqeoBXc+sBjZsxLF4ZfgM7goqSqMRL1S7oOSrq6sdLodjH0rYfbyByPEOePwZ4CO8Liv3RCL70Wctr8+mA2NkT53P91iu92aCFYx8TU1NpbOi8gfs2R7iDYLxnXqYPg3c5Fm+Xygcbs/omXXATZGBBagQqNAe9Psf4d+ZiVwQ8qjqFVVl5dmi9ShvDEL90IieXtVDevic5ruOyYiAXYiA9YSxsZow0YnSKkKFjoAn8OAENsPGjKs9qnp5iSDuBXFLXsLjR4fSIy29vb2DU7UThW4d8n0zxjXtRVAYNaJnlocikWNTHZPvP1PPl2LLujM3cfbzwJXUyukQzxrZraptRCcbEDm60Wh4S0IE7McByVJQjf3yac+EfEm9ouxAcWu2TsS6koOplr6+vstWXf5IKBrejBR4ybIAlLpE1JE6j8eyh8h/dEKmS95e7w9sy57G+MkQ6sdYMrmiv79/gNdNR0YEbGKUvIIFQMRffRBtbkG0HQj6fHdcRafWmg55Gzy+BR5vtUzF2O96kjSH4nHNopsB0B0Ob6SEvcYvAPYS1UwQDyqLFcu5IZ/pTMUkjxfEoD/wLVY9+z02PXDL8RE9s0y9qMZNigIJcU37TZblfj7aUAMqURLXuqqq9sQHBi5NZbqpkBfh8a9BPLtDMz3wyImh9GhTLBab0uSmQfIQcNQ95pJkDVG3wtgdC1KFA+HaSodjdzKZ/Neou1Y7X/JC0K98BeIvWAdjp+jwUKN6/nyfVVd4JK4lunDrkwJhc6Gl1GGjwhqnLO3UNC2Rz8z5kKfw+EYQf5EfEKF+Wh+kDd0XYxd43WzKiIBfEAEjiIAm0zyUSFiU1XJF+feJy5evW3euR57C41+A+MumSbICY2dGmd6gnlPPWXRFABABP7llCXsA2mCcDjVAJoK4qryycsfAwEDSqOPb1yQPj38O4q/yL4F4aCiTXhqNRmMWXREBFMGjslOywUbToQeyyy4IrVVO53bUgEk/uZOSr/MHPsOd0hs8F4R6mI2ONKi9vRFeNxdyIqkddknOMhA2nyuy+wAqtEol8rbEYCLnZisneXj8UxB/00KGkUiGsqU90WiPRTeHACLgoNsp4eBDHzaagRS4RbCzle6ysq3xVIq/LiMW8ti5fYRVfMs4yFibsdgI05eqqhqy6OYBEE9qnSiCLhRB7tRHFzDR1oIasBU1wHTAMpHHjcmHIP4OzwXf8XMkk24IR6NneN18klEE97mc0gJwuN9oF+SFNlF8vNJR1YYacGVcN0Eet6XvY6Pw3rhi/Bc5fiEzShp7eiOnx7H5/IsI6EAELEIE3Gu0EymwyCbQZocktWEfMHa3MEa+zqe8KwjCB8bO/7f70kxvVGPqyRy6eQshAtpdsuTDN/9us5F0MQ4zTS5BaIsPDQ3jO+5/G+fjj82dIDF2CZeKjd3R6J8W3Y0BYFca+JJQssFqLuvSUqlmESHSiZywGzsgx+OZNFnWE4scN+I3WJshAnYjAm5FBNxptp16y+y2hICLEtOVMXJcI0xvDveGi/ofU7NxBZN0XIpuIIy0mUZkZNNZVf1kDAt6lZagEhjGnxbweh8wdbw5hOwdxHbwY/j9BpTM9xi4MGzFvZhpk3Bz8J5gkb19ym7cJr5w/wEmUjzJqoNVhwAAAABJRU5ErkJggg=="
// swiftlint:enable line_length
// Support for RTL languages like Arabic, Persia etc... (Bug ID: #448)
if let data: Data = Data(base64Encoded: base64Data, options: .ignoreUnknownCharacters),
let image = UIImage(data: data, scale: 3)?.imageFlippedForRightToLeftLayoutDirection() {
return image
}
return UIImage()
}()
static let keyboardNextImage: UIImage = {
// swiftlint:disable line_length
let base64Data: String = "iVBORw0KGgoAAAANSUhEUgAAAD8AAAAkCAYAAAA+TuKHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABWWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgpMwidZAAAGp0lEQVRoBd1ZCWhcRRiemff25WrydmOtuXbfZlMo4lEpKkppm6TpZUovC4UqKlQoUhURqQcUBcWDIkhVUCuI9SpJa+2h0VZjUawUEUUUirLNXqmxSnc32WaT7O4bv0nd5R1bc+2maR8s7z9m5v+/+f/5Z94sIf89jW73Yp/bfUuWvwLfDp/H8zhwObLYmCCaPJ6FjLJPCWNHNU1bkFVeQW/Zp2l7KWUvNmlaB3DJAhvz1ntvI5R1EUpnUUKdEifHGuvr519BwKUmj/cDYNtwARNd5/NoH4GWKIhzlFKXCSzn/xCut/jD4V9N8suPYYj4ewC+2e46f55Rwp/geExKSmdzJn2l1WrXmuSXF8MQ8XfyAeeEn9KTyV3MHwq9RTh50IqLEjJHUkh3Y13dPKvuMuApIr6bUHKP1VeE+Y8MIa09Z8/+JQlltD/+Q7VaFcW6X2VsjFmbRRnbUFFZeai/v/+cUTeDaYqIv4GlfL/NR879I3qmORwOnxG6UfCCiMbjJ51VagKdlgs+91BaKVO6oVJVD8bj8WhOPkMJn1t7jTL6gNU9pHpgKJ1q7u3tjWR1OfBCEOuPf+9Sq4YwAW3ZBqNvSqsYpeuc5WUHYolE3KSbQYzP430FwB+yuoSCFtKHaXP4z3DIqDOBFwpkwHfVThXLgrYaG6IGOAmT1pZVVHw8MDDQb9TNBLrJre0E8EdtvnAeSRPeHOwN9lh1NvCiASbgG5fqRLDJEmMHsSU6GFuDGrAfNWDAqLuUNE5uL6A2bbf5wPkZrmdaAuGw36aDIC940TAajx1HBijIgEWmjpRWS4ytrnKq+1EDEibdJWAa3dqzjLGnrKaxxvt4OtXS09v7u1WX5S8KXjRABnQ7VbUCEV+Y7SDeWAJX4dfuLCnZFzt//rxRN500jqo74NvTVptY42fTnLcGI5FTVp2R/1/womEsHj/mwgxg27vd2BH8bCrLq0rKyjoTicSgUTcdNIrbkwD+nM2WOJ3qmaVI9d9sOotgTPCiPTLgi+oqdTbOAbea+lM6xyHLK8pnVXSiCCZNuiIyjZr2GArSS1YTOKie45n0UqT6L1ZdPn5c4EVHHIS6sA3WYLZvNg6E9L9GZmwZzgEdqAFDRl0xaET8EQB/2To21ngsQ0kbIv6zVXcxftzgxQDIgM+qVbUeGbDAPCCtxbfxUhdjHdGhoWGzrnAcIr4NwHflGbGf6PqyQCj0Yx7dRUUTAi9GwQQccapOL7bBm4yjIiPqSElpC5VYRzKZLPgE4M5hK0rt67CDZDM9A+k0XxmIhE6apONgJgxejBmLxw65VHUu/LjRaANeNZQpyhJZUToGBwdHjLqp0Ij4FgB/0wocaxw7DV8F4CcmM/6kwMMQRwYcrFad87DvXW8yTKlbkZVFSmlJB3bBlEk3CQYRvxfA3wbw0Vun7BAAPqjrmfaecPjbrGyib2sKTbS/LG5F4NhGe0d+fDiTuSMSiUx6F8Bn6V343N6TB3gSyb/aHwx22+2OX2KazfF3y7VMnw4FcUvCP8lJcgRtVph0yEu8pTnRBAiv270JwN+1AscQw5zr66YKXLgyVfBijBQc2YQ0PCIY4wPH2yQPERNTYpSPRSPid0qUvY/+1mU5QjJ8PVL96FhjjEdfCPDCzggyAKnPP7cZpWQFlsZ+yPGdMPaDiK/F6fEjbKeypXVK5/pGfyTYZZFPmi0UeOHAcCZI1+Oa6JjVG0SwHbcrnZDn7sytbQSPiLdLTBJXy+Z2nKcR8U09odDhfP0mKyskeBIggaERPb0WGfC1zSFK1gDcXsitER1t6m3wrkTEbRmC5ZTRCd+MiB+wjTlFwVSrfV7zdXV15aWy0oWKvNjWgJMOfyiAIklwYXLhwfd4G/47OAxnTMVRAKec3u0PB8SkFfyxFpSCGMBHTkpWHPsU2bEEKe8xDUrJdfhKnItzgiiEXKvXWhijR9CuzNgOwHWc1+87HQ5+aJQXki4KeOGgOOFJDkdnqeJowSGlweg00vsGHJAa1UpnTJKIAF5u1AM4R8S3APgeo7zQdFHS3uikz+VSSWXVlwBo+hoUbUR0ITfVHQEcEd+K4rbbOE4xaJPhYhg4HY3GcYG4HFB/so5vBT6q53TbdAAXtooe+SzghoaGakWSu2FwflZmfWMffxjAX7XKi8VPG3gBoKam5uoKpeQEDjBz7YD4dpwUd9rlxZMUPe2Nrvf19f2dTKdasap7jHIsiR3TDdxsfxq5xtpazad5g02al+Na6plpND0zTHk8Hp+4iLyU3vwLp0orLWXqrZQAAAAASUVORK5CYII="
// swiftlint:enable line_length
// Support for RTL languages like Arabic, Persia etc... (Bug ID: #448)
if let data: Data = Data(base64Encoded: base64Data, options: .ignoreUnknownCharacters),
let image = UIImage(data: data, scale: 3)?.imageFlippedForRightToLeftLayoutDirection() {
return image
}
return UIImage()
}()
}

View File

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "982A68D37F5DCBC1FC1FDC0BB2F0EB8E"
BuildableName = "IQKeyboardManagerSwift.bundle"
BlueprintName = "IQKeyboardManagerSwift-IQKeyboardManagerSwift"
ReferencedContainer = "container:Pods.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,24 +0,0 @@
<?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>CFBundleDevelopmentRegion</key>
<string>${PODS_DEVELOPMENT_LANGUAGE}</string>
<key>CFBundleIdentifier</key>
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>7.0.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "75E2881C2B9EB0700049AB56"
BuildableName = "wallpaper_project.app"
BlueprintName = "wallpaper_project"
ReferencedContainer = "container:wallpaper_project.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "75E288322B9EB0740049AB56"
BuildableName = "wallpaper_projectTests.xctest"
BlueprintName = "wallpaper_projectTests"
ReferencedContainer = "container:wallpaper_project.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "75E2883C2B9EB0740049AB56"
BuildableName = "wallpaper_projectUITests.xctest"
BlueprintName = "wallpaper_projectUITests"
ReferencedContainer = "container:wallpaper_project.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "75E2881C2B9EB0700049AB56"
BuildableName = "wallpaper_project.app"
BlueprintName = "wallpaper_project"
ReferencedContainer = "container:wallpaper_project.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "75E2881C2B9EB0700049AB56"
BuildableName = "wallpaper_project.app"
BlueprintName = "wallpaper_project"
ReferencedContainer = "container:wallpaper_project.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,30 @@
<?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>API_KEY</key>
<string>AIzaSyCSllhrjLODy6W-0IE9JEu50nFP4eUgwNA</string>
<key>GCM_SENDER_ID</key>
<string>157500622168</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.wallpapaer.hd.live.app</string>
<key>PROJECT_ID</key>
<string>wallpaper-home-546b3</string>
<key>STORAGE_BUCKET</key>
<string>wallpaper-home-546b3.appspot.com</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:157500622168:ios:84591532a0a06f1f40912d</string>
</dict>
</plist>