杨锴
2025-03-11 90dc3329d1973fda691e357cf4523d5c7c67fa1d
Pods/IQKeyboardManagerSwift/IQKeyboardManagerSwift/IQKeyboardManager/IQKeyboardManager+Position.swift
@@ -22,10 +22,14 @@
// THE SOFTWARE.
import UIKit
import IQKeyboardCore
// swiftlint:disable file_length
@available(iOSApplicationExtension, unavailable)
public extension IQKeyboardManager {
@MainActor
@objc public extension IQKeyboardManager {
    private typealias IQLayoutGuide = (top: CGFloat, bottom: CGFloat)
    @MainActor
    private struct AssociatedKeys {
@@ -37,7 +41,7 @@
    }
    /**
     moved distance to the top used to maintain distance between keyboard and textField.
     moved distance to the top used to maintain distance between keyboard and textInputView.
     Most of the time this will be a positive value.
     */
    private(set) var movedDistance: CGFloat {
@@ -53,7 +57,7 @@
    /**
    Will be called then movedDistance will be changed
     */
    @objc var movedDistanceChanged: ((CGFloat) -> Void)? {
    var movedDistanceChanged: ((CGFloat) -> Void)? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.movedDistanceChanged) as? ((CGFloat) -> Void)
        }
@@ -65,6 +69,7 @@
    }
    /** Variable to save lastScrollView that was scrolled. */
    @nonobjc
    internal var lastScrollViewConfiguration: IQScrollViewConfiguration? {
        get {
            return objc_getAssociatedObject(self,
@@ -77,6 +82,7 @@
    }
    /** used to adjust contentInset of UITextView. */
    @nonobjc
    internal var startingTextViewConfiguration: IQScrollViewConfiguration? {
        get {
            return objc_getAssociatedObject(self,
@@ -88,23 +94,10 @@
        }
    }
    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) {
    internal func applicationDidBecomeActive(_ notification: Notification) {
        guard privateIsEnabled(),
              activeConfiguration.keyboardInfo.keyboardShowing,
              activeConfiguration.keyboardInfo.isVisible,
              activeConfiguration.isReady else {
            return
        }
@@ -112,117 +105,63 @@
    }
    /* 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 {
              let textInputView: any IQTextInputView = activeConfiguration.textInputView,
              let superview: UIView = textInputView.superview,
              let rootConfiguration = activeConfiguration.rootConfiguration,
              let rootController: UIViewController = rootConfiguration.rootController,
              let window: UIWindow = 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,
        defer {
            showLog("<<<<< \(#function) ended <<<<<", indentation: -1)
        }
        let textInputViewRectInWindow: CGRect = superview.convert(textInputView.frame, to: window)
        let textInputViewRectInRootSuperview: CGRect = superview.convert(textInputView.frame,
                                                                         to: rootController.view.superview)
        //  Getting RootViewOrigin.
        var rootViewOrigin: CGPoint = rootController.view.frame.origin
        let rootViewOrigin: CGPoint = rootController.view.frame.origin
        let keyboardDistance: CGFloat
        let keyboardDistance: CGFloat = getSpecialTextInputViewDistance(textInputView: textInputView)
        do {
            // Maintain keyboardDistanceFromTextField
            let specialKeyboardDistanceFromTextField: CGFloat
        let kbSize: CGSize = Self.getKeyboardSize(keyboardDistance: keyboardDistance,
                                                  keyboardFrame: activeConfiguration.keyboardInfo.endFrame,
                                                  safeAreaInsets: rootConfiguration.beginSafeAreaInsets,
                                                  windowFrame: window.frame)
        let originalKbSize: CGSize = activeConfiguration.keyboardInfo.endFrame.size
            if let searchBar: UIView = textFieldView.iq.textFieldSearchBar() {
                specialKeyboardDistanceFromTextField = searchBar.iq.distanceFromKeyboard
        let isScrollableTextInputView: Bool
        if let textInputView: UIScrollView = textInputView as? UITextView {
            isScrollableTextInputView = textInputView.isScrollEnabled
            } else {
                specialKeyboardDistanceFromTextField = textFieldView.iq.distanceFromKeyboard
            isScrollableTextInputView = false
            }
            if specialKeyboardDistanceFromTextField == UIView.defaultKeyboardDistance {
                keyboardDistance = keyboardDistanceFromTextField
            } else {
                keyboardDistance = specialKeyboardDistanceFromTextField
            }
        }
        let layoutGuide: IQLayoutGuide = Self.getLayoutGuides(rootController: rootController, window: window,
                                                              isScrollableTextInputView: isScrollableTextInputView)
        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 {
            statusBarHeight = window.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
            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.
        //  Move positive = textInputView is hidden.
        //  Move negative = textInputView 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))
        }
        var moveUp: CGFloat = Self.getMoveUpDistance(keyboardSize: kbSize,
                                                     layoutGuide: layoutGuide,
                                                     textInputViewRectInRootSuperview: textInputViewRectInRootSuperview,
                                                     textInputViewRectInWindow: textInputViewRectInWindow,
                                                     windowFrame: window.frame)
        showLog("Need to move: \(moveUp), will be moving \(moveUp < 0 ? "down" : "up")")
        var superScrollView: UIScrollView?
        var superView: UIScrollView? = textFieldView.iq.superviewOf(type: UIScrollView.self)
        var superView: UIScrollView? = (textInputView as UIView).iq.superviewOf(type: UIScrollView.self)
        // Getting UIScrollView whose scrolling is enabled.    //  (Bug ID: #285)
        while let view: UIScrollView = superView {
@@ -236,44 +175,189 @@
            }
        }
        // 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 {
        setupActiveScrollViewConfiguration(superScrollView: superScrollView, textInputView: textInputView)
                if lastConfiguration.hasChanged {
                    if lastConfiguration.scrollView.contentInset != lastConfiguration.startingContentInset {
                        showLog("Restoring contentInset to: \(lastConfiguration.startingContentInset)")
        //  Special case for ScrollView.
        //  If we found lastScrollView then setting it's contentOffset to show textInputView.
        if let lastScrollViewConfiguration: IQScrollViewConfiguration  = lastScrollViewConfiguration {
            adjustScrollViewContentOffsets(moveUp: &moveUp, textInputView: textInputView,
                                           lastScrollViewConfiguration: lastScrollViewConfiguration,
                                           rootSuperview: rootController.view.superview, layoutGuide: layoutGuide,
                                           textInputViewRectInRootSuperview: textInputViewRectInRootSuperview,
                                           isScrollableTextInputView: isScrollableTextInputView, window: window,
                                           kbSize: kbSize, keyboardDistance: keyboardDistance,
                                           rootBeginSafeAreaInsets: rootConfiguration.beginSafeAreaInsets)
                    }
                    if lastConfiguration.scrollView.iq.restoreContentOffset,
                       !lastConfiguration.scrollView.contentOffset.equalTo(lastConfiguration.startingContentOffset) {
                        showLog("Restoring contentOffset to: \(lastConfiguration.startingContentOffset)")
        // Special case for UITextView
        // (Readjusting textInputView.contentInset when textInputView hight is too big to fit on screen)
        // _lastScrollView If not having inside any scrollView, now contentInset manages the full screen textInputView.
        // If is a UITextView type
        if isScrollableTextInputView, let textInputView = textInputView as? UITextView {
            adjustTextInputViewContentInset(window: window, originalKbSize: originalKbSize,
                                            rootSuperview: rootController.view.superview,
                                            layoutGuide: layoutGuide,
                                            textInputView: textInputView)
        }
        adjustRootController(moveUp: moveUp, rootViewOrigin: rootViewOrigin, originalKbSize: originalKbSize,
                             rootController: rootController, rootBeginOrigin: rootConfiguration.beginOrigin)
    }
    // swiftlint:enable function_body_length
    internal func restorePosition() {
        //  Setting rootViewController frame to it's original position. //  (Bug ID: #18)
        guard let configuration: IQRootControllerConfiguration = activeConfiguration.rootConfiguration else {
            return
        }
        showLog(">>>>> \(#function) started >>>>>", indentation: 1)
        defer {
            showLog("<<<<< \(#function) ended <<<<<", indentation: -1)
                    }
                    activeConfiguration.animate(alongsideTransition: {
                        lastConfiguration.restore(for: textFieldView)
            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 lastConfiguration: IQScrollViewConfiguration = lastScrollViewConfiguration {
            let textInputView: (any IQTextInputView)? = activeConfiguration.textInputView
            restoreScrollViewConfigurationIfChanged(configuration: lastConfiguration, textInputView: textInputView)
            activeConfiguration.animate(alongsideTransition: {
                // This is temporary solution. Have to implement the save and restore scrollView state
                self.restoreScrollViewContentOffset(superScrollView: lastConfiguration.scrollView,
                                                    textInputView: textInputView)
                    })
                }
        self.movedDistance = 0
    }
}
// swiftlint:disable function_parameter_count
@available(iOSApplicationExtension, unavailable)
@MainActor
private extension IQKeyboardManager {
    func getSpecialTextInputViewDistance(textInputView: some IQTextInputView) -> CGFloat {
        // Maintain keyboardDistance
        let specialKeyboardDistance: CGFloat
        if let searchBar: UISearchBar = textInputView.iq.textFieldSearchBar() {
            specialKeyboardDistance = searchBar.iq.distanceFromKeyboard
        } else {
            specialKeyboardDistance = textInputView.iq.distanceFromKeyboard
        }
        if specialKeyboardDistance == UIView.defaultKeyboardDistance {
            return keyboardDistance
        } else {
            return specialKeyboardDistance
        }
    }
    static func getKeyboardSize(keyboardDistance: CGFloat, keyboardFrame: CGRect,
                                safeAreaInsets: UIEdgeInsets, windowFrame: CGRect) -> CGSize {
        let kbSize: CGSize
        var kbFrame: CGRect = keyboardFrame
        kbFrame.origin.y -= keyboardDistance
        kbFrame.size.height += keyboardDistance
        kbFrame.origin.y -= safeAreaInsets.bottom
        kbFrame.size.height += safeAreaInsets.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(windowFrame)
        if intersectRect.isNull {
            kbSize = CGSize(width: kbFrame.size.width, height: 0)
        } else {
            kbSize = intersectRect.size
        }
        return kbSize
    }
    static private func getLayoutGuides(rootController: UIViewController, window: UIWindow,
                                        isScrollableTextInputView: Bool) -> IQLayoutGuide {
        let navigationBarAreaHeight: CGFloat
        if let navigationController: UINavigationController = rootController.navigationController {
            navigationBarAreaHeight = navigationController.navigationBar.frame.maxY
        } else {
            let statusBarHeight: CGFloat = window.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
            navigationBarAreaHeight = statusBarHeight
        }
        let directionalLayoutMargin: NSDirectionalEdgeInsets = rootController.view.directionalLayoutMargins
        let topLayoutGuide: CGFloat = CGFloat.maximum(navigationBarAreaHeight, directionalLayoutMargin.top)
        // Validation of textInputView for case where there is a tab bar
        // at the bottom or running on iPhone X and textInputView is at the bottom.
        let bottomLayoutGuide: CGFloat = isScrollableTextInputView ? 0 : directionalLayoutMargin.bottom
        return (topLayoutGuide, bottomLayoutGuide)
    }
    static private func getMoveUpDistance(keyboardSize: CGSize,
                                          layoutGuide: IQLayoutGuide,
                                          textInputViewRectInRootSuperview: CGRect,
                                          textInputViewRectInWindow: CGRect,
                                          windowFrame: CGRect) -> CGFloat {
        //  Move positive = textInputView is hidden.
        //  Move negative = textInputView is showing.
        //  Calculating move position.
        let visibleHeight: CGFloat = windowFrame.height-keyboardSize.height
        let topMovement: CGFloat = textInputViewRectInRootSuperview.minY-layoutGuide.top
        let bottomMovement: CGFloat = textInputViewRectInWindow.maxY - visibleHeight + layoutGuide.bottom
        var moveUp: CGFloat = CGFloat.minimum(topMovement, bottomMovement)
        moveUp = CGFloat(Int(moveUp))
        return moveUp
    }
    func setupActiveScrollViewConfiguration(superScrollView: UIScrollView?, textInputView: some IQTextInputView) {
        // If there was a lastScrollView.    //  (Bug ID: #34)
        guard let lastConfiguration: IQScrollViewConfiguration = lastScrollViewConfiguration 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)
                        """)
            }
            return
        }
        // If we can't find current superScrollView, then setting lastScrollView to it's original form.
        if superScrollView == nil {
            restoreScrollViewConfigurationIfChanged(configuration: lastConfiguration,
                                                    textInputView: textInputView)
                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)
                    })
                }
            restoreScrollViewConfigurationIfChanged(configuration: lastConfiguration,
                                                    textInputView: textInputView)
                if let superScrollView = superScrollView {
                    let configuration = IQScrollViewConfiguration(scrollView: superScrollView,
@@ -288,23 +372,37 @@
                }
            }
            // 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)
                    """)
        // after switching to different textInputView. So doing nothing, going ahead
        }
        //  Special case for ScrollView.
        //  If we found lastScrollView then setting it's contentOffset to show textField.
        if let lastScrollViewConfiguration: IQScrollViewConfiguration  = lastScrollViewConfiguration {
    func restoreScrollViewConfigurationIfChanged(configuration: IQScrollViewConfiguration,
                                                 textInputView: (some IQTextInputView)?) {
        guard configuration.hasChanged else { return }
        if configuration.scrollView.contentInset != configuration.startingContentInset {
            showLog("Restoring contentInset to: \(configuration.startingContentInset)")
        }
        if configuration.scrollView.iq.restoreContentOffset,
           !configuration.scrollView.contentOffset.equalTo(configuration.startingContentOffset) {
            showLog("Restoring contentOffset to: \(configuration.startingContentOffset)")
        }
        activeConfiguration.animate(alongsideTransition: {
            configuration.restore(for: textInputView)
        })
    }
    // swiftlint:disable function_body_length
    private func adjustScrollViewContentOffsets(moveUp: inout CGFloat, textInputView: some IQTextInputView,
                                                lastScrollViewConfiguration: IQScrollViewConfiguration,
                                                rootSuperview: UIView?,
                                                layoutGuide: IQLayoutGuide,
                                                textInputViewRectInRootSuperview: CGRect,
                                                isScrollableTextInputView: Bool, window: UIWindow,
                                                kbSize: CGSize, keyboardDistance: CGFloat,
                                                rootBeginSafeAreaInsets: UIEdgeInsets) {
            // Saving
            var lastView: UIView = textFieldView
        var lastView: UIView = textInputView
            var superScrollView: UIScrollView? = lastScrollViewConfiguration.scrollView
            while let scrollView: UIScrollView = superScrollView {
@@ -319,49 +417,25 @@
                    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)
                        }
                    }
                Self.handleTableViewCase(moveUp: &moveUp, isContinue: isContinue, textInputView: textInputView,
                                         tableView: tableView, rootSuperview: rootSuperview, layoutGuide: layoutGuide)
                } 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)
                        }
                    }
                Self.handleCollectionViewCase(moveUp: &moveUp, isContinue: isContinue,
                                              textInputView: textInputView, collectionView: collectionView,
                                              rootSuperview: rootSuperview, layoutGuide: layoutGuide)
                } else {
                    isContinue = textFieldViewRectInRootSuperview.minY < topLayoutGuide
                isContinue = textInputViewRectInRootSuperview.minY < layoutGuide.top
                    if isContinue {
                        moveUp = CGFloat.minimum(0, textFieldViewRectInRootSuperview.minY - topLayoutGuide)
                    moveUp = CGFloat.minimum(0, textInputViewRectInRootSuperview.minY - layoutGuide.top)
                    }
                }
                // Looping in upper hierarchy until we don't found any scrollView then
            // Looping in upper hierarchy until we don't found any scrollView
                // in it's upper hierarchy till UIWindow object.
                if isContinue {
@@ -387,61 +461,124 @@
                        // 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)
                        }
                    updateSuggestedOffsetYAndMoveUp(suggestedOffsetY: &suggestedOffsetY, moveUp: &moveUp,
                                                    isScrollableTextInputView: isScrollableTextInputView,
                                                    nextScrollView: nextScrollView, textInputView: textInputView,
                                                    window: window, layoutGuide: layoutGuide,
                                                    scrollViewContentOffset: scrollView.contentOffset)
                        let newContentOffset: CGPoint = CGPoint(x: scrollView.contentOffset.x, y: suggestedOffsetY)
                        if !scrollView.contentOffset.equalTo(newContentOffset) {
                        updateScrollViewContentOffset(scrollView: scrollView, newContentOffset: newContentOffset,
                                                      moveUp: moveUp, textInputView: textInputView)
                    }
                }
                // Getting next lastView & superScrollView.
                lastView = scrollView
                superScrollView = nextScrollView
            } else {
                moveUp = 0
                break
            }
        }
        adjustScrollViewContentInset(lastScrollViewConfiguration: lastScrollViewConfiguration, window: window,
                                     kbSize: kbSize, keyboardDistance: keyboardDistance,
                                     rootBeginSafeAreaInsets: rootBeginSafeAreaInsets)
    }
    // swiftlint:enable function_body_length
    private static func handleTableViewCase(moveUp: inout CGFloat, isContinue: Bool,
                                            textInputView: some IQTextInputView, tableView: UITableView,
                                            rootSuperview: UIView?, layoutGuide: IQLayoutGuide) {
        guard isContinue,
              let tableCell: UITableViewCell = textInputView.iq.superviewOf(type: UITableViewCell.self),
              let indexPath: IndexPath = tableView.indexPath(for: tableCell),
              let previousIndexPath: IndexPath = tableView.previousIndexPath(of: indexPath) else { return }
        let previousCellRect: CGRect = tableView.rectForRow(at: previousIndexPath)
        guard !previousCellRect.isEmpty else { return }
        let previousCellRectInRootSuperview: CGRect = tableView.convert(previousCellRect,
                                                                        to: rootSuperview)
        moveUp = CGFloat.minimum(0, previousCellRectInRootSuperview.maxY - layoutGuide.top)
    }
    private static func handleCollectionViewCase(moveUp: inout CGFloat, isContinue: Bool,
                                                 textInputView: some IQTextInputView, collectionView: UICollectionView,
                                                 rootSuperview: UIView?,
                                                 layoutGuide: IQLayoutGuide) {
        guard isContinue,
              let collectionCell = textInputView.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) else { return }
        let previousCellRect: CGRect = attributes.frame
        guard !previousCellRect.isEmpty else { return }
        let previousCellRectInRootSuperview: CGRect = collectionView.convert(previousCellRect,
                                                                             to: rootSuperview)
        moveUp = CGFloat.minimum(0, previousCellRectInRootSuperview.maxY - layoutGuide.top)
    }
    private func updateSuggestedOffsetYAndMoveUp(suggestedOffsetY: inout CGFloat, moveUp: inout CGFloat,
                                                 isScrollableTextInputView: Bool, nextScrollView: UIScrollView?,
                                                 textInputView: some IQTextInputView, window: UIWindow,
                                                 layoutGuide: IQLayoutGuide,
                                                 scrollViewContentOffset: CGPoint) {
        // If is a UITextView type
        // nextScrollView == nil
        // If processing scrollView is last scrollView in upper hierarchy
        // (there is no other scrollView in upper hierarchy.)
        //
        // suggestedOffsetY >= 0
        // suggestedOffsetY must be >= 0 in order to keep distance from navigationBar (Bug ID: #92)
        guard isScrollableTextInputView,
              nextScrollView == nil,
              suggestedOffsetY >= 0,
              let superview: UIView = textInputView.superview else {
            // Subtracting the Y offset from the move variable,
            // because we are going to change scrollView's contentOffset.y to suggestedOffsetY.
            moveUp -= (suggestedOffsetY-scrollViewContentOffset.y)
            return
        }
        let currentTextInputViewRect: CGRect = superview.convert(textInputView.frame,
                                                                 to: window)
        // Calculating expected fix distance which needs to be managed from navigation bar
        let expectedFixDistance: CGFloat = currentTextInputViewRect.minY - layoutGuide.top
        // 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 = scrollViewContentOffset.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
    }
    func updateScrollViewContentOffset(scrollView: UIScrollView, newContentOffset: CGPoint,
                                       moveUp: CGFloat, textInputView: some IQTextInputView) {
                            showLog("""
                                    old contentOffset: \(scrollView.contentOffset)
                                    new contentOffset: \(newContentOffset)
                                    """)
                            self.showLog("Remaining Move: \(moveUp)")
        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,
            let stackView: UIStackView? = textInputView.iq.superviewOf(type: UIStackView.self,
                                                                                           belowView: scrollView)
                                // (Bug ID: #1901, #1996)
                                let animatedContentOffset: Bool = stackView != nil ||
@@ -457,28 +594,23 @@
                                if scrollView is UITableView || scrollView is UICollectionView {
                                    // This will update the next/previous states
                                    self.reloadInputViews()
                textInputView.reloadInputViews()
                                }
                            })
                        }
                    }
                    // Getting next lastView & superScrollView.
                    lastView = scrollView
                    superScrollView = nextScrollView
                } else {
                    moveUp = 0
                    break
                }
            }
    func adjustScrollViewContentInset(lastScrollViewConfiguration: IQScrollViewConfiguration,
                                      window: UIWindow, kbSize: CGSize, keyboardDistance: CGFloat,
                                      rootBeginSafeAreaInsets: UIEdgeInsets) {
        let lastScrollView = lastScrollViewConfiguration.scrollView
        guard let lastScrollViewRect: CGRect = lastScrollView.superview?.convert(lastScrollView.frame, to: window),
              !lastScrollView.iq.ignoreContentInsetAdjustment else { return }
            // 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
        let keyboardAndSafeArea: CGFloat = keyboardDistance + rootBeginSafeAreaInsets.bottom
                var bottomScrollIndicatorInset: CGFloat = bottomInset - keyboardAndSafeArea
                // Update the insets so that the scrollView doesn't shift incorrectly
@@ -494,8 +626,10 @@
                var movedInsets: UIEdgeInsets = lastScrollView.contentInset
                movedInsets.bottom = bottomInset
                if lastScrollView.contentInset != movedInsets {
                    showLog("old ContentInset: \(lastScrollView.contentInset) new ContentInset: \(movedInsets)")
        guard lastScrollView.contentInset != movedInsets else { return }
        showLog("""
                old ContentInset: \(lastScrollView.contentInset) new ContentInset: \(movedInsets)
                """)
                    activeConfiguration.animate(alongsideTransition: {
                        lastScrollView.contentInset = movedInsets
@@ -507,55 +641,51 @@
                        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 {
    private func adjustTextInputViewContentInset(window: UIWindow, originalKbSize: CGSize,
                                                 rootSuperview: UIView?,
                                                 layoutGuide: IQLayoutGuide,
                                                 textInputView: UIScrollView) {
            let keyboardYPosition: CGFloat = window.frame.height - originalKbSize.height
            var rootSuperViewFrameInWindow: CGRect = window.frame
            if let rootSuperview: UIView = rootController.view.superview {
        if let rootSuperview: UIView = rootSuperview {
                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)
        let availableHeight: CGFloat = rootSuperViewFrameInWindow.height-layoutGuide.top-keyboardOverlapping
        let textInputViewHeight: CGFloat = CGFloat.minimum(textInputView.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)
        guard textInputView.frame.size.height-textInputView.contentInset.bottom>textInputViewHeight else { return }
        // If frame is not change by library in past, then saving user textInputView properties  (Bug ID: #92)
                if startingTextViewConfiguration == nil {
                    startingTextViewConfiguration = IQScrollViewConfiguration(scrollView: textView,
            startingTextViewConfiguration = IQScrollViewConfiguration(scrollView: textInputView,
                                                                              canRestoreContentOffset: false)
                }
                var newContentInset: UIEdgeInsets = textView.contentInset
                newContentInset.bottom = textView.frame.size.height-textViewHeight
                newContentInset.bottom -= textView.safeAreaInsets.bottom
        var newContentInset: UIEdgeInsets = textInputView.contentInset
        newContentInset.bottom = textInputView.frame.size.height-textInputViewHeight
        newContentInset.bottom -= textInputView.safeAreaInsets.bottom
                if textView.contentInset != newContentInset {
                    self.showLog("""
                                \(textFieldView) Old UITextView.contentInset: \(textView.contentInset)
                                 New UITextView.contentInset: \(newContentInset)
        guard textInputView.contentInset != newContentInset else { return }
        showLog("""
                                \(textInputView) Old textInputView.contentInset: \(textInputView.contentInset)
                                 New textInputView.contentInset: \(newContentInset)
                                """)
                    activeConfiguration.animate(alongsideTransition: {
                        textView.contentInset = newContentInset
                        textView.layoutIfNeeded() // (Bug ID: #1996)
                        textView.scrollIndicatorInsets = newContentInset
            textInputView.contentInset = newContentInset
            textInputView.layoutIfNeeded() // (Bug ID: #1996)
            textInputView.scrollIndicatorInsets = newContentInset
                    })
                }
            }
        }
    func adjustRootController(moveUp: CGFloat, rootViewOrigin: CGPoint, originalKbSize: CGSize,
                              rootController: UIViewController, rootBeginOrigin: CGPoint) {
        // +Positive or zero.
        var rootViewOrigin: CGPoint = rootViewOrigin
        if moveUp >= 0 {
            rootViewOrigin.y = CGFloat.maximum(rootViewOrigin.y - moveUp, CGFloat.minimum(0, -originalKbSize.height))
@@ -581,9 +711,9 @@
                })
            }
            movedDistance = (rootConfiguration.beginOrigin.y-rootViewOrigin.y)
            movedDistance = rootBeginOrigin.y-rootViewOrigin.y
        } else {  //  -Negative
            let disturbDistance: CGFloat = rootViewOrigin.y-rootConfiguration.beginOrigin.y
            let disturbDistance: CGFloat = rootViewOrigin.y-rootBeginOrigin.y
            //  disturbDistance Negative = frame disturbed.
            //  disturbDistance positive = frame not disturbed.
@@ -614,64 +744,13 @@
                    })
                }
                movedDistance = (rootConfiguration.beginOrigin.y-rootViewOrigin.y)
                movedDistance = rootBeginOrigin.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 lastConfiguration: IQScrollViewConfiguration = lastScrollViewConfiguration {
            let textFieldView: UIView? = activeConfiguration.textFieldViewInfo?.textFieldView
            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
    func restoreScrollViewContentOffset(superScrollView: UIScrollView, textInputView: (some IQTextInputView)?) {
        var superScrollView: UIScrollView? = superScrollView
                while let scrollView: UIScrollView = superScrollView {
                    let width: CGFloat = CGFloat.maximum(scrollView.contentSize.width, scrollView.frame.width)
@@ -686,8 +765,13 @@
                        if !scrollView.contentOffset.equalTo(newContentOffset) {
                            //  (Bug ID: #1365, #1508, #1541)
                            let stackView: UIStackView? = textFieldView?.iq.superviewOf(type: UIStackView.self,
                    let stackView: UIStackView?
                    if let textInputView: UIView = textInputView {
                        stackView = textInputView.iq.superviewOf(type: UIStackView.self,
                                                                                        belowView: scrollView)
                    } else {
                        stackView = nil
                    }
                            // (Bug ID: #1901, #1996)
                            let animatedContentOffset: Bool = stackView != nil ||
@@ -700,19 +784,12 @@
                                scrollView.contentOffset = newContentOffset
                            }
                            self.showLog("Restoring contentOffset to: \(newContentOffset)")
                    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
}
// swiftlint:enable function_parameter_count