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