自适应键盘,自带隐藏键盘的输入框(UITextField)
引言
在iOS开发中,输入框占据着举足轻重的地位。与安卓不同,iOS输入框经常面临键盘遮挡的问题,或者无法方便地取消键盘。为了解决这些问题,有许多针对iOS键盘管理的库,如IQKeyboardManager、TPKeyboardAvoiding和KeyboardManager等等。
然而,一些库可能对整个项目的侵入性较大,可能会影响到其他功能。有时,我们可能不希望某些输入框被这些库管理,虽然它们通常也提供了相应的解决方案,但有时会显得有些繁琐。
因此,我们可以考虑自己实现一个输入框,根据项目需求定制输入框的功能。这样做不仅轻量级,而且更加灵活。
实现
本篇博客将通过继承的方式,分别介绍如何自定义实现UITextFiled和UITextView。即使你的项目已经存在一段时间,也可以采用"黑魔法"的方式来实现这些功能。
我们首先明确两个要解决的问题:第一个是解决键盘遮挡输入框的问题,第二个是管理键盘的显示和隐藏。
UITextField
首先继承UITextField创建一个名为LATextField的类,然后通过重写它的init方法来处理上面要解决的两个问题。
解决键盘遮挡
为它添加一个属性,该属性是指当键盘出现时,需要跟随键盘上移的视图,我们可以通过遍历父图层的方式自动获取,也可以使用主动赋值的方式。
但属性一定要使用weak来修饰(子视图不能持有它的父视图)。
代码如下:
class LATextField: UITextField { /// 滑动的视图 weak var sliderView:UIView? override init(frame: CGRect) { super.init(frame: frame) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
在init方法中添加关于键盘出现和消失的监听,代码如下:
override init(frame: CGRect) { super.init(frame: frame) addNotification() } fileprivate func addNotification() { // 监听键盘的弹出和收起 NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) }
键盘弹出后,我们可以通过通知的userinfo来获取键盘出现的动画时长,以及键盘的frame。
动画时长对应的key为UIResponder.keyboardAnimationDurationUserInfoKey
键盘frame对一个的key为UIResponder.keyboardFrameEndUserInfoKey
代码如下:
@objc fileprivate func keyboardWillShow(notification: Notification) { let userInfo = notification.userInfo let duration = userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as! TimeInterval let keyboardFrame = userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as! CGRect let keyboardHeight = keyboardFrame.size.height }
当我们获取到了键盘的frame就可以判断输入框是否被键盘遮挡。而键盘出现的动画时长可以让我们的上移动画更平滑,像是跟随键盘进行上移和下移,具体代码如下:
if !self.isFirstResponder { return } guard let sliderView = sliderView else { return } // 输入框相对于屏幕的位置 let textfiledFrame = self.convert(self.bounds, to: UIApplication.shared.keyWindow) // 输入框的底部位置 let textfiledBottom = textfiledFrame.origin.y + textfiledFrame.size.height if textfiledBottom > keyboardFrame.origin.y { let offsetY = textfiledBottom - keyboardFrame.origin.y UIView.animate(withDuration: duration) { sliderView.transform = CGAffineTransform(translationX: 0, y: -offsetY) } }
这里面有两个需要注意的地方:
- 首先需要判断该输入框是否是第一响应者,如果不是第一响应者那么就不需要处理它。
- 第二我们采用了transform进行移动,而不是直接修改y值,防止移动被累加,也方便还原操作。
当键盘隐藏时,我们只需要设置sliderView的transform为.identity即可,代码如下:
@objc fileprivate func keyboardWillHide(notification: Notification) { let userInfo = notification.userInfo let duration = userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as! TimeInterval // if !self.isFirstResponder { // return // } guard let sliderView = sliderView else { return } UIView.animate(withDuration: duration) { sliderView.transform = .identity } }
这里面就不需要进行判断该键盘是否是第一响应者了,因为键盘消失,那么该输入框明显已经不是第一响应者,如果此处添加了判断会影响页面还原。
解决键盘隐藏
这也是一个场景的问题,我们通常会每个输入框都单独处理,比如通过重写它的回车按钮,或者在页面添加手势点击后隐藏键盘。
在这里我们借助它的一个inputAccessoryView属性,在inputAccessoryView添加一个down按钮,点击后调用resignFirstResponder方法取消输入框的第一响应者身份。
代码如下:
// 添加默认的输入框附加视图 fileprivate func addInputAccessoryView() { let inputAccessortyView = UIView(frame: CGRect(x: 0, y: 0, width: SCREENWIDTH, height: 40)) inputAccessortyView.backgroundColor = .clear let arrowButton = LADownButton() arrowButton.frame = CGRect(x: SCREENWIDTH - 40, y: 8, width: 30, height: 30) arrowButton.layer.cornerRadius = 15 arrowButton.layer.masksToBounds = true arrowButton.backgroundColor = .black.withAlphaComponent(0.1) inputAccessortyView.addSubview(arrowButton) arrowButton.addTarget(self, action: #selector(arrowButtonAction), for: .touchUpInside) self.inputAccessoryView = inputAccessortyView } @objc fileprivate func arrowButtonAction() { self.resignFirstResponder() }
其中LADownButton按钮是一个我们自定义的按钮,不过它里面并没有什么实际的东西只是绘制了一个向下的箭头,你可以使用文案代替,也可以使用图片代替。
借助了UIBezierPath来进行绘制,它的代码如下:
class LADownButton: UIButton { override func draw(_ rect: CGRect) { // 绘制向下的三角 let path = UIBezierPath() path.move(to: CGPoint(x: rect.width * 0.2, y: rect.height * 0.4)) path.addLine(to: CGPoint(x: rect.width / 2, y: rect.height * 0.7)) path.addLine(to: CGPoint(x: rect.width * 0.8, y: rect.height * 0.4)) UIColor.white.setStroke() path.lineWidth = 2.0 path.lineJoinStyle = .round path.stroke() } }
整个输入框效果如下:
结语
在本篇博客中,我们演示了简单的实现方式,已经能够满足大部分需求。进一步优化的话,我们可以定制输入框与键盘之间的距离,还可以自动获取需要上移的视图等功能,这些都可以根据具体需求进行定制和扩展。