对TextView的封装

2018-07-27  本文已影响0人  LiYaoPeng
  1. 限制textView的最大字数
  2. 计算剩余字数

demo

属性:

  open var placeholderLabel: UILabel { return self.placeholderLabel_prevate }
   open var remainWordsLabel: UILabel { return self.remainWordsLabel_private }
   open var textView: UITextView { return self.textView_private }

/// 向下滑动,关闭编辑
    open var isDownScrollEndEdit: Bool = false
    
    /// 下拉多少时候需要关闭编辑
    open var pullDownMarginEndEdit: CGFloat = 10
    
    /// 可以输入的最大字数 如果小于 0那么不再制约输入字数
    open var maxNumberOfWords = -1
    ///placeholder
    open var placeholder = "写几句评论吧..." {
        didSet{ placeholderLabel.text = placeholder } }
    open var placeholderColor: UIColor? { didSet { placeholderLabel.textColor = placeholderColor ?? c_0x333333() } }
    open var placeholderFont = UIFont.systemFont(ofSize: 14) { didSet { placeholderLabel.font = placeholderFont } }
    ///font
    open var font: UIFont = UIFont.systemFont(ofSize: 14) { didSet { textView.font = font } }
    ///text
    open var text: String {
        get{ return textView.text }
        set{ setText(text: newValue) }
    }
    open var textColor: UIColor? {
        didSet {
            textView.textColor = textColor ?? c_0x333333()
        }
    }
    ///剩余 数字书描述的 边距
    open var remainWordsLabelEdg: UIEdgeInsets = .zero { didSet { layoutRemainWords() } }
     
    /// 剩余字数label的高度 默认为 font(pingfangR: 14) height为 17pt
    open var remainWordsLabelHeight: CGFloat = 0 { didSet { layoutRemainWords() } }
    
    /// placeholder 边距
    open var placeholderLabelEdg: UIEdgeInsets = UIEdgeInsets.init(
        top: 0,
        left: 8,
        bottom: 0,
        right: 0)
        { didSet { layoutPlaceholder() }}
    
    ///textView的边距
    open var textContainerInset: UIEdgeInsets = .zero { didSet { didSetTextContainerInset() } }

方法

/// 设置底部剩余输入字数的 描述
    ///
    /// - surplusCount: 剩余字数
    /// - maxNumberOfWords: 总数
    
    open func setBottomDescreptionFunc(_ setBottomDescreptionCallBack:((_ surplusCount: NSInteger, _ maxNumberOfWords: NSInteger)->(NSAttributedString))?) {
        self.setBottomDescreptionCallBack = setBottomDescreptionCallBack
    }
    
    /// 已经改变text的时候
    ///
    /// - Parameter willChangeTextCallBack:
    /// - crrentText:  textView 没有拼接新字符
    /// - willText: textView 拼接新字符
    /// - text: 新输入的字符
    open func changedTextFucn (_ changedTextCallBack:((_ textView: UITextView, _ text: String)->())?) {
        self.changedTextCallBack = changedTextCallBack
    }
    
    
    /// 已经结束编辑的时候
    ///
    /// - Parameter willChangeTextCallBack:
    /// - crrentText:  textView 没有拼接新字符
    /// - willText: textView 拼接新字符
    /// - text: 新输入的字符
    open func didEndEditingFunc (_ changedTextCallBack:((_ textView: UITextView, _ text: String)->())?) {
        self.txtRemarkDidEndEditingCallBack = changedTextCallBack
    }
    
    /// 输入字数到达最大值的时候调用
    ///
    /// - Parameter block: 回调
    open func reachTheMaximumNumberOfWordsFunc(_ block:((_ textView: UITextView,_ maxNumber: NSInteger)->())?) {
        reachTheMaximumNumberOfWords = block
    }

具体实现

   public override init(frame: CGRect) {
        super.init(frame:frame)
        setup()
    }
    
    required public init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    //MARK: - 关于配置
    ///设置
    private func setup() {
        setupView()
        setupNotification()
    }
    private func setupNotification() {
        NotificationCenter.default.addObserver(self, selector: #selector(keybordChange(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keybordChange(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(txtRemarkEditChanged), name: NSNotification.Name.UITextViewTextDidChange, object: self.textView)
        NotificationCenter.default.addObserver(self, selector: #selector(txtRemarkDidEndEditing), name: NSNotification.Name.UITextViewTextDidEndEditing, object: self.textView)
    }
    var placeholderLeft: NSLayoutConstraint?
    var placeholderRight: NSLayoutConstraint?
    var placeholderTop: NSLayoutConstraint?
    var placeholderBottom: NSLayoutConstraint?
    
    var remainWordsLeft: NSLayoutConstraint?
    var remainWordsRight: NSLayoutConstraint?
    var remainWordsTop: NSLayoutConstraint?
    var remainWordsBottom: NSLayoutConstraint?
    
    private func setupView() {
        addSubview(textView)
        addSubview(placeholderLabel)
        addSubview(remainWordsLabel)
        textView.edgsEqual(toItem: self, offset: 0)
        layoutPlaceholder()
        layoutRemainWords()
    }
    
    private func layoutPlaceholder() {
        if let placeholderLeft = placeholderLeft,
            let placeholderRight = placeholderRight,
            let placeholderBottom = placeholderBottom,
            let placeholderTop = placeholderTop{
            
            self.removeConstraint(placeholderLeft)
            self.removeConstraint(placeholderRight)
            self.removeConstraint(placeholderBottom)
            self.removeConstraint(placeholderTop)
        }
        
        let left =  textContainerInset.left + placeholderLabelEdg.left
        let right = -textContainerInset.right - placeholderLabelEdg.right
        let top = textContainerInset.top + placeholderLabelEdg.top
        let bottom = -textContainerInset.bottom - placeholderLabelEdg.bottom
        
        placeholderLeft = placeholderLabel.leftEqual(toItem: self, offset: left)
        placeholderRight = placeholderLabel.rightEqual(toItem: self, offset: right)
        
        placeholderTop = placeholderLabel.topEqual(toItem: self, offset: top)
        placeholderBottom = placeholderLabel.bottomLessThanOrEqual(toItem: self, offset: bottom)
        self.updateConstraints()
    }
    
    private func layoutRemainWords() {
        if let remainWordsLeft = remainWordsLeft,
            let remainWordsRight = remainWordsRight,
            let remainWordsBottom = remainWordsBottom,
            let remainWordsTop = remainWordsTop{
            
            self.removeConstraint(remainWordsLeft)
            self.removeConstraint(remainWordsRight)
            self.removeConstraint(remainWordsBottom)
            self.removeConstraint(remainWordsTop)
        }
        if let textViewBottom = textViewBottom {
            self.removeConstraint(textViewBottom)
        }
        
        let left =  textContainerInset.left + remainWordsLabelEdg.left
        let right = -textContainerInset.right - remainWordsLabelEdg.right
        let top = remainWordsLabelEdg.top
        let bottom = -textContainerInset.bottom - remainWordsLabelEdg.bottom
        
        remainWordsLeft = remainWordsLabel.leftLessThanOrEqual(toItem: self, offset: left)
        remainWordsRight = remainWordsLabel.rightEqual(toItem: self, offset: right)
        
        //        remainWordsTop = remainWordsLabel.topLessThanOrEqual(toItem: self, offset: top)
        remainWordsBottom = remainWordsLabel.bottomEqual(toItem: self, offset: bottom)
        
        let bottomMargin = remainWordsLabelHeight - bottom + top
        textViewBottom = textView.bottomEqual(toItem: self, offset: -bottomMargin)
        self.updateConstraints()
    }

    private func didSetTextContainerInset() {
        textView.textContainerInset = textContainerInset
        layoutPlaceholder()
        layoutRemainWords()
    }
    
    private var isFirst: Bool = true
    override open func layoutSubviews() {
        if isFirst {
            var surplusCount = (maxWords - textView.text.count)
            surplusCount = surplusCount <= 0 ? 0 : surplusCount
            surplusCount = surplusCount >= maxWords ? maxWords : surplusCount
            remainWordsLabel.attributedText = setBottomDescreptionCallBack?(surplusCount,maxWords)
            textView.backgroundColor = self.backgroundColor
            isFirst = false
        }
    }
    //MARK: - Notification
    /// 已经改变的时候调用
    @objc private func txtRemarkEditChanged(notif: Notification) {
        guard let textView: UITextView = notif.object as? UITextView else { return }
        
        placeholderLabel.isHidden = textView.text.count > 0
        
        let length = textView.text.count
        let range = NSRange.init(location: length - 1, length: 1)
        textView.scrollRangeToVisible(range)
        
        if (maxWords >= 0
            && length > maxWords
            && (textView.markedTextRange == nil)) {
            
            textView.text = NSString(string: textView.text).substring(with: NSRange.init(location: 0, length: maxWords))
            remainWordsLabel.attributedText = setBottomDescreptionCallBack?(0,maxWords)
            textView.undoManager?.removeAllActions()
            
        } else if (textView.markedTextRange == nil) {
            var length = (maxWords - textView.text.count)
            length = length < 0 ? 0 : length
            remainWordsLabel.attributedText = setBottomDescreptionCallBack?( length,maxWords)
        }
        
        changedTextCallBack?(textView,textView.text)
    }
    @objc private func txtRemarkDidEndEditing(notif: Notification) {
        if let textView = notif.object as? UITextView {
            placeholderLabel.isHidden = textView.text.count > 0
            txtRemarkDidEndEditingCallBack?(textView,textView.text)
        }
    }
    @objc private func keybordChange(notification:Notification)  {
        
        let userinfo: NSDictionary = notification.userInfo! as NSDictionary
        
        let nsValue = userinfo.object(forKey: UIKeyboardFrameEndUserInfoKey) as! NSValue
        
        let keyboardRec = nsValue.cgRectValue
        let y = keyboardRec.origin.y
        //获取textview 在window的y + h
        let view = UIApplication.shared.keyWindow ?? self
        let pointToWindow = convert(textView.frame, to: view)
        let maxY = self.textView.frame.size.height + pointToWindow.origin.y
        var margin = maxY - y
        margin = margin <= 0 ? 0 : margin
        textView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: margin, right: 0)
    }
    var panPointStart: CGPoint = .zero
    var panPoint: CGPoint = .zero
    open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == "contentOffset" {
            let state = textView.panGestureRecognizer.state
            switch state {
                
            case .possible:fallthrough
            case .began:
                panPointStart = textView.contentOffset
            case .changed:fallthrough
            case .cancelled:fallthrough
            case .failed:fallthrough
            case .ended: break
            }
            
            if (textView.contentOffset.y - panPointStart.y) < -pullDownMarginEndEdit {
                
                self.endEditing(true)
            }
        }
        
        //        }
    }
    
    
    //MARK: - property
    private lazy var textView_private: UITextView = {
        let textView = UITextView()
        textView.font = UIFont.systemFont(ofSize: 14)
        
        textView.addObserver(self, forKeyPath: "contentOffset", options: .new, context: nil)
        return textView
    }()
    ///懒加载 还可以输入 x 个字
    private lazy var remainWordsLabel_private: UILabel = {
        let label = UILabel()
        label.font = UIFont.systemFont(ofSize: 14)
        label.numberOfLines = 0
        label.attributedText = self.setBottomDescreptionCallBack?(maxWords,maxWords)
        label.textColor = UIColor.init(red: 51.0/225,
                                       green: 51.0/225,
                                       blue: 51.0/225,
                                       alpha: 1)
        label.textAlignment = .right
        return label
    }()
    ///懒加载 写几句评论吧...
    private lazy var placeholderLabel_prevate: UILabel = {
        let label = UILabel()
        label.font = UIFont.systemFont(ofSize: 14)
        label.numberOfLines = 0
        label.text = self.placeholder
        label.textColor = UIColor(red: 204.0 / 255.0,
                                  green: 204.0 / 255.0,
                                  blue: 204.0 / 255.0,
                                  alpha: 1.0)
        label.textAlignment = .left
        return label
    }()
    
    private func setText(text: String) {
      textView.text = text
        placeholderLabel.isHidden = true
        var surplusCount = (maxWords - text.count)
        surplusCount = surplusCount <= 0 ? 0 : surplusCount
        surplusCount = surplusCount >= maxWords ? maxWords : surplusCount
        remainWordsLabel.attributedText = setBottomDescreptionCallBack?(surplusCount,maxWords)
        changedTextCallBack?(textView,text)
    }
    
    private func c_0x333333() -> UIColor {
        return UIColor(red: 51.0 / 255.0, green: 51.0 / 255.0, blue: 51.0 / 255.0, alpha: 1.0)
    }
    deinit {
        NotificationCenter.default.removeObserver(self)
        textView.removeObserver(self, forKeyPath: "contentOffset")
    }
}

UIView布局分类

private extension UIView {
    func leftEqual(toItem:UIView,offset: CGFloat) -> NSLayoutConstraint {
        translatesAutoresizingMaskIntoConstraints = false
        let left = NSLayoutConstraint.init(
            item: self,
            attribute: .left,
            relatedBy: .equal,
            toItem: toItem,
            attribute: .left,
            multiplier: 1,
            constant: offset
        )
        toItem.addConstraint(left)
        return left
    }
    
    func rightEqual(toItem:UIView,offset: CGFloat) -> NSLayoutConstraint {
        translatesAutoresizingMaskIntoConstraints = false
        let right = NSLayoutConstraint.init(
            item: self,
            attribute: .right,
            relatedBy: .equal,
            toItem: toItem,
            attribute: .right,
            multiplier: 1,
            constant: offset
        )
        toItem.addConstraint(right)
        return right
    }
    
    func topEqual(toItem:UIView,offset: CGFloat) -> NSLayoutConstraint {
        translatesAutoresizingMaskIntoConstraints = false
        let top = NSLayoutConstraint.init(
            item: self,
            attribute: .top,
            relatedBy: .equal,
            toItem: toItem,
            attribute: .top,
            multiplier: 1,
            constant: offset
        )
        toItem.addConstraint(top)
        return top
    }
    func bottomEqual(toItem:UIView,offset: CGFloat) -> NSLayoutConstraint {
        translatesAutoresizingMaskIntoConstraints = false
        let bottom = NSLayoutConstraint.init(
            item: self,
            attribute: .bottom,
            relatedBy: .equal,
            toItem: toItem,
            attribute: .bottom,
            multiplier: 1,
            constant: offset
        )
        toItem.addConstraint(bottom)
        return bottom
    }
    func bottomLessThanOrEqual(toItem: UIView, offset: CGFloat) -> NSLayoutConstraint {
        translatesAutoresizingMaskIntoConstraints = false
        let bottom = NSLayoutConstraint.init(
            item: self,
            attribute: .bottom,
            relatedBy: .lessThanOrEqual,
            toItem: toItem,
            attribute: .bottom,
            multiplier: 1,
            constant: offset
        )
        toItem.addConstraint(bottom)
        return bottom
    }
    
    func topLessThanOrEqual(toItem: UIView, offset: CGFloat) -> NSLayoutConstraint {
        translatesAutoresizingMaskIntoConstraints = false
        let top = NSLayoutConstraint.init(
            item: self,
            attribute: .top,
            relatedBy: .lessThanOrEqual,
            toItem: toItem,
            attribute: .top,
            multiplier: 1,
            constant: offset
        )
        toItem.addConstraint(top)
        return top
    }
    
    func leftLessThanOrEqual(toItem: UIView, offset: CGFloat) -> NSLayoutConstraint {
        translatesAutoresizingMaskIntoConstraints = false
        let left = NSLayoutConstraint.init(
            item: self,
            attribute: .left,
            relatedBy: .lessThanOrEqual,
            toItem: toItem,
            attribute: .left,
            multiplier: 1,
            constant: offset
        )
        toItem.addConstraint(left)
        return left
    }
    
    func edgsEqual(toItem: UIView, offset: CGFloat) {
        translatesAutoresizingMaskIntoConstraints = false
        let _ = leftEqual(toItem: toItem, offset: offset)
        let _ = rightEqual(toItem: toItem, offset: -offset)
        let _ = bottomEqual(toItem: toItem, offset: -offset)
        let _ = topEqual(toItem: toItem, offset: offset)
    }
}

demo

上一篇下一篇

猜你喜欢

热点阅读