- 限制textView的最大字数
- 计算剩余字数
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
网友评论