no message
This commit is contained in:
parent
803519a909
commit
35831caa2d
0
.gitignore
vendored
Normal file
0
.gitignore
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
@ -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 { } }
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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?()
|
||||
})
|
||||
// }
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
@ -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 }
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}()
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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>
|
||||
Loading…
Reference in New Issue
Block a user