IQKeyboardManager 三步走

大家都用 IQKeyboardManager,

IQKeyboardManager 引入,就治理好了

第 1 步,注册零碎告诉,取得键盘事件

从键盘事件中,失去输出文本框对象, UITextField / UITextView 的实例

IQKeyboardManager 初始化的时候,就实现了这些

第 2 步,计算出以后文本框的地位, 并挪动

有了文本框,要找到他以后的地位,frame

就要从文本框溯源,找到他的根视图控制器

而后计算出以后文本框在哪个地位适合,

挪动过来,就好了

2.1 , 计算出适合的地位

先算出,该文本框在根视图的地位

再算出,该文本框在以后窗口, KeyWindow, 中的适合地位

2.2,键盘呈现,与键盘隐没

开始编辑,键盘呈现,挪动地位

完结编辑,键盘隐没,还原地位

3,状况判断

UIView 上搁置几个 UITextField / UITextView ,好解决

UIView 上搁置 UITableView, UITableView 上的一个 cell,下面摆放 UITextField / UITextView,就简单了一些

3.1 非凡类解决,

对于 UIAlertController 的输入框,不必解决

比拟非凡的,还有 UITableViewController、UISearchBar、

_UIAlertControllerTextFieldViewController

0, 键盘治理,很简略

对于一个输入框 UITextField , 搁置在 UIView 上,

键盘进去了,这个 UITextField 的地位,要适当,

通过两个告诉解决掉,

个别状况下,键盘进去,把 UITextField 地位放高一点,

键盘隐没,把 UITextField 地位放回原处

    import SnapKit        // 注册告诉    func config(){        NotificationCenter.default.addObserver(self,                             selector: #selector(self.keyboardWillShow(noti:)),                             name: UIWindow.keyboardWillShowNotification,                             object: nil)        NotificationCenter.default.addObserver(self,                             selector: #selector(self.keyboardWillHide(noti:)),                             name: UIWindow.keyboardWillHideNotification,                             object: nil)    }            // 放高一点    @objc    func keyboardWillShow(noti notification: NSNotification){        yConstraint?.constraint.update(offset: s.height * (-0.5))        layoutIfNeeded()    }                // 放回原处    @objc    func keyboardWillHide(noti notification: NSNotification){        yConstraint?.constraint.update(offset: 0)        layoutIfNeeded()    }

对于 UITextView,也这样解决

IQKeyboardManager 做的工作,就简单、全面了很多


1, 初始化工作

注册 4 个键盘告诉,

键盘将要呈现,键盘呈现了,

键盘将要隐没,键盘隐没了,

输出文本框,有两种,UITextField 和 UITextView

再注册两个 UITextField 的告诉,两个 UITextView 的告诉

最初注册一个屏幕旋转的告诉

@objc func registerAllNotifications() {        //  Registering for keyboard notification.        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(_:)), name: UIKeyboardWillShowNotification, object: nil)        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidShow(_:)), name: UIKeyboardDidShowNotification, object: nil)        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(_:)), name: UIKeyboardWillHideNotification, object: nil)        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardDidHide(_:)), name: UIKeyboardDidHideNotification, object: nil)        //  Registering for UITextField notification.        registerTextFieldViewClass(UITextField.self, didBeginEditingNotificationName: UITextFieldTextDidBeginEditingNotification.rawValue, didEndEditingNotificationName: UITextFieldTextDidEndEditingNotification.rawValue)        //  Registering for UITextView notification.        registerTextFieldViewClass(UITextView.self, didBeginEditingNotificationName: UITextViewTextDidBeginEditingNotification.rawValue, didEndEditingNotificationName: UITextViewTextDidEndEditingNotification.rawValue)        //  Registering for orientation changes notification        NotificationCenter.default.addObserver(self, selector: #selector(self.willChangeStatusBarOrientation(_:)), name: UIApplicationWillChangeStatusBarOrientationNotification, object: UIApplication.shared)    }

咱们应用主动键盘,这样来一下

IQKeyboardManager.shared.enable = true

激活了外部的性能应用,状态判断

func privateIsEnabled() -> Bool {        // 这里用到了,咱们设置的属性        var isEnabled = enable        let enableMode = textFieldView?.enableMode        if enableMode == .enabled {            isEnabled = true        } else if enableMode == .disabled {            isEnabled = false        } else if var textFieldViewController = textFieldView?.viewContainingController() {            // 走这里            //If it is searchBar textField embedded in Navigation Bar            if textFieldView?.textFieldSearchBar() != nil, let navController = textFieldViewController as? UINavigationController, let topController = 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            }            // ...        }        // 这里起作用        return isEnabled    }


2, 计算地位,与批改

2.1, 点击一个文本框 UITextField,先走输入框告诉的办法

输入框开始编辑

从失去的告诉中,获得输入框的对象,应用关联的属性,保存起来

// 注册告诉@objc public func registerTextFieldViewClass(_ aClass: UIView.Type, didBeginEditingNotificationName: String, didEndEditingNotificationName: String) {        NotificationCenter.default.addObserver(self, selector: #selector(self.textFieldViewDidBeginEditing(_:)), name: Notification.Name(rawValue: didBeginEditingNotificationName), object: nil)        // ...    }            // @objc func textFieldViewDidBeginEditing(_ notification: Notification) {        let startTime = CACurrentMediaTime()        showLog("****** \(#function) started ******", indentation: 1)        //  Getting object        textFieldView = notification.object as? UIView                // 上面是一些,去除反复状态的工作}

2.2, 再走键盘将要呈现的办法

 // 键盘呈现的告诉 @objc func registerAllNotifications() {        //  Registering for keyboard notification.        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(_:)), name: UIKeyboardWillShowNotification, object: nil)        // ...}// 键盘呈现,调整地位@objc internal func keyboardWillShow(_ notification: Notification?) {     // ...             if keyboardShowing,                let textFieldView = textFieldView,                textFieldView.isAlertViewTextField() == false {                //  keyboard is already showing. adjust position.                //  调整地位                optimizedAdjustPosition()            }     // ...}

2.3, 去调整地位

通过属性记录,hasPendingAdjustRequest, 一次调整必须实现,再来下一个

internal func optimizedAdjustPosition() {        if !hasPendingAdjustRequest {            hasPendingAdjustRequest = true            OperationQueue.main.addOperation {                self.adjustPosition()                self.hasPendingAdjustRequest = false            }        }    }

先做坐标转换,

找出适合的挪动间隔,

再判断状况,

最初去挪动,

挪动就是把父视图的坐标原点改下,与 后面的 0 ,键盘治理 一样

private func adjustPosition() {         // 坐标转换        //  We are unable to get textField object while keyboard showing on WKWebView's textField.  (Bug ID: #11)        guard hasPendingAdjustRequest,            let textFieldView = textFieldView,            let rootController = textFieldView.parentContainerViewController(),            let window = keyWindow(),            let textFieldViewRectInWindow = textFieldView.superview?.convert(textFieldView.frame, to: window),            let textFieldViewRectInRootSuperview = textFieldView.superview?.convert(textFieldView.frame, to: rootController.view?.superview) else {                return        }        // ...        // 计算初步挪动间隔        var move: CGFloat = min(textFieldViewRectInRootSuperview.minY-(topLayoutGuide), textFieldViewRectInWindow.maxY-(window.frame.height-kbSize.height)+bottomLayoutGuide)        // 状况判断        // ...        // 上移局部                //  +Positive or zero.        if move >= 0 {            rootViewOrigin.y = max(rootViewOrigin.y - move, min(0, -(kbSize.height-newKeyboardDistanceFromTextField)))            if rootController.view.frame.origin.equalTo(rootViewOrigin) == false {                showLog("Moving Upward")                UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { () -> Void in                    var rect = 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()                    }                    self.showLog("Set \(rootController) origin to: \(rootViewOrigin)")                })            }            movedDistance = (topViewBeginOrigin.y-rootViewOrigin.y)        } else {  //  -Negative                // 还有下移局部                }        

2.4 坐标转换,找出地位

下面的代码

guard hasPendingAdjustRequest,            let textFieldView = textFieldView,            let rootController = textFieldView.parentContainerViewController(),            let textFieldViewRectInRootSuperview = textFieldView.superview?.convert(textFieldView.frame, to: rootController.view?.superview) else {                return        }

拿到了一个输入框 UITextField / UITextView,

先获得其父响应者中,第一个视图控制器,

拿到第一个视图控制器, 找到以后根视图控制器,

( 因为可能一个控制器的子控制器的视图上,放了一个文本框 )

@objc public extension UIView {    // 从输入框,到控制器    func viewContainingController() -> UIViewController? {        var nextResponder: UIResponder? = self        repeat {            nextResponder = nextResponder?.next            if let viewController = nextResponder as? UIViewController {                return viewController            }        } while nextResponder != nil        return nil    }    // 从控制器,到以后根视图控制器,    // 这个办法,没有应用递归,只是简略的往前翻func parentContainerViewController() -> UIViewController? {        var matchController = viewContainingController()        var parentContainerViewController: UIViewController?        if var navController = matchController?.navigationController {            while let parentNav = navController.navigationController {                navController = parentNav            }            var parentController: UIViewController = navController            while let parent = parentController.parent,                (parent.isKind(of: UINavigationController.self) == false &&                    parent.isKind(of: UITabBarController.self) == false &&                    parent.isKind(of: UISplitViewController.self) == false) {                        parentController = parent            }            if navController == parentController {                parentContainerViewController = navController.topViewController            } else {                parentContainerViewController = parentController            }        } else if let tabController = matchController?.tabBarController {            if let navController = tabController.selectedViewController as? UINavigationController {                parentContainerViewController = navController.topViewController            } else {                parentContainerViewController = tabController.selectedViewController            }        } else {            while let parentController = matchController?.parent,                (parentController.isKind(of: UINavigationController.self) == false &&                    parentController.isKind(of: UITabBarController.self) == false &&                    parentController.isKind(of: UISplitViewController.self) == false) {                        matchController = parentController            }            parentContainerViewController = matchController        }        let finalController = parentContainerViewController?.parentIQContainerViewController() ?? parentContainerViewController        return finalController    }        }

坐标转换,找出地位,简略的 convert

2.4, 再走键盘呈现了的办法

 @objc internal func keyboardDidShow(_ notification: Notification?) {        // 性能激活管制        if privateIsEnabled() == false {            return        }                let startTime = CACurrentMediaTime()        showLog("****** \(#function) started ******", indentation: 1)                if let textFieldView = _textFieldView,            let parentController = textFieldView.parentContainerViewController(), (parentController.modalPresentationStyle == UIModalPresentationStyle.formSheet || parentController.modalPresentationStyle == UIModalPresentationStyle.pageSheet){                        self.optimizedAdjustPosition()        }        // ...    }

3 利用: ( 状况判断局部,略)

控制器外面,采纳了自定制的头部视图,

键盘呈现,自定制的头部视图不动,其余局部照常动

3.1 解决

用到的控制器,继承自 BaseC

键盘相干的视图,增加在 contentView

class BaseC: UIViewController {            var contentView :UIView = {        let w = UIView()        w.backgroundColor = .clear        return w    }()            var firstLoad = true            var contentFrame: CGRect{        get{            contentView.frame        }        set{            contentView.frame = newValue        }    }                override func viewDidLoad() {        super.viewDidLoad()                view.addSubview(contentView)            }         override func viewDidLayoutSubviews() {        super.viewDidLayoutSubviews()        if firstLoad{            contentView.frame = view.frame            firstLoad = false        }            }}
  • IQKeyboardManager 原来应用的 controller.view ,一部分换成

controller.virtualView

查看适宜的地位,沿用

let textFieldViewRectInRootSuperview = textFieldView.superview?.convert(textFieldView.frame, to: rootController.view.superview)

  • IQKeyboardManager 原来批改地位的 controller.view.frame =

换成 controller.containerFrame

extension UIViewController{            var containerFrame: CGRect{        get{            if let me = self as? BaseC{                return me.contentFrame            }            else{                return view.frame            }                    }        set{            if let me = self as? BaseC{                me.contentFrame = newValue            }            else{                view.frame = newValue            }        }                    }                var virtualView: UIView{        if let v = self as? BaseC{            return v.contentView        }        else{            return view        }            }    }

具体代码,见 github repo