IQKeyboardManager 源代碼看看

IQKeyboardManager 三步走

你們都用 IQKeyboardManager,git

IQKeyboardManager 引入,就管理好了github

第 1 步,註冊系統通知,得到鍵盤事件

從鍵盤事件中,獲得輸入文本框對象, UITextField / UITextView 的實例ide

IQKeyboardManager 初始化的時候,就完成了這些ui

第 2 步,計算出當前文本框的位置, 並移動

有了文本框,要找到他當前的位置,framepwa

就要從文本框溯源,找到他的根視圖控制器code

而後計算出當前文本框在哪一個位置合適,orm

移動過去,就行了server

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

相關文章
相關標籤/搜索