引言

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

这里面有两个需要注意的地方:

  1. 首先需要判断该输入框是否是第一响应者,如果不是第一响应者那么就不需要处理它。
  2. 第二我们采用了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()
    }

}

整个输入框效果如下:

结语

在本篇博客中,我们演示了简单的实现方式,已经能够满足大部分需求。进一步优化的话,我们可以定制输入框与键盘之间的距离,还可以自动获取需要上移的视图等功能,这些都可以根据具体需求进行定制和扩展。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部