IBTextView.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. //
  2. // IBTextView.swift
  3. // MTP2_iOS
  4. //
  5. // Created by Handy_Cao on 2018/8/9.
  6. // Copyright © 2018年 Muchinfo. All rights reserved.
  7. //
  8. import UIKit
  9. @IBDesignable
  10. class IBTextView: UITextView, UITextViewDelegate {
  11. /// 默认最大行高
  12. var maxRowHeight:CGFloat = 100
  13. /// 默认样式
  14. var borderStyle: IBTextViewStyle = .default {
  15. didSet {
  16. switch borderStyle {
  17. case .default:
  18. defaultSetting()
  19. case .NoneBorder:
  20. noneBorderSetting()
  21. case .custom:
  22. customBorderSetting()
  23. }
  24. }
  25. }
  26. /// IBTextView高度变化通知
  27. static let RowChangeNotification = NSNotification.Name.init("IBTextViewRowChange")
  28. /// delegate
  29. weak var IBTextViewDelegate: UITextViewDelegate?
  30. /// 样式
  31. ///
  32. /// - `default`: 默认样式
  33. /// - NoneBorder: 无边框样式
  34. /// - custom: 自定义样式,由IB决定
  35. enum IBTextViewStyle {
  36. case `default`
  37. case NoneBorder
  38. case custom
  39. }
  40. @objc required public init?(coder aDecoder: NSCoder) {
  41. super.init(coder: aDecoder)
  42. #if swift(>=4.2)
  43. let UITextViewTextDidChange = UITextView.textDidChangeNotification
  44. #else
  45. let UITextViewTextDidChange = Notification.Name.UITextViewTextDidChange
  46. #endif
  47. NotificationCenter.default.addObserver(self, selector: #selector(self.refreshPlaceholder), name:UITextViewTextDidChange, object: self)
  48. delegate = self
  49. }
  50. @objc override public init(frame: CGRect, textContainer: NSTextContainer?) {
  51. super.init(frame: frame, textContainer: textContainer)
  52. #if swift(>=4.2)
  53. let notificationName = UITextView.textDidChangeNotification
  54. #else
  55. let notificationName = Notification.Name.UITextViewTextDidChange
  56. #endif
  57. NotificationCenter.default.addObserver(self, selector: #selector(self.refreshPlaceholder), name: notificationName, object: self)
  58. delegate = self
  59. }
  60. @objc override open func awakeFromNib() {
  61. super.awakeFromNib()
  62. #if swift(>=4.2)
  63. let UITextViewTextDidChange = UITextView.textDidChangeNotification
  64. #else
  65. let UITextViewTextDidChange = Notification.Name.UITextViewTextDidChange
  66. #endif
  67. NotificationCenter.default.addObserver(self, selector: #selector(self.refreshPlaceholder), name: UITextViewTextDidChange, object: self)
  68. delegate = self
  69. }
  70. private func defaultSetting() {
  71. layer.borderColor = UIColor(red: 215.0 / 255.0, green: 215.0 / 255.0, blue: 215.0 / 255.0, alpha: 1).cgColor
  72. layer.borderWidth = 0.6
  73. layer.cornerRadius = 6.0
  74. }
  75. private func noneBorderSetting() {
  76. layer.borderColor = nil
  77. layer.borderWidth = 0
  78. layer.cornerRadius = 0
  79. }
  80. private func customBorderSetting() {
  81. // 由IB决定
  82. }
  83. deinit {
  84. placeholderLabel.removeFromSuperview()
  85. NotificationCenter.default.removeObserver(self)
  86. }
  87. private var placeholderInsets : UIEdgeInsets {
  88. return UIEdgeInsets(top: self.textContainerInset.top, left: self.textContainerInset.left + self.textContainer.lineFragmentPadding, bottom: self.textContainerInset.bottom, right: self.textContainerInset.right + self.textContainer.lineFragmentPadding)
  89. }
  90. private var placeholderExpectedFrame : CGRect {
  91. let placeholderInsets = self.placeholderInsets
  92. let maxWidth = self.frame.width-placeholderInsets.left-placeholderInsets.right
  93. let expectedSize = placeholderLabel.sizeThatFits(CGSize(width: maxWidth, height: self.frame.height-placeholderInsets.top-placeholderInsets.bottom))
  94. return CGRect(x: placeholderInsets.left, y: placeholderInsets.top, width: maxWidth, height: expectedSize.height)
  95. }
  96. lazy var placeholderLabel: UILabel = {
  97. let label = UILabel()
  98. label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  99. label.lineBreakMode = .byWordWrapping
  100. label.numberOfLines = 0
  101. label.font = self.font
  102. label.textAlignment = self.textAlignment
  103. label.backgroundColor = UIColor.clear
  104. label.textColor = UIColor(white: 0.7, alpha: 1.0)
  105. label.alpha = 0
  106. self.addSubview(label)
  107. return label
  108. }()
  109. /** @abstract To set textView's placeholder text color. */
  110. @IBInspectable open var placeholderTextColor : UIColor? {
  111. get {
  112. return placeholderLabel.textColor
  113. }
  114. set {
  115. placeholderLabel.textColor = newValue
  116. }
  117. }
  118. /** @abstract To set textView's placeholder text. Default is nil. */
  119. @IBInspectable open var placeholder : String? {
  120. get {
  121. return placeholderLabel.text
  122. }
  123. set {
  124. placeholderLabel.text = newValue
  125. refreshPlaceholder()
  126. }
  127. }
  128. /** @abstract To set textView's borderColor. Default is nil. */
  129. @IBInspectable open var borderColor : UIColor? {
  130. get {
  131. if let borderColor = layer.borderColor {
  132. return UIColor(cgColor: borderColor)
  133. } else {
  134. return nil
  135. }
  136. }
  137. set {
  138. if let newValue = newValue {
  139. layer.borderColor = newValue.cgColor
  140. } else {
  141. layer.borderColor = UIColor(red: 215.0 / 255.0, green: 215.0 / 255.0, blue: 215.0 / 255.0, alpha: 1).cgColor
  142. }
  143. }
  144. }
  145. /** @abstract To set textView's borderWidth. Default is 0. */
  146. @IBInspectable open var borderWidth : CGFloat {
  147. get {
  148. return layer.borderWidth
  149. }
  150. set {
  151. layer.borderWidth = newValue
  152. }
  153. }
  154. /** @abstract To set textView's cornerRadius. Default is 0. */
  155. @IBInspectable open var cornerRadius : CGFloat {
  156. get {
  157. return layer.cornerRadius
  158. }
  159. set {
  160. layer.cornerRadius = newValue
  161. }
  162. }
  163. /** @abstract To set textView's placeholder attributed text. Default is nil. */
  164. open var attributedPlaceholder: NSAttributedString? {
  165. get {
  166. return placeholderLabel.attributedText
  167. }
  168. set {
  169. placeholderLabel.attributedText = newValue
  170. refreshPlaceholder()
  171. }
  172. }
  173. @objc override open func layoutSubviews() {
  174. super.layoutSubviews()
  175. placeholderLabel.frame = placeholderExpectedFrame
  176. }
  177. @objc internal func refreshPlaceholder() {
  178. if !text.isEmpty || !attributedText.string.isEmpty {
  179. placeholderLabel.alpha = 0
  180. } else {
  181. placeholderLabel.alpha = 1
  182. }
  183. }
  184. @objc override open var text: String! {
  185. didSet {
  186. refreshPlaceholder()
  187. }
  188. }
  189. open override var attributedText: NSAttributedString! {
  190. didSet {
  191. refreshPlaceholder()
  192. }
  193. }
  194. @objc override open var font : UIFont? {
  195. didSet {
  196. if let unwrappedFont = font {
  197. placeholderLabel.font = unwrappedFont
  198. } else {
  199. placeholderLabel.font = UIFont.systemFont(ofSize: 12)
  200. }
  201. }
  202. }
  203. @objc override open var textAlignment: NSTextAlignment
  204. {
  205. didSet {
  206. placeholderLabel.textAlignment = textAlignment
  207. }
  208. }
  209. @objc override internal var delegate : UITextViewDelegate? {
  210. get {
  211. refreshPlaceholder()
  212. return super.delegate
  213. }
  214. set {
  215. super.delegate = newValue
  216. }
  217. }
  218. @objc override open var intrinsicContentSize: CGSize {
  219. guard !hasText else {
  220. var newSize = super.intrinsicContentSize
  221. newSize.height = 30
  222. return newSize
  223. }
  224. var newSize = super.intrinsicContentSize
  225. let placeholderInsets = self.placeholderInsets
  226. newSize.height = placeholderExpectedFrame.height + placeholderInsets.top + placeholderInsets.bottom
  227. return newSize
  228. }
  229. //MARK: - UITextViewDelegate
  230. func textViewDidChange(_ textView: UITextView) {
  231. let frame = textView.frame
  232. let constraintSize = CGSize(width: frame.width, height: CGFloat(MAXFLOAT))
  233. let size = textView.sizeThatFits(constraintSize)
  234. if size.height <= maxRowHeight {
  235. let constraints = textView.constraints.filter({ $0.firstAttribute == NSLayoutConstraint.Attribute.height })
  236. if constraints.count > 0 {
  237. textView.removeConstraints(constraints)
  238. }
  239. let newConstraints = NSLayoutConstraint(item: textView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: size.height)
  240. newConstraints.priority = UILayoutPriority(rawValue: 999)
  241. textView.addConstraint(newConstraints)
  242. NotificationCenter.default.post(name: IBTextView.RowChangeNotification, object: nil)
  243. } else {
  244. let constraints = textView.constraints.filter({ $0.firstAttribute == NSLayoutConstraint.Attribute.height })
  245. if constraints.count > 0 {
  246. textView.removeConstraints(constraints)
  247. }
  248. let newConstraints = NSLayoutConstraint(item: textView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: maxRowHeight)
  249. newConstraints.priority = UILayoutPriority(rawValue: 999)
  250. textView.addConstraint(newConstraints)
  251. NotificationCenter.default.post(name: IBTextView.RowChangeNotification, object: nil)
  252. }
  253. }
  254. func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
  255. return IBTextViewDelegate?.textViewShouldEndEditing?(textView) ?? true
  256. }
  257. func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
  258. return IBTextViewDelegate?.textViewShouldEndEditing?(textView) ?? true
  259. }
  260. func textViewDidBeginEditing(_ textView: UITextView) {
  261. IBTextViewDelegate?.textViewDidBeginEditing?(textView)
  262. }
  263. func textViewDidEndEditing(_ textView: UITextView) {
  264. IBTextViewDelegate?.textViewDidEndEditing?(textView)
  265. }
  266. func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
  267. return IBTextViewDelegate?.textView?(textView, shouldChangeTextIn: range, replacementText: text) ?? true
  268. }
  269. }