引言
在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()
}
}
整个输入框效果如下:
结语
在本篇博客中,我们演示了简单的实现方式,已经能够满足大部分需求。进一步优化的话,我们可以定制输入框与键盘之间的距离,还可以自动获取需要上移的视图等功能,这些都可以根据具体需求进行定制和扩展。