// // IBTextView.swift // MTP2_iOS // // Created by Handy_Cao on 2018/8/9. // Copyright © 2018年 Muchinfo. All rights reserved. // import UIKit @IBDesignable class IBTextView: UITextView, UITextViewDelegate { /// 默认最大行高 var maxRowHeight:CGFloat = 100 /// 默认样式 var borderStyle: IBTextViewStyle = .default { didSet { switch borderStyle { case .default: defaultSetting() case .NoneBorder: noneBorderSetting() case .custom: customBorderSetting() } } } /// IBTextView高度变化通知 static let RowChangeNotification = NSNotification.Name.init("IBTextViewRowChange") /// delegate weak var IBTextViewDelegate: UITextViewDelegate? /// 样式 /// /// - `default`: 默认样式 /// - NoneBorder: 无边框样式 /// - custom: 自定义样式,由IB决定 enum IBTextViewStyle { case `default` case NoneBorder case custom } @objc required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) #if swift(>=4.2) let UITextViewTextDidChange = UITextView.textDidChangeNotification #else let UITextViewTextDidChange = Notification.Name.UITextViewTextDidChange #endif NotificationCenter.default.addObserver(self, selector: #selector(self.refreshPlaceholder), name:UITextViewTextDidChange, object: self) delegate = self } @objc override public init(frame: CGRect, textContainer: NSTextContainer?) { super.init(frame: frame, textContainer: textContainer) #if swift(>=4.2) let notificationName = UITextView.textDidChangeNotification #else let notificationName = Notification.Name.UITextViewTextDidChange #endif NotificationCenter.default.addObserver(self, selector: #selector(self.refreshPlaceholder), name: notificationName, object: self) delegate = self } @objc override open func awakeFromNib() { super.awakeFromNib() #if swift(>=4.2) let UITextViewTextDidChange = UITextView.textDidChangeNotification #else let UITextViewTextDidChange = Notification.Name.UITextViewTextDidChange #endif NotificationCenter.default.addObserver(self, selector: #selector(self.refreshPlaceholder), name: UITextViewTextDidChange, object: self) delegate = self } private func defaultSetting() { layer.borderColor = UIColor(red: 215.0 / 255.0, green: 215.0 / 255.0, blue: 215.0 / 255.0, alpha: 1).cgColor layer.borderWidth = 0.6 layer.cornerRadius = 6.0 } private func noneBorderSetting() { layer.borderColor = nil layer.borderWidth = 0 layer.cornerRadius = 0 } private func customBorderSetting() { // 由IB决定 } deinit { placeholderLabel.removeFromSuperview() NotificationCenter.default.removeObserver(self) } private var placeholderInsets : UIEdgeInsets { return UIEdgeInsets(top: self.textContainerInset.top, left: self.textContainerInset.left + self.textContainer.lineFragmentPadding, bottom: self.textContainerInset.bottom, right: self.textContainerInset.right + self.textContainer.lineFragmentPadding) } private var placeholderExpectedFrame : CGRect { let placeholderInsets = self.placeholderInsets let maxWidth = self.frame.width-placeholderInsets.left-placeholderInsets.right let expectedSize = placeholderLabel.sizeThatFits(CGSize(width: maxWidth, height: self.frame.height-placeholderInsets.top-placeholderInsets.bottom)) return CGRect(x: placeholderInsets.left, y: placeholderInsets.top, width: maxWidth, height: expectedSize.height) } lazy var placeholderLabel: UILabel = { let label = UILabel() label.autoresizingMask = [.flexibleWidth, .flexibleHeight] label.lineBreakMode = .byWordWrapping label.numberOfLines = 0 label.font = self.font label.textAlignment = self.textAlignment label.backgroundColor = UIColor.clear label.textColor = UIColor(white: 0.7, alpha: 1.0) label.alpha = 0 self.addSubview(label) return label }() /** @abstract To set textView's placeholder text color. */ @IBInspectable open var placeholderTextColor : UIColor? { get { return placeholderLabel.textColor } set { placeholderLabel.textColor = newValue } } /** @abstract To set textView's placeholder text. Default is nil. */ @IBInspectable open var placeholder : String? { get { return placeholderLabel.text } set { placeholderLabel.text = newValue refreshPlaceholder() } } /** @abstract To set textView's borderColor. Default is nil. */ @IBInspectable open var borderColor : UIColor? { get { if let borderColor = layer.borderColor { return UIColor(cgColor: borderColor) } else { return nil } } set { if let newValue = newValue { layer.borderColor = newValue.cgColor } else { layer.borderColor = UIColor(red: 215.0 / 255.0, green: 215.0 / 255.0, blue: 215.0 / 255.0, alpha: 1).cgColor } } } /** @abstract To set textView's borderWidth. Default is 0. */ @IBInspectable open var borderWidth : CGFloat { get { return layer.borderWidth } set { layer.borderWidth = newValue } } /** @abstract To set textView's cornerRadius. Default is 0. */ @IBInspectable open var cornerRadius : CGFloat { get { return layer.cornerRadius } set { layer.cornerRadius = newValue } } /** @abstract To set textView's placeholder attributed text. Default is nil. */ open var attributedPlaceholder: NSAttributedString? { get { return placeholderLabel.attributedText } set { placeholderLabel.attributedText = newValue refreshPlaceholder() } } @objc override open func layoutSubviews() { super.layoutSubviews() placeholderLabel.frame = placeholderExpectedFrame } @objc internal func refreshPlaceholder() { if !text.isEmpty || !attributedText.string.isEmpty { placeholderLabel.alpha = 0 } else { placeholderLabel.alpha = 1 } } @objc override open var text: String! { didSet { refreshPlaceholder() } } open override var attributedText: NSAttributedString! { didSet { refreshPlaceholder() } } @objc override open var font : UIFont? { didSet { if let unwrappedFont = font { placeholderLabel.font = unwrappedFont } else { placeholderLabel.font = UIFont.systemFont(ofSize: 12) } } } @objc override open var textAlignment: NSTextAlignment { didSet { placeholderLabel.textAlignment = textAlignment } } @objc override internal var delegate : UITextViewDelegate? { get { refreshPlaceholder() return super.delegate } set { super.delegate = newValue } } @objc override open var intrinsicContentSize: CGSize { guard !hasText else { var newSize = super.intrinsicContentSize newSize.height = 30 return newSize } var newSize = super.intrinsicContentSize let placeholderInsets = self.placeholderInsets newSize.height = placeholderExpectedFrame.height + placeholderInsets.top + placeholderInsets.bottom return newSize } //MARK: - UITextViewDelegate func textViewDidChange(_ textView: UITextView) { let frame = textView.frame let constraintSize = CGSize(width: frame.width, height: CGFloat(MAXFLOAT)) let size = textView.sizeThatFits(constraintSize) if size.height <= maxRowHeight { let constraints = textView.constraints.filter({ $0.firstAttribute == NSLayoutConstraint.Attribute.height }) if constraints.count > 0 { textView.removeConstraints(constraints) } let newConstraints = NSLayoutConstraint(item: textView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: size.height) newConstraints.priority = UILayoutPriority(rawValue: 999) textView.addConstraint(newConstraints) NotificationCenter.default.post(name: IBTextView.RowChangeNotification, object: nil) } else { let constraints = textView.constraints.filter({ $0.firstAttribute == NSLayoutConstraint.Attribute.height }) if constraints.count > 0 { textView.removeConstraints(constraints) } let newConstraints = NSLayoutConstraint(item: textView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: maxRowHeight) newConstraints.priority = UILayoutPriority(rawValue: 999) textView.addConstraint(newConstraints) NotificationCenter.default.post(name: IBTextView.RowChangeNotification, object: nil) } } func textViewShouldBeginEditing(_ textView: UITextView) -> Bool { return IBTextViewDelegate?.textViewShouldEndEditing?(textView) ?? true } func textViewShouldEndEditing(_ textView: UITextView) -> Bool { return IBTextViewDelegate?.textViewShouldEndEditing?(textView) ?? true } func textViewDidBeginEditing(_ textView: UITextView) { IBTextViewDelegate?.textViewDidBeginEditing?(textView) } func textViewDidEndEditing(_ textView: UITextView) { IBTextViewDelegate?.textViewDidEndEditing?(textView) } func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { return IBTextViewDelegate?.textView?(textView, shouldChangeTextIn: range, replacementText: text) ?? true } }