uitextfield键盘_如何在UITextField焦点上管理键盘以获得更好的用户体验

uitextfield键盘

by Roland Leth

罗兰·莱斯(Roland Leth)

如何在UITextField焦点上管理键盘以获得更好的用户体验 (How to manage the keyboard on UITextField focus for a better user experience)

A couple of posts ago I was writing about handling the Next button automatically. In this post I’d like to write about avoiding the keyboard automatically, in a manner that provides both a good user experience and a good developer experience.

几篇文章之前,我在写有关自动处理“下一步”按钮的文章。 在这篇文章中,我想写一篇关于自动避开键盘的方法,这种方式既要提供良好的用户体验,又要提供良好的开发人员体验。

Most apps have some sort of form that needs to be filled, even if just a login/register, if not several forms. As a user, having the keyboard cover the text field I’m about to fill makes me sad — it’s a poor user experience. As developers, we’d like to solve this as easily as possible and have the solution be as reusable as possible.

大多数应用程序都有某种形式的表单,即使不是一个登录/注册,也需要填写。 作为用户,让键盘覆盖我要填写的文本字段会让我感到难过-这是糟糕的用户体验。 作为开发人员,我们希望尽可能轻松地解决此问题,并使解决方案尽可能重用。

What does a good user experience mean?

良好的用户体验是什么意思?

  • The focused UITextField is brought above the keyboard on focus.

    重点突出的UITextField置于焦点上方的键盘上方。

  • The focused UITextField is “sent back” on dismiss.

    聚焦后的UITextField在关闭时会“发送回”。

What does a good developer experience mean? Everything should happen as automatically as possible, so we’ll go with a protocol once again. What does this protocol need to encapsulate?

良好的开发人员经验意味着什么? 一切都应尽可能自动发生,因此我们将再次使用协议。 该协议需要封装什么?

  • Observing the keyboard will show/hide notifications.

    观察键盘将显示/隐藏通知。
  • On keyboard appearance, it needs to modify the scrollView.contentInset and scrollView.contentOffset in a way that brings the UITextField right above the keyboard.

    在键盘外观上,它需要以将UITextField置于键盘上方的方式来修改scrollView.contentInsetscrollView.contentOffset

  • On keyboard disappearance, it needs to reset the inset and offset to previous values.

    键盘消失时,需要将插图和偏移量重置为以前的值。

With this in mind, let’s build our protocol:

考虑到这一点,让我们构建协议:

protocol KeyboardListener: AnyObject { // 1
var scrollView: UIScrollView { get } // 2   var contentOffsetPreKeyboardDisplay: CGPoint? { get set } // 3   var contentInsetPreKeyboardDisplay: UIEdgeInsets? { get set } // 4
func keyboardChanged(with notification: Notification) // 5
}

We need to constrain this protocol to be conformed to only by classes (1) because we’ll need to modify the two preKeyboard properties (3, 4). We’ll use them to know how to revert the scrollView’s inset and offset on keyboard dismissal. We’ll most likely implement this in a UIViewController anyway.

我们需要限制此协议仅符合类(1),因为我们需要修改两个preKeyboard属性(3、4)。 我们将使用它们来了解如何在scrollView键盘时还原scrollView的插入和偏移。 无论如何,我们很可能会在UIViewController实现它。

The protocol also needs to have a scrollView (2), otherwise this isn’t really … feasible (I guess it could be doable). Lastly, we need the method that will handle everything (5), but it just acts as a proxy for two helpers that we’ll implement in just a bit:

该协议还需要有一个scrollView (2),否则这实际上是不可行的(我想这是可行的 )。 最后,我们需要可以处理所有内容的方法(5),但它只是作为我们将要实现的两个助手的代理:

extension KeyboardListener {
func keyboardChanged(with notification: Notification) {      guard         notification.name == UIResponder.keyboardWillShowNotification,         let rawFrameEnd = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey],         let frameEnd = rawFrameEnd as? CGRect,         let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval      else {         resetScrollView() // 1
return      }
if let currentTextField = UIResponder.current as? UITextField {         updateContentOffsetOnTextFieldFocus(currentTextField, bottomCoveredArea: frame.height) // 2      }
scrollView.contentInset.bottom += frameEnd.height // 3   }
}

If the notification is not for willShow, or we can not parse the notification’s userInfo, bail out and reset the scrollView. If it is, increase the bottom inset by the keyboard’s height (3). As for (2), we find the current first responder with a little trick to call updateContentOffsetOnTextFieldFocus(_:bottomCoveredArea:) with, but we could also call it from our delegate’s textFieldShouldBeginEditing(_:).

如果该通知不是针对willShow ,或者我们无法解析该通知的userInfo ,请退出并重置scrollView 。 如果是这样,请通过键盘高度(3)增加底部插图。 至于(2),我们找到了当前的第一个响应者,并带有一些技巧来调用updateContentOffsetOnTextFieldFocus(_:bottomCoveredArea:) ,但是我们也可以从委托人的textFieldShouldBeginEditing(_:)调用它。

The first helper will update our two preKeyboard properties:

第一助手将更新我们的两个preKeyboard属性:

extension KeyboardListener where Self: UIViewController { // 1
func keyboardChanged(with notification: Notification) {      // [...]   }
func updateContentOffsetOnTextFieldFocus(_ textField: UITextField, bottomCoveredArea: CGFloat) {      let projectedKeyboardY = view.window!.frame.minY - bottomCoveredArea // 2
if contentInsetPreKeyboardDisplay == nil { // 3         contentInsetPreKeyboardDisplay = scrollView.contentInset      }      if contentOffsetPreKeyboardDisplay == nil { // 4         contentOffsetPreKeyboardDisplay = scrollView.contentOffset      }
let textFieldFrameInWindow = view.window!.convert(textField.frame,                                                        from: textField.superview) // 5      let bottomLimit = textFieldFrameInWindow.maxY + 10 // 6
guard bottomLimit > projectedKeyboardY else { return } // 7
let delta = projectedKeyboardY - bottomLimit // 8      let newOffset = CGPoint(x: scrollView.contentOffset.x,                              y: scrollView.contentOffset.y - delta) // 9
scrollView.setContentOffset(newOffset, animated: true) // 10   }
}

We will now update the protocol extension with a Self: UIViewController constraint (1), because we’ll need access to the window. This shouldn’t be an inconvenience, because this protocol will be most likely used by UIViewControllers. However, another approach would be to replace all the view.window occurrences with UIApplication.shared.keyWindow or a variation of UIApplication.shared.windows[yourIndex], in case you have a complex hierarchy.

现在,我们将使用Self: UIViewController约束(1)更新协议扩展,因为我们需要访问该窗口。 这不应该带来不便,因为UIViewController很有可能会使用此协议。 但是,另一种方法是在层次结构复杂的情况下,将所有view.window出现的内容替换为UIApplication.shared.keyWindowUIApplication.shared.windows[yourIndex]的变体。

We then calculate the minY for the keyboard (2) — we use a parameter for those cases where we have a custom inputView and we’ll call this from textFieldShouldBeginEditing(_:), for example. We then check if our preKeyboard properties are nil. If they are, we assign the current values from the scrollView (3, 4). They might not be nil if we changed them prior to calling this method.

然后,我们计算出minY的键盘(2) -我们使用那些我们有一个自定义的情况下的参数inputView我们将从调用这个textFieldShouldBeginEditing(_:) ,例如。 然后,我们检查preKeyboard属性是否为nil 。 如果是的话,我们从scrollView (3,4)分配当前值。 如果在调用此方法之前更改它们,它们可能不会nil

We then convert the textField’s maxY in the window’s coordinates (5) and add 10 to it (6), so we have a small padding between the field and the keyboard. If the bottomLimit is above the keyboard’s minY, do nothing, because the textField is already fully visible (7). If the bottomLimit is below the keyboard’s minY, calculate the difference between them (8) so we know how much to scroll the scrollView (9, 10) so that the textField will be visible.

然后,我们在窗口的坐标(5)中转换textFieldmaxY并为其添加10 (6),因此在字段和键盘之间会有一个小的填充。 如果bottomLimit在键盘的minY ,则不执行任何操作,因为textField已经完全可见(7)。 如果bottomLimit在键盘的minY ,请计算它们之间的差(8),以便我们知道滚动scrollView (9,10)多少,以便textField可见。

The second helper resets our scrollView back to the initial values:

第二个助手将我们的scrollView重置回初始值:

extension KeyboardListener where Self: UIViewController {
func keyboardChanged(with notification: Notification) {      // [...]   }
func updateContentOffsetOnTextFieldFocus(_ textField: UITextField, bottomCoveredArea: CGFloat) {      // [...]   }
func resetScrollView() {      guard // 1         let originalInsets = contentInsetPreKeyboardDisplay,         let originalOffset = contentOffsetPreKeyboardDisplay      else { return }
scrollView.contentInset = originalInsets // 2      scrollView.setContentOffset(originalOffset, animated: true) // 3
contentInsetPreKeyboardDisplay = nil // 4      contentOffsetPreKeyboardDisplay = nil // 5   }
}

If we have no original insets/offset, do nothing; for example, a hardware keyboard is used (1). If we do, we reset the scrollView to its original, pre-keyboard values (2, 3) and nil-out the preKeyboard properties (4, 5).

如果我们没有原始的插图/偏移,则不执行任何操作。 例如,使用硬件键盘(1)。 如果这样做,则将scrollView重置为其原始的键盘前值(2、3),然后将preKeyboard属性设置为nil preKeyboard (5)。

Using this may vary depending on your needs, but the usual scenario would go like this:

根据您的需要,使用此方法可能会有所不同,但是通常情况如下:

final class FormViewController: UIViewController, KeyboardListener {
let scrollView = UIScrollView()      /* Or if you have a tableView:            private let tableView = UITableView()      var scrollView: UIScrollView {         return tableView      }   */
// [...]
override func viewDidLoad() {      super.videDidLoad()
let center = NotificationCenter.default
center.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: nil) { [weak self] notification in        self?.keyboardChanged(with: notification)      }
center.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: nil) { [weak self] notification in        self?.keyboardChanged(with: notification)      }
// And that's it!   }
// [...]
}

This was a lot of info, but we now have a nice ”keep the text field above the keyboard” logic. If we implement all of this alongside the automatic Next button handling, it will be like magic for our users.

这是很多信息,但是我们现在有了一个很好的“将文本字段保留在键盘上方”的逻辑。 如果我们在自动执行“下一步”按钮处理的同时实现所有这些功能 ,那么对于我们的用户来说,这就像是魔术。

Check out this post about slightly automating this even further, by implementing the Broadcaster/Listener system and moving the observers in the Broadcaster itself. We wouldn’t need to add observers in our view controllers anymore, we’d just have to call Broadcaster.shared.addListener(self).

通过实现Broadcaster / Listener系统并在Broadcaster本身中移动观察者,来查看有关稍微进一步自动化的文章 。 我们不再需要在视图控制器中添加观察者,只需要调用Broadcaster.shared.addListener(self)

As usual, I’d love to hear your thoughts @rolandleth.

和往常一样,我很想听听你的想法

Originally published at rolandleth.com on October 18, 2018.

最初于2018年10月18日在rolandleth.com上发布。

翻译自: https://www.freecodecamp.org/news/how-to-manage-the-keyboard-on-uitextfield-focus-for-a-better-user-experience-1320c057b7c5/

uitextfield键盘

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值