当前位置:网站首页>Iqkeyboardmanager source code to see

Iqkeyboardmanager source code to see

2020-11-08 09:41:00 black_pearl

IQKeyboardManager Three steps

We all use IQKeyboardManager,

IQKeyboardManager introduce , Just manage it

The first 1 Step , Registration system notification , Get keyboard events

From keyboard events , Get the input text box object , UITextField / UITextView Example

IQKeyboardManager When initializing , That's it

The first 2 Step , Calculate the position of the current text box , And move

With the text box , To find his current position ,frame

You have to go back to the source of the text box , Find his root view controller

Then figure out where the current text box is ,

Move past , Just fine

2.1 , Figure out the right position

Figure out first. , The location of the text box in the root view

To calculate the , The text box is in the current window , KeyWindow, The right place in

2.2, The keyboard appears , And the keyboard disappears

Start editing , The keyboard appears , Mobile location

End edit , The keyboard disappears , Restore location

3, Situation judgment

UIView Put a few UITextField / UITextView , Easy to handle

UIView Put... On top of UITableView, UITableView On the one cell, Put it on top of it UITextField / UITextView, It's a little more complicated

3.1 Special class processing ,

about UIAlertController The input box of , No need to deal with it

It's special , also UITableViewController、UISearchBar、

_UIAlertControllerTextFieldViewController

0, Keyboard management , It's simple

For an input box UITextField , Put in UIView On ,

The keyboard came out , This UITextField The location of , Be appropriate ,

Dispose of by two notices ,

In general , The keyboard comes out , hold UITextField Put it up a little bit ,

The keyboard disappears , hold UITextField Put the position back in place

    import SnapKit
    
    //  Registration notice 
    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)
    }
    
    
    //  Put it up a little bit 
    @objc
    func keyboardWillShow(noti notification: NSNotification){
        yConstraint?.constraint.update(offset: s.height * (-0.5))
        layoutIfNeeded()
    }
    
    
    
    //  Put it back where it was 
    @objc
    func keyboardWillHide(noti notification: NSNotification){
        yConstraint?.constraint.update(offset: 0)
        layoutIfNeeded()
    }

about UITextView, Do the same with

IQKeyboardManager The work done by the , It's complicated 、 It's a lot more comprehensive


1, Initialization work

register 4 A keyboard notification ,

The keyboard will appear , The keyboard appears ,

The keyboard is going to disappear , The keyboard is gone ,

Enter text box , There are two kinds of ,UITextField and UITextView

Register two more UITextField The notice of , Two UITextView The notice of

Finally register for a screen rotation notification

@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)
    }

We use automatic keyboards , Let's do this

IQKeyboardManager.shared.enable = true

Activate internal function use , State judgement


func privateIsEnabled() -> Bool {
        //  It's used here , The properties we set 
        var isEnabled = enable

        let enableMode = textFieldView?.enableMode

        if enableMode == .enabled {
            isEnabled = true
        } else if enableMode == .disabled {
            isEnabled = false
        } else if var textFieldViewController = textFieldView?.viewContainingController() {
            //  Walk here 
            //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
            }

            // ...
        }
        //  It works here 
        return isEnabled
    }


2, Calculation location , And modification

2.1, Click on a text box UITextField, The method of input box notification first

The input box starts editing

From the notice received , Get the object of the input box , Use associated properties , Save up


//  Registration notice 
@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
        
        //  Here are some , The task of removing repetitive states 
}

2.2, Go back to the way the keyboard will appear

 //  Notification of keyboard presence 
 @objc func registerAllNotifications() {

        //  Registering for keyboard notification.
        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(_:)), name: UIKeyboardWillShowNotification, object: nil)
        // ...
}


//  The keyboard appears , Adjusting position 
@objc internal func keyboardWillShow(_ notification: Notification?) {
     // ...
             if keyboardShowing,
                let textFieldView = textFieldView,
                textFieldView.isAlertViewTextField() == false {

                //  keyboard is already showing. adjust position.
                //   Adjusting position 
                optimizedAdjustPosition()
            }
     // ...
}

2.3, To adjust the position

Record by attribute ,hasPendingAdjustRequest, An adjustment must be done , Next one

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

First, coordinate transformation ,

Find the right distance to move ,

Judge the situation again ,

And finally move ,

Move is to change the coordinate origin of the parent view , And Ahead 0 , Keyboard management equally


private func adjustPosition() {

         //  Coordinate transformation 

        //  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
        }
        // ...
        //  Calculate the initial movement distance 
        var move: CGFloat = min(textFieldViewRectInRootSuperview.minY-(topLayoutGuide), textFieldViewRectInWindow.maxY-(window.frame.height-kbSize.height)+bottomLayoutGuide)
        //  Situation judgment 
        // ...
        //  Move up the part 
        
        //  +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
        
        //  And then there's the moving down part 
        
        }
        

2.4 Coordinate transformation , Find the location

The above code

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

I got an input box UITextField / UITextView,

The father gets the response first , The first view controller ,

Get the first view controller , Find the current root view controller ,

( Because it's possible that a controller's sub controller's view , Put a text box )


@objc public extension UIView {

    //  From the input box , To the controller 
    func viewContainingController() -> UIViewController? {

        var nextResponder: UIResponder? = self

        repeat {
            nextResponder = nextResponder?.next

            if let viewController = nextResponder as? UIViewController {
                return viewController
            }

        } while nextResponder != nil

        return nil
    }



    //  From the controller , To the current root view controller ,
    //  This method , No recursion is used , It's just a simple forward turn 
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

    }
    
    
}

Coordinate transformation , Find the location , ordinary convert Next

2.4, Go back to the keyboard and the way it appears

 @objc internal func keyboardDidShow(_ notification: Notification?) {
        //  Function activation control 
        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 application : ( The situation judgment part , A little )

In the controller , With a custom head view ,

The keyboard appears , The custom head view doesn't move , The rest moves as usual

3.1 solve

The controller used , Inherited from BaseC,

Keyboard related views , Add in contentView On



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 Originally used controller.view , Part of it is replaced by

controller.virtualView

See the right location , Continue to use

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

  • IQKeyboardManager The original location was changed controller.view.frame = ,

Switch to 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
        }
        
    }
    
}

Specific code , see github repo

版权声明
本文为[black_pearl]所创,转载请带上原文链接,感谢