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