UITextView+WZB.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. //
  2. // UITextView+WZB.m
  3. // WZBTextView-demo
  4. //
  5. // Created by normal on 2016/11/14.
  6. // Copyright © 2016年 onecloud.ltd. All rights reserved.
  7. //
  8. #import "UITextView+WZB.h"
  9. #import <objc/runtime.h>
  10. // 占位文字
  11. static const void *WZBPlaceholderViewKey = &WZBPlaceholderViewKey;
  12. // 占位文字颜色
  13. static const void *WZBPlaceholderColorKey = &WZBPlaceholderColorKey;
  14. // 最大高度
  15. static const void *WZBTextViewMaxHeightKey = &WZBTextViewMaxHeightKey;
  16. // 最小高度
  17. static const void *WZBTextViewMinHeightKey = &WZBTextViewMinHeightKey;
  18. // 高度变化的block
  19. static const void *WZBTextViewHeightDidChangedBlockKey = &WZBTextViewHeightDidChangedBlockKey;
  20. // 存储添加的图片
  21. static const void *WZBTextViewImageArrayKey = &WZBTextViewImageArrayKey;
  22. // 存储最后一次改变高度后的值
  23. static const void *WZBTextViewLastHeightKey = &WZBTextViewLastHeightKey;
  24. @interface UITextView ()
  25. // 存储添加的图片
  26. @property (nonatomic, strong) NSMutableArray *wzb_imageArray;
  27. // 存储最后一次改变高度后的值
  28. @property (nonatomic, assign) CGFloat lastHeight;
  29. @end
  30. @implementation UITextView (WZB)
  31. #pragma mark - Swizzle Dealloc
  32. + (void)load {
  33. // 交换dealoc
  34. Method dealoc = class_getInstanceMethod(self.class, NSSelectorFromString(@"dealloc"));
  35. Method myDealloc = class_getInstanceMethod(self.class, @selector(myDealloc));
  36. method_exchangeImplementations(dealoc, myDealloc);
  37. }
  38. - (void)myDealloc {
  39. // 移除监听
  40. [[NSNotificationCenter defaultCenter] removeObserver:self];
  41. UITextView *placeholderView = objc_getAssociatedObject(self, WZBPlaceholderViewKey);
  42. // 如果有值才去调用,这步很重要
  43. if (placeholderView) {
  44. NSArray *propertys = @[@"frame", @"bounds", @"font", @"text", @"textAlignment", @"textContainerInset"];
  45. for (NSString *property in propertys) {
  46. @try {
  47. [self removeObserver:self forKeyPath:property];
  48. } @catch (NSException *exception) {}
  49. }
  50. }
  51. [self myDealloc];
  52. }
  53. #pragma mark - set && get
  54. - (UITextView *)wzb_placeholderView {
  55. // 为了让占位文字和textView的实际文字位置能够完全一致,这里用UITextView
  56. UITextView *placeholderView = objc_getAssociatedObject(self, WZBPlaceholderViewKey);
  57. if (!placeholderView) {
  58. // 初始化数组
  59. self.wzb_imageArray = [NSMutableArray array];
  60. placeholderView = [[UITextView alloc] init];
  61. // 动态添加属性的本质是: 让对象的某个属性与值产生关联
  62. objc_setAssociatedObject(self, WZBPlaceholderViewKey, placeholderView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  63. placeholderView = placeholderView;
  64. // 设置基本属性
  65. placeholderView.scrollEnabled = placeholderView.userInteractionEnabled = NO;
  66. // self.scrollEnabled = placeholderView.scrollEnabled = placeholderView.showsHorizontalScrollIndicator = placeholderView.showsVerticalScrollIndicator = placeholderView.userInteractionEnabled = NO;
  67. placeholderView.textColor = [UIColor lightGrayColor];
  68. placeholderView.backgroundColor = [UIColor clearColor];
  69. [self refreshPlaceholderView];
  70. [self addSubview:placeholderView];
  71. // 监听文字改变
  72. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textViewTextChange) name:UITextViewTextDidChangeNotification object:self];
  73. // 这些属性改变时,都要作出一定的改变,尽管已经监听了TextDidChange的通知,也要监听text属性,因为通知监听不到setText:
  74. NSArray *propertys = @[@"frame", @"bounds", @"font", @"text", @"textAlignment", @"textContainerInset"];
  75. // 监听属性
  76. for (NSString *property in propertys) {
  77. [self addObserver:self forKeyPath:property options:NSKeyValueObservingOptionNew context:nil];
  78. }
  79. }
  80. return placeholderView;
  81. }
  82. - (void)setWzb_placeholder:(NSString *)placeholder
  83. {
  84. // 为placeholder赋值
  85. [self wzb_placeholderView].text = placeholder;
  86. }
  87. - (NSString *)wzb_placeholder
  88. {
  89. // 如果有placeholder值才去调用,这步很重要
  90. if (self.placeholderExist) {
  91. return [self wzb_placeholderView].text;
  92. }
  93. return nil;
  94. }
  95. - (void)setWzb_placeholderColor:(UIColor *)wzb_placeholderColor
  96. {
  97. // 如果有placeholder值才去调用,这步很重要
  98. if (!self.placeholderExist) {
  99. MOLogV(@"请先设置placeholder值!");
  100. } else {
  101. self.wzb_placeholderView.textColor = wzb_placeholderColor;
  102. // 动态添加属性的本质是: 让对象的某个属性与值产生关联
  103. objc_setAssociatedObject(self, WZBPlaceholderColorKey, wzb_placeholderColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  104. }
  105. }
  106. - (UIColor *)wzb_placeholderColor
  107. {
  108. return objc_getAssociatedObject(self, WZBPlaceholderColorKey);
  109. }
  110. - (void)setWzb_maxHeight:(CGFloat)wzb_maxHeight
  111. {
  112. CGFloat max = wzb_maxHeight;
  113. // 如果传入的最大高度小于textView本身的高度,则让最大高度等于本身高度
  114. if (wzb_maxHeight < self.frame.size.height) {
  115. max = self.frame.size.height;
  116. }
  117. objc_setAssociatedObject(self, WZBTextViewMaxHeightKey, [NSString stringWithFormat:@"%lf", max], OBJC_ASSOCIATION_COPY_NONATOMIC);
  118. }
  119. - (CGFloat)wzb_maxHeight
  120. {
  121. return [objc_getAssociatedObject(self, WZBTextViewMaxHeightKey) doubleValue];
  122. }
  123. - (void)setWzb_minHeight:(CGFloat)wzb_minHeight
  124. {
  125. objc_setAssociatedObject(self, WZBTextViewMinHeightKey, [NSString stringWithFormat:@"%lf", wzb_minHeight], OBJC_ASSOCIATION_COPY_NONATOMIC);
  126. }
  127. - (CGFloat)wzb_minHeight
  128. {
  129. return [objc_getAssociatedObject(self, WZBTextViewMinHeightKey) doubleValue];
  130. }
  131. - (void)setWzb_textViewHeightDidChanged:(textViewHeightDidChangedBlock)wzb_textViewHeightDidChanged
  132. {
  133. objc_setAssociatedObject(self, WZBTextViewHeightDidChangedBlockKey, wzb_textViewHeightDidChanged, OBJC_ASSOCIATION_COPY_NONATOMIC);
  134. }
  135. - (textViewHeightDidChangedBlock)wzb_textViewHeightDidChanged
  136. {
  137. void(^textViewHeightDidChanged)(CGFloat currentHeight) = objc_getAssociatedObject(self, WZBTextViewHeightDidChangedBlockKey);
  138. return textViewHeightDidChanged;
  139. }
  140. - (NSArray *)wzb_getImages
  141. {
  142. return self.wzb_imageArray;
  143. }
  144. - (void)setLastHeight:(CGFloat)lastHeight {
  145. objc_setAssociatedObject(self, WZBTextViewLastHeightKey, [NSString stringWithFormat:@"%lf", lastHeight], OBJC_ASSOCIATION_COPY_NONATOMIC);
  146. }
  147. - (CGFloat)lastHeight {
  148. return [objc_getAssociatedObject(self, WZBTextViewLastHeightKey) doubleValue];
  149. }
  150. - (void)setWzb_imageArray:(NSMutableArray *)wzb_imageArray {
  151. objc_setAssociatedObject(self, WZBTextViewImageArrayKey, wzb_imageArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  152. }
  153. - (NSMutableArray *)wzb_imageArray {
  154. return objc_getAssociatedObject(self, WZBTextViewImageArrayKey);
  155. }
  156. - (void)wzb_autoHeightWithMaxHeight:(CGFloat)maxHeight
  157. {
  158. [self wzb_autoHeightWithMaxHeight:maxHeight textViewHeightDidChanged:nil];
  159. }
  160. // 是否启用自动高度,默认为NO
  161. static bool autoHeight = NO;
  162. - (void)wzb_autoHeightWithMaxHeight:(CGFloat)maxHeight textViewHeightDidChanged:(textViewHeightDidChangedBlock)textViewHeightDidChanged
  163. {
  164. autoHeight = YES;
  165. [self wzb_placeholderView];
  166. self.wzb_maxHeight = maxHeight;
  167. if (textViewHeightDidChanged) self.wzb_textViewHeightDidChanged = textViewHeightDidChanged;
  168. }
  169. #pragma mark - addImage
  170. /* 添加一张图片 */
  171. - (void)wzb_addImage:(UIImage *)image
  172. {
  173. [self wzb_addImage:image size:CGSizeZero];
  174. }
  175. /* 添加一张图片 image:要添加的图片 size:图片大小 */
  176. - (void)wzb_addImage:(UIImage *)image size:(CGSize)size
  177. {
  178. [self wzb_insertImage:image size:size index:self.attributedText.length > 0 ? self.attributedText.length : 0];
  179. }
  180. /* 插入一张图片 image:要添加的图片 size:图片大小 index:插入的位置 */
  181. - (void)wzb_insertImage:(UIImage *)image size:(CGSize)size index:(NSInteger)index
  182. {
  183. [self wzb_addImage:image size:size index:index multiple:-1];
  184. }
  185. /* 添加一张图片 image:要添加的图片 multiple:放大/缩小的倍数 */
  186. - (void)wzb_addImage:(UIImage *)image multiple:(CGFloat)multiple
  187. {
  188. [self wzb_addImage:image size:CGSizeZero index:self.attributedText.length > 0 ? self.attributedText.length : 0 multiple:multiple];
  189. }
  190. /* 插入一张图片 image:要添加的图片 multiple:放大/缩小的倍数 index:插入的位置 */
  191. - (void)wzb_insertImage:(UIImage *)image multiple:(CGFloat)multiple index:(NSInteger)index
  192. {
  193. [self wzb_addImage:image size:CGSizeZero index:index multiple:multiple];
  194. }
  195. /* 插入一张图片 image:要添加的图片 size:图片大小 index:插入的位置 multiple:放大/缩小的倍数 */
  196. - (void)wzb_addImage:(UIImage *)image size:(CGSize)size index:(NSInteger)index multiple:(CGFloat)multiple {
  197. if (image) [self.wzb_imageArray addObject:image];
  198. NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText];
  199. NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
  200. textAttachment.image = image;
  201. CGRect bounds = textAttachment.bounds;
  202. if (!CGSizeEqualToSize(size, CGSizeZero)) {
  203. bounds.size = size;
  204. textAttachment.bounds = bounds;
  205. } else if (multiple <= 0) {
  206. CGFloat oldWidth = textAttachment.image.size.width;
  207. CGFloat scaleFactor = oldWidth / (self.frame.size.width - 10);
  208. textAttachment.image = [UIImage imageWithCGImage:textAttachment.image.CGImage scale:scaleFactor orientation:UIImageOrientationUp];
  209. } else {
  210. bounds.size = image.size;
  211. textAttachment.bounds = bounds;
  212. }
  213. NSAttributedString *attrStringWithImage = [NSAttributedString attributedStringWithAttachment:textAttachment];
  214. [attributedString replaceCharactersInRange:NSMakeRange(index, 0) withAttributedString:attrStringWithImage];
  215. self.attributedText = attributedString;
  216. [self textViewTextChange];
  217. [self refreshPlaceholderView];
  218. }
  219. #pragma mark - KVO监听属性改变
  220. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  221. [self refreshPlaceholderView];
  222. if ([keyPath isEqualToString:@"text"]) [self textViewTextChange];
  223. }
  224. // 刷新PlaceholderView
  225. - (void)refreshPlaceholderView {
  226. UITextView *placeholderView = objc_getAssociatedObject(self, WZBPlaceholderViewKey);
  227. // 如果有值才去调用,这步很重要
  228. if (placeholderView) {
  229. self.wzb_placeholderView.frame = self.bounds;
  230. if (self.wzb_maxHeight < self.bounds.size.height) self.wzb_maxHeight = self.bounds.size.height;
  231. self.wzb_placeholderView.font = self.font;
  232. self.wzb_placeholderView.textAlignment = self.textAlignment;
  233. self.wzb_placeholderView.textContainerInset = self.textContainerInset;
  234. self.wzb_placeholderView.hidden = (self.text.length > 0 && self.text);
  235. }
  236. }
  237. // 处理文字改变
  238. - (void)textViewTextChange {
  239. UITextView *placeholderView = objc_getAssociatedObject(self, WZBPlaceholderViewKey);
  240. // 如果有值才去调用,这步很重要
  241. if (placeholderView) {
  242. self.wzb_placeholderView.hidden = (self.text.length > 0 && self.text);
  243. }
  244. // 如果没有启用自动高度,不执行以下方法
  245. if (!autoHeight) return;
  246. if (self.wzb_maxHeight >= self.bounds.size.height) {
  247. // 计算高度
  248. NSInteger currentHeight = ceil([self sizeThatFits:CGSizeMake(self.bounds.size.width, MAXFLOAT)].height);
  249. // 如果高度有变化,调用block
  250. if (currentHeight != self.lastHeight) {
  251. // 是否可以滚动
  252. self.scrollEnabled = currentHeight >= self.wzb_maxHeight;
  253. CGFloat currentTextViewHeight = currentHeight >= self.wzb_maxHeight ? self.wzb_maxHeight : currentHeight;
  254. // 改变textView的高度
  255. if (currentTextViewHeight >= self.wzb_minHeight) {
  256. CGRect frame = self.frame;
  257. frame.size.height = currentTextViewHeight;
  258. self.frame = frame;
  259. // 调用block
  260. if (self.wzb_textViewHeightDidChanged) self.wzb_textViewHeightDidChanged(currentTextViewHeight);
  261. // 记录当前高度
  262. self.lastHeight = currentTextViewHeight;
  263. }
  264. }
  265. }
  266. if (!self.isFirstResponder) [self becomeFirstResponder];
  267. }
  268. // 判断是否有placeholder值,这步很重要
  269. - (BOOL)placeholderExist {
  270. // 获取对应属性的值
  271. UITextView *placeholderView = objc_getAssociatedObject(self, WZBPlaceholderViewKey);
  272. // 如果有placeholder值
  273. if (placeholderView) return YES;
  274. return NO;
  275. }
  276. #pragma mark - 过期
  277. - (NSString *)placeholder
  278. {
  279. return self.wzb_placeholder;
  280. }
  281. - (void)setPlaceholder:(NSString *)placeholder
  282. {
  283. self.wzb_placeholder = placeholder;
  284. }
  285. - (UIColor *)placeholderColor
  286. {
  287. return self.wzb_placeholderColor;
  288. }
  289. - (void)setPlaceholderColor:(UIColor *)placeholderColor
  290. {
  291. self.wzb_placeholderColor = placeholderColor;
  292. }
  293. - (void)setMaxHeight:(CGFloat)maxHeight
  294. {
  295. self.wzb_maxHeight = maxHeight;
  296. }
  297. - (CGFloat)maxHeight
  298. {
  299. return self.maxHeight;
  300. }
  301. - (void)setMinHeight:(CGFloat)minHeight
  302. {
  303. self.wzb_minHeight = minHeight;
  304. }
  305. - (CGFloat)minHeight
  306. {
  307. return self.wzb_minHeight;
  308. }
  309. - (void)setTextViewHeightDidChanged:(textViewHeightDidChangedBlock)textViewHeightDidChanged
  310. {
  311. self.wzb_textViewHeightDidChanged = textViewHeightDidChanged;
  312. }
  313. - (textViewHeightDidChangedBlock)textViewHeightDidChanged
  314. {
  315. return self.wzb_textViewHeightDidChanged;
  316. }
  317. - (NSArray *)getImages
  318. {
  319. return self.wzb_getImages;
  320. }
  321. - (void)autoHeightWithMaxHeight:(CGFloat)maxHeight
  322. {
  323. [self wzb_autoHeightWithMaxHeight:maxHeight];
  324. }
  325. - (void)autoHeightWithMaxHeight:(CGFloat)maxHeight textViewHeightDidChanged:(void(^)(CGFloat currentTextViewHeight))textViewHeightDidChanged
  326. {
  327. [self wzb_autoHeightWithMaxHeight:maxHeight textViewHeightDidChanged:textViewHeightDidChanged];
  328. }
  329. - (void)addImage:(UIImage *)image
  330. {
  331. [self wzb_addImage:image];
  332. }
  333. - (void)addImage:(UIImage *)image size:(CGSize)size
  334. {
  335. [self wzb_addImage:image size:size];
  336. }
  337. - (void)insertImage:(UIImage *)image size:(CGSize)size index:(NSInteger)index
  338. {
  339. [self wzb_insertImage:image size:size index:index];
  340. }
  341. - (void)addImage:(UIImage *)image multiple:(CGFloat)multiple
  342. {
  343. [self wzb_addImage:image multiple:multiple];
  344. }
  345. - (void)insertImage:(UIImage *)image multiple:(CGFloat)multiple index:(NSInteger)index
  346. {
  347. [self wzb_insertImage:image multiple:multiple index:index];
  348. }
  349. @end