KeyboardAssistant
版本 1.3.0
Keyboard Assistant 当设备键盘存在时方便视图的重定位。通过观测键盘通知(willShow, didShow, willHide, didHide)和在 UITextField 和 UITextView 对象开始编辑时对其进行响应来实现。
注意:在继续阅读之前,请注意我在这份文件中多次使用了“输入”和“输入项”这两个词。我指的是 InputNavigator 类中的输入项,其类型为 UITextField 和 UITextView。
要求
- iOS 9.0+
- Swift 5.0
Cocoapods
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'
use_frameworks!
target '<Your Target Name>' do
pod 'KeyboardAssistant', '1.3.0'
end
类概述
键盘助手被分解为3个核心类:KeyboardNotificationObserver、InputNavigator和KeyboardAssistant。KeyboardAssistant类是您将与之交互的主要类,位于KeyboardNotificationObserver和InputNavigator之上。在开始使用KeyboardAssistant之前,您需要创建一个InputNavigator并将其注入到KeyboardAssistant中。InputNavigator处理输入元素之间的导航,并负责通知KeyboardAssistant关于焦点变化(当一个UITextField或UITextView开始编辑时)。KeyboardNotificationObserver类负责检查键盘状态变化(willShow、didShow、willHide、didHide、didChangeFrame)并向KeyboardAssistant报告变化。如果您愿意,可以单独使用InputNavigator和KeyboardNotificationObserver。然而,这不是本模块的意图。要重新定位设备键盘上方的输入项,请使用KeyboardAssistant。要了解更多关于KeyboardAssistant的信息,请从下面的如何使用KeyboardAssistant部分开始。
如何使用KeyboardAssistant
使用键盘助手的3种主要方式。
自动滚动视图中助手
自动滚动视图中助手会自动为您定位键盘上方的输入项。设置它需要非常少的代码,但是您需要将您的viewcontroller类结构为使用滚动视图。
在继续之前,请阅读更多关于如何结构您的滚动视图的信息。
在本示例中,我们将
开始创建自动滚动视图中助手的步骤。首先在控制器类中声明一个KeyboardAssistant变量。
import UIKit
class YourViewController: UIViewController {
private var keyboardAssistant: KeyboardAssistant!
}
接下来,确保您的控制器结构为使用滚动视图进行定位。您需要一个为定位而定位的滚动视图的引用,以及键盘助手将放置在设备键盘顶部的滚动视图的底部约束。
import UIKit
class YourViewController: UIViewController {
private var keyboardAssistant: KeyboardAssistant!
@IBOutlet weak private var scrollView: UIScrollView!
@IBOutlet weak private var scrollViewBottomConstraint: NSLayoutConstraint!
}
viewDidLoad()方法是您完成KeyboardAssistant设置的步骤。首先创建一个用于使用的InputNavigator并添加一些用于导航的输入。
import UIKit
class YourViewController: UIViewController {
private var keyboardAssistant: KeyboardAssistant!
@IBOutlet weak private var scrollView: UIScrollView!
@IBOutlet weak private var contentView: UIView!
@IBOutlet weak private var lbTitle: UILabel!
@IBOutlet weak private var txtFirstName: UITextField!
@IBOutlet weak private var txtLastName: UITextField!
@IBOutlet weak private var txtEmail: UITextField!
@IBOutlet weak private var txtPassword: UITextField!
@IBOutlet weak private var btRegisterAccount: UIButton!
@IBOutlet weak private var scrollViewBottomConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
let navigator: InputNavigator = InputNavigator.createWithDefaultController()
navigator.addInputItems(inputItems: [self.txtFirstName,
self.txtLastName,
self.txtEmail,
self.txtPassword])
}
}
然后使用新创建的InputNavigator创建KeyboardAssistant。自动滚动视图中助手有6个参数。
- inputNavigator:定义使用的导航和要导航和重新定位的输入项。
- positionScrollView:将被重新定位的滚动视图。
- 位置约束:存在两种位置约束方式。(viewTopToTopOfScreen 和 viewBottomToTopOfKeyboard),用于定义如何定位输入项目。可以是将视图的顶部放置在屏幕顶部,或视图的底部放置在键盘顶部。
- 位置偏移:这是一个在通过位置约束定位视图后应用的偏移量。因此,如果您使用 position constraint viewTopToTopOfScreen,则视图的顶部将被定位到设备屏幕的顶部,然后应用位置偏移。
- 底部约束:这是 scrollview 的底部约束。它被设置为键盘顶部。这允许用户滚动通过所有子视图而不会受到键盘的干扰。
- 底部约束布局视图:这是更改底部约束位置所必需的。在大多数情况下,这将是控制器视图属性,因为它是 UIScrollView 底部约束的父视图。
import UIKit
class YourViewController: UIViewController {
private var keyboardAssistant: KeyboardAssistant!
@IBOutlet weak private var scrollView: UIScrollView!
@IBOutlet weak private var contentView: UIView!
@IBOutlet weak private var lbTitle: UILabel!
@IBOutlet weak private var txtFirstName: UITextField!
@IBOutlet weak private var txtLastName: UITextField!
@IBOutlet weak private var txtEmail: UITextField!
@IBOutlet weak private var txtPassword: UITextField!
@IBOutlet weak private var btRegisterAccount: UIButton!
@IBOutlet weak private var scrollViewBottomConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
let navigator: InputNavigator = InputNavigator.createWithDefaultController()
navigator.addInputItems(inputItems: [
self.txtFirstName,
self.txtLastName,
self.txtEmail,
self.txtPassword])
self.keyboardAssistant = KeyboardAssistant.createAutoScrollView(
inputNavigator: navigator,
positionScrollView: self.scrollView,
positionConstraint: .viewBottomToTopOfKeyboard,
positionOffset: 30,
bottomConstraint: self.scrollViewBottomConstraint,
bottomConstraintLayoutView: self.view)
}
}
最后,您需要管理注册通知的生命周期。这通过 KeyboardAssistant 的开始和停止方法来完成。
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.keyboardAssistant.start()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillAppear(animated)
self.keyboardAssistant.stop()
}
这就全部了。当其中一个输入项处于活动状态时,它会自动为您定位。
手动滑动视图辅助工具
手动辅助工具可用于手动定位滑动视图。
请看下面的截图。这是我们想要达到的效果。当一个输入项被聚焦时,我们想要定位到下一个输入项。如果我们位于最后一个输入项上,则定位到注册按钮。
在下面的代码中,我们将
- 创建一个包含键盘导航和默认控制器导航的 InputNavigator。
- 添加可供导航的输入项。
- 创建一个手动辅助工具。
- 管理键盘辅助工具通知,开始和停止。
import UIKit
class YourViewController: UIViewController {
private var keyboardAssistant: KeyboardAssistant!
@IBOutlet weak private var scrollView: UIScrollView!
@IBOutlet weak private var contentView: UIView!
@IBOutlet weak private var lbTitle: UILabel!
@IBOutlet weak private var txtFirstName: UITextField!
@IBOutlet weak private var txtLastName: UITextField!
@IBOutlet weak private var txtEmail: UITextField!
@IBOutlet weak private var txtPassword: UITextField!
@IBOutlet weak private var btRegisterAccount: UIButton!
@IBOutlet weak private var scrollViewBottomConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
let navigator: InputNavigator = InputNavigator.createWithKeyboardNavigationAndDefaultController(shouldSetTextFieldDelegates: true)
navigator.addInputItems(inputItems: [
self.txtFirstName,
self.txtLastName,
self.txtEmail,
self.txtPassword])
self.keyboardAssistant = KeyboardAssistant.createManual(
inputNavigator: navigator,
delegate: self,
bottomConstraint: self.scrollViewBottomConstraint,
bottomConstraintLayoutView: self.view)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.keyboardAssistant.start()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillAppear(animated)
self.keyboardAssistant.stop()
}
}
我们使用 KeyboardAssistantDelegate: keyboardAssistantManuallyReposition() 执行所有手动定位。如果有 nextInputItem,则定位到它。如果没有,那么我们位于末尾,所以定位到注册按钮。
我们将 shouldLoop: false 传递给 getNextInputItem。如果传递 true,则在位于最后一个输入项时返回第一个输入项。
// MARK: - KeyboardAssistantDelegate
extension YourViewController: KeyboardAssistantDelegate {
func keyboardAssistantManuallyReposition(keyboardAssistant: KeyboardAssistant, toInputItem: UIView, keyboardHeight: Double) {
let constraint: KeyboardAssistant.RepositionConstraint = .viewBottomToTopOfKeyboard
let offset: CGFloat = 20
if let nextInputItem = keyboardAssistant.navigator.getNextInputItem(inputItem: toInputItem, shouldLoop: false) {
keyboardAssistant.reposition(scrollView: self.scrollView, toInputItem: nextInputItem, constraint: constraint, offset: offset)
}
else {
keyboardAssistant.reposition(scrollView: self.scrollView, toInputItem: self.btRegisterAccount, constraint: constraint, offset: offset)
}
}
}
这达到了相同的效果,但是是一种硬编码的方法。
// MARK: - KeyboardAssistantDelegate
extension YourViewController: KeyboardAssistantDelegate {
func keyboardAssistantManuallyReposition(keyboardAssistant: KeyboardAssistant, toInputItem: UIView, keyboardHeight: Double) {
let constraint: KeyboardAssistant.RepositionConstraint = .viewBottomToTopOfKeyboard
let offset: CGFloat = 20
if toInputItem == self.txtFirstName {
keyboardAssistant.reposition(scrollView: self.scrollView, toInputItem: self.txtLastName, constraint: constraint, offset: offset)
}
else if toInputItem == self.txtLastName {
keyboardAssistant.reposition(scrollView: self.scrollView, toInputItem: self.txtEmail, constraint: constraint, offset: offset)
}
else if toInputItem == self.txtEmail {
keyboardAssistant.reposition(scrollView: self.scrollView, toInputItem: self.txtPassword, constraint: constraint, offset: offset)
}
else if toInputItem == self.txtPassword {
keyboardAssistant.reposition(scrollView: self.scrollView, toInputItem: self.btRegisterAccount, constraint: constraint, offset: offset)
}
}
}
手动辅助工具
当您想要进行自己的定位时,创建手动辅助工具。
在下面的代码中,我们将
- 创建一个包含键盘导航和默认控制器导航的 InputNavigator。
- 添加可供导航的输入项。
- 创建一个手动辅助工具。
- 管理键盘辅助工具通知,开始和停止。
- 添加 KeyboardAssistantDelegate: keyboardAssistantManuallyReposition() 来执行手动定位。
import UIKit
class YourViewController: UIViewController {
private var keyboardAssistant: KeyboardAssistant!
@IBOutlet weak private var txtFirstName: UITextField!
@IBOutlet weak private var txtLastName: UITextField!
@IBOutlet weak private var txtEmail: UITextField!
@IBOutlet weak private var txtPassword: UITextField!
@IBOutlet weak private var btRegisterAccount: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
let navigator: InputNavigator = InputNavigator.createWithKeyboardNavigationAndDefaultController(shouldSetTextFieldDelegates: true)
navigator.addInputItems(inputItems: [
self.txtFirstName,
self.txtLastName,
self.txtEmail,
self.txtPassword])
self.keyboardAssistant = KeyboardAssistant.createManual(inputNavigator: navigator, delegate: self)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.keyboardAssistant.start()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillAppear(animated)
self.keyboardAssistant.stop()
}
}
// MARK: - KeyboardAssistantDelegate
extension YourViewController: KeyboardAssistantDelegate {
func keyboardAssistantManuallyReposition(keyboardAssistant: KeyboardAssistant, toInputItem: UIView, keyboardHeight: Double) {
// Do your manual positioning here.
}
}
如何使用输入导航器
InputNavigator 有它自己的部分,因为这个类实际上有很多可以配置的方式,所以配置方式有很多。
在深入研究代码之前,最好先简要概述一下这个类的职责。这个类的主要目的是处理和提供输入项(UITextField / UITextView)之间的导航。InputNavigator 非常灵活,您可以自行选择使用内置导航选项或提供自己的选项。有两个内置选项,键盘返回键和DefaultNavigationView。这两个选项可以同时使用、分别使用或者都不使用。您可以提供自己的自定义视图用于导航,并将其附加到输入项的inputAccessoryView上,甚至可以和键盘返回键一起使用。您有大量的选项可供选择。
让我们从内置选项开始,并在此基础上进行展开。
使用默认控制器创建
我们从默认控制器开始。DefaultNavigationView 是 KeyboardAssistant 模块附带的一个自定义视图类,有一个自己的 .xib 文件用于创建 UI。它有 3 个主要按钮:btPrev、btNext 和 btDone。prev 和 next 按钮用于导航输入项,done 按钮将关闭键盘,通过解除活动输入项。要使用默认控制器创建导航器,请使用以下示例中的静态方法。
override func viewDidLoad() {
super.viewDidLoad()
let navigator: InputNavigator = InputNavigator.createWithDefaultController()
}
编辑默认控制器是很容易的。
override func viewDidLoad() {
super.viewDidLoad()
let navigator: InputNavigator = InputNavigator.createWithDefaultController()
// change all button colors
navigator.defaultController?.setButtonColors(color: .red)
// you can also configure the default controller in anyway you like.
if let defaultController = navigator.defaultController {
// remove the top shadow or change the top shadow in anyway you want
defaultController.layer.shadowOpacity = 0
// edit individual buttons
defaultController.btPrev.backgroundColor = .lightGray
defaultController.btNext.backgroundColor = .lightGray
defaultController.btDone.backgroundColor = .lightGray
defaultController.setBtPrevColor(color: .white)
defaultController.setBtNextColor(color: .white)
defaultController.setBtDoneColor(color: .black)
}
}
使用键盘导航创建
下一个内置导航选项是键盘导航,该导航使用键盘的返回键。在编辑 UITextField 时,returnKeyType 会被设置为 next 或 done,这取决于 UITextField 在 inputItems 列表中的索引位置。如果被编辑的 UITextField 在列表的末尾,它的 returnKeyType 被设置为 done,否则设置为 next。当点击 next 时,InputNavigator 会移动到下一个 inputItem;当点击 done 时,当前 inputItem 将解除活动状态,并隐藏键盘。
注意:不会在 UITextView 对象上设置 returnKeyType。这是故意的,因为返回键可用于向.TextView 添加新行。如果您需要从.TextView 导航,请考虑使用默认控制器或您自己的自定义导航。
重要!在创建具有键盘导航的InputNavigator时,存在一个布尔标志shouldSetTextFieldDelegates
。如果传递true,InputNavigator将设置UITextField的代理属性以响应textFieldShouldReturn代理方法。如果您需要在控制器中使用UITextFieldDelegate,请在此处传递false,并确保调用InputNavigator的textFieldShouldReturn方法来传递导航。您可以在下面的代码示例中看到这个示例。
这是如何创建具有键盘导航的InputNavigator的方式。在这里传递true,将设置所有UITextField的代理为InputNavigator。
override func viewDidLoad() {
super.viewDidLoad()
let navigator: InputNavigator = InputNavigator.createWithKeyboardNavigation(shouldSetTextFieldDelegates: true)
}
如果您的控制器类需要使用UITextFieldDelegate,则将标志设置为false,并确保在InputNavigator上调用textFieldShouldReturn。
class YourViewController: UIViewController {
private var keyboardAssistant: KeyboardAssistant!
override func viewDidLoad() {
super.viewDidLoad()
let navigator: InputNavigator = InputNavigator.createWithKeyboardNavigation(shouldSetTextFieldDelegates: false)
}
}
// MARK: - UITextFieldDelegate
extension YourViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
_ = self.keyboardAssistant.navigator.textFieldShouldReturn(textField)
return true
}
}
使用自定义控制器创建
自定义控制器用于创建自定义导航视图。它将替换默认控制器。要创建自定义控制器,创建自己的UIView类并实现InputNavigatorAccessoryController协议。按钮Delegate属性用于InputNavigator响应用户操作btPrev、btNext和btDone。您必须在自定义视图类中设置这些操作并确保调用代理方法。
在您的自定义视图类中实现InputNavigatorAccessoryController协议。
import UIKit
public class YourCustomController: UIView, InputNavigatorAccessoryController {
public var controllerView: UIView { return self }
@IBOutlet public weak var btPrev: UIButton!
@IBOutlet public weak var btNext: UIButton!
@IBOutlet public weak var btDone: UIButton!
public weak var buttonDelegate: InputNavigatorAccessoryControllerDelegate?
}
为btPrev、btNext和btDone添加操作,并调用适当的buttonDelegate方法来通知InputNavigator。
import UIKit
public class YourCustomController: UIView, InputNavigatorAccessoryController {
// MARK: - Actions
@IBAction func handlePrev(button: UIButton) {
if let buttonDelegate = self.buttonDelegate {
buttonDelegate.inputNavigatorAccessoryControllerPreviousButtonTapped(accessoryController: self)
}
}
@IBAction func handleNext(button: UIButton) {
if let buttonDelegate = self.buttonDelegate {
buttonDelegate.inputNavigatorAccessoryControllerNextButtonTapped(accessoryController: self)
}
}
@IBAction func handleDone(button: UIButton) {
if let buttonDelegate = self.buttonDelegate {
buttonDelegate.inputNavigatorAccessoryControllerDoneButtonTapped(accessoryController: self)
}
}
}
使用自定义附加视图创建
let navigator: InputNavigator = InputNavigator.createWithCustomAccessoryView(accessoryView: yourAccessoryView)
使用键盘导航和默认控制器创建
let navigator: InputNavigator = InputNavigator.createWithKeyboardNavigationAndDefaultController(shouldSetTextFieldDelegates: true)
使用键盘导航和自定义控制器创建
let navigator: InputNavigator = InputNavigator.createWithKeyboardNavigation(shouldSetTextFieldDelegates: true, andController: yourCustomController)
使用键盘导航和自定义配件视图创建
let navigator: InputNavigator = InputNavigator.createWithKeyboardNavigation(shouldSetTextFieldDelegates: true, andCustomAccessoryView: yourAccessoryView)
添加输入项
将输入项添加到 InputNavigator 有多种方式。
显式添加输入项
显式添加输入项时,InputNavigator 将按照添加的顺序导航这些项。
要显式添加输入项,请在您的 InputNavigator 实例上调用以下方法
addInputItem(inputItem: UIView)
addInputItems(inputItems: [UIView])
从视图控制器添加输入项
从视图控制器引用添加输入项时,InputNavigator 将递归遍历视图控制器视图层次结构,并将所有 UITextField 和.TextView 对象添加到 inputItems 数组中。然后,根据每个输入项的起点(x,y)对 inputItems 数组进行排序,以便按从上到下的顺序进行导航。
此方法需要两个参数:ViewController 和 InputItemType。InputItemType 是一个枚举,用于过滤要添加哪些输入项。
- InputItem.textField:仅添加在视图控制器中找到的文本字段。
- InputItem.textView:仅添加在视图控制器中找到的文本视图。
- InputItem.bothTextFieldAndTextView:添加在视图控制器中找到的文本字段和文本视图。
请在您的 InputNavigator 实例上调用以下方法
addInputItems(from: UIViewController, itemType: InputItemType)
组织您的滚视图
对于键盘定位,我更喜欢使用滚视图方法。这里有几个主要原因。
- 这使得它更加用户友好,因为在键盘打开的情况下,用户可以滚动浏览输入内容。
- 管理起来比TableView要简单得多。TableView虽然很棒,但在收集用户输入时管理起来可能会变得麻烦。这是因为在你滚动TableView时,单元格会被回收。这增加了收集输入和导航输入的额外管理。
- 结果是我通常需要在大多数ViewController中使用ScrollView来处理较小的设备大小。
在开始构建ScrollView之前,请确保在Interface Builder中勾选了“使用安全区域布局指南”选项。检查方法如下:选择您的.storyboard文件并选择文档选项卡。
以下是您需要构建的ViewController视图层级结构。UIView [root / UIViewController.view] > UIScrollView [scrollView] > UIView [contentView]。
UIScrollView应该设置所有边缘约束到安全区域。UIView [contentView]应该设置所有边缘约束到UIScrollView,并设置与UIScrollView相同的宽度。
就是这样。然后,您所有的自定义UI都放在UIView [contentView]中。
注意:此设置使用自动布局来确定ScrollView的内容大小。这意味着,UIView [contentView]内部的子视图都需要提供顶部和底部约束,以便满足contentView的高度。它还需要为您的一些子视图设置高度约束。除非它们的高度由它们的子视图决定。如果您不熟悉这个概念,请阅读更多关于自动布局的信息。
下方的截图是这个结构的示例。右边的约束显示了如何设置UIScrollView和UIView [contentView]的约束。
最后,请确保将ScrollView的底部约束连接到输出。这个约束位于键盘顶部,允许整个视图滚动而不受键盘的干扰。
示例
请确保检查KeyboardAssistant-Example项目。目前有3个示例,分别使用长ScrollView、嵌套子视图的长ScrollView和模拟注册屏幕的短ScrollView。每个示例都遵循FilteredKeyboardAssistant在运行时配置KeyboardAssistant,这可以让测试更加容易。如果您有任何希望添加的示例,请告诉我。
路线图
KeyboardAssistant目前处于早期发行阶段。我非常希望得到大家的意见和反馈。我还在考虑增加更多定位选项,例如直观的UIView定位和对UITableView的支持。