PageRenderTime 2567ms CodeModel.GetById 0ms RepoModel.GetById 27ms app.codeStats 0ms

/SPHChatBubble/HPGrowingTextView.m

https://gitlab.com/lisit1003/SPHChatBubble
Objective C | 539 lines | 355 code | 122 blank | 62 comment | 40 complexity | 3e103903727513fd8e49a851a7f82807 MD5 | raw file
  1. //
  2. // HPTextView.m
  3. //
  4. // Created by Hans Pinckaers on 29-06-10.
  5. //
  6. // MIT License
  7. //
  8. // Copyright (c) 2011 Hans Pinckaers
  9. //
  10. // Permission is hereby granted, free of charge, to any person obtaining a copy
  11. // of this software and associated documentation files (the "Software"), to deal
  12. // in the Software without restriction, including without limitation the rights
  13. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  14. // copies of the Software, and to permit persons to whom the Software is
  15. // furnished to do so, subject to the following conditions:
  16. //
  17. // The above copyright notice and this permission notice shall be included in
  18. // all copies or substantial portions of the Software.
  19. //
  20. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  21. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  22. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  23. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  24. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  25. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  26. // THE SOFTWARE.
  27. #import "HPGrowingTextView.h"
  28. #import "HPTextViewInternal.h"
  29. @interface HPGrowingTextView(private)
  30. -(void)commonInitialiser;
  31. -(void)resizeTextView:(NSInteger)newSizeH;
  32. -(void)growDidStop;
  33. @end
  34. @implementation HPGrowingTextView
  35. @synthesize internalTextView;
  36. @synthesize delegate;
  37. @synthesize font;
  38. @synthesize textColor;
  39. @synthesize textAlignment;
  40. @synthesize selectedRange;
  41. @synthesize editable;
  42. @synthesize dataDetectorTypes;
  43. @synthesize animateHeightChange;
  44. @synthesize returnKeyType;
  45. // having initwithcoder allows us to use HPGrowingTextView in a Nib. -- aob, 9/2011
  46. - (id)initWithCoder:(NSCoder *)aDecoder
  47. {
  48. if ((self = [super initWithCoder:aDecoder])) {
  49. [self commonInitialiser];
  50. }
  51. return self;
  52. }
  53. - (id)initWithFrame:(CGRect)frame {
  54. if ((self = [super initWithFrame:frame])) {
  55. [self commonInitialiser];
  56. }
  57. return self;
  58. }
  59. -(void)commonInitialiser
  60. {
  61. // Initialization code
  62. CGRect r = self.frame;
  63. r.origin.y = 0;
  64. r.origin.x = 0;
  65. internalTextView = [[HPTextViewInternal alloc] initWithFrame:r];
  66. internalTextView.delegate = self;
  67. internalTextView.scrollEnabled = NO;
  68. internalTextView.font = [UIFont fontWithName:@"Helvetica" size:13];
  69. internalTextView.contentInset = UIEdgeInsetsZero;
  70. internalTextView.showsHorizontalScrollIndicator = NO;
  71. internalTextView.text = @"-";
  72. [self addSubview:internalTextView];
  73. minHeight = internalTextView.frame.size.height;
  74. minNumberOfLines = 1;
  75. animateHeightChange = YES;
  76. internalTextView.text = @"";
  77. [self setMaxNumberOfLines:3];
  78. }
  79. -(CGSize)sizeThatFits:(CGSize)size
  80. {
  81. if (self.text.length == 0) {
  82. size.height = minHeight;
  83. }
  84. return size;
  85. }
  86. -(void)layoutSubviews
  87. {
  88. [super layoutSubviews];
  89. CGRect r = self.bounds;
  90. r.origin.y = 0;
  91. r.origin.x = contentInset.left;
  92. r.size.width -= contentInset.left + contentInset.right;
  93. internalTextView.frame = r;
  94. }
  95. -(void)setContentInset:(UIEdgeInsets)inset
  96. {
  97. contentInset = inset;
  98. CGRect r = self.frame;
  99. r.origin.y = inset.top - inset.bottom;
  100. r.origin.x = inset.left;
  101. r.size.width -= inset.left + inset.right;
  102. internalTextView.frame = r;
  103. [self setMaxNumberOfLines:maxNumberOfLines];
  104. [self setMinNumberOfLines:minNumberOfLines];
  105. }
  106. -(UIEdgeInsets)contentInset
  107. {
  108. return contentInset;
  109. }
  110. -(void)setMaxNumberOfLines:(int)n
  111. {
  112. // Use internalTextView for height calculations, thanks to Gwynne <http://blog.darkrainfall.org/>
  113. NSString *saveText = internalTextView.text, *newText = @"-";
  114. internalTextView.delegate = nil;
  115. internalTextView.hidden = YES;
  116. for (int i = 1; i < n; ++i)
  117. newText = [newText stringByAppendingString:@"\n|W|"];
  118. internalTextView.text = newText;
  119. maxHeight = internalTextView.contentSize.height;
  120. internalTextView.text = saveText;
  121. internalTextView.hidden = NO;
  122. internalTextView.delegate = self;
  123. [self sizeToFit];
  124. maxNumberOfLines = n;
  125. }
  126. -(int)maxNumberOfLines
  127. {
  128. return maxNumberOfLines;
  129. }
  130. -(void)setMinNumberOfLines:(int)m
  131. {
  132. // Use internalTextView for height calculations, thanks to Gwynne <http://blog.darkrainfall.org/>
  133. NSString *saveText = internalTextView.text, *newText = @"-";
  134. internalTextView.delegate = nil;
  135. internalTextView.hidden = YES;
  136. for (int i = 1; i < m; ++i)
  137. newText = [newText stringByAppendingString:@"\n|W|"];
  138. internalTextView.text = newText;
  139. minHeight = internalTextView.contentSize.height;
  140. internalTextView.text = saveText;
  141. internalTextView.hidden = NO;
  142. internalTextView.delegate = self;
  143. [self sizeToFit];
  144. minNumberOfLines = m;
  145. }
  146. -(int)minNumberOfLines
  147. {
  148. return minNumberOfLines;
  149. }
  150. - (void)textViewDidChange:(UITextView *)textView
  151. {
  152. //size of content, so we can set the frame of self
  153. NSInteger newSizeH = internalTextView.contentSize.height;
  154. if(newSizeH < minHeight || !internalTextView.hasText) newSizeH = minHeight; //not smalles than minHeight
  155. if (internalTextView.frame.size.height > maxHeight) newSizeH = maxHeight; // not taller than maxHeight
  156. if (internalTextView.frame.size.height != newSizeH)
  157. {
  158. // [fixed] Pasting too much text into the view failed to fire the height change,
  159. // thanks to Gwynne <http://blog.darkrainfall.org/>
  160. if (newSizeH > maxHeight && internalTextView.frame.size.height <= maxHeight)
  161. {
  162. newSizeH = maxHeight;
  163. }
  164. if (newSizeH <= maxHeight)
  165. {
  166. if(animateHeightChange) {
  167. if ([UIView resolveClassMethod:@selector(animateWithDuration:animations:)]) {
  168. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000
  169. [UIView animateWithDuration:0.1f
  170. delay:0
  171. options:(UIViewAnimationOptionAllowUserInteraction|
  172. UIViewAnimationOptionBeginFromCurrentState)
  173. animations:^(void) {
  174. [self resizeTextView:newSizeH];
  175. }
  176. completion:^(BOOL finished) {
  177. if ([delegate respondsToSelector:@selector(growingTextView:didChangeHeight:)]) {
  178. [delegate growingTextView:self didChangeHeight:newSizeH];
  179. }
  180. }];
  181. #endif
  182. } else {
  183. [UIView beginAnimations:@"" context:nil];
  184. [UIView setAnimationDuration:0.1f];
  185. [UIView setAnimationDelegate:self];
  186. [UIView setAnimationDidStopSelector:@selector(growDidStop)];
  187. [UIView setAnimationBeginsFromCurrentState:YES];
  188. [self resizeTextView:newSizeH];
  189. [UIView commitAnimations];
  190. }
  191. } else {
  192. [self resizeTextView:newSizeH];
  193. // [fixed] The growingTextView:didChangeHeight: delegate method was not called at all when not animating height changes.
  194. // thanks to Gwynne <http://blog.darkrainfall.org/>
  195. if ([delegate respondsToSelector:@selector(growingTextView:didChangeHeight:)]) {
  196. [delegate growingTextView:self didChangeHeight:newSizeH];
  197. }
  198. }
  199. }
  200. // if our new height is greater than the maxHeight
  201. // sets not set the height or move things
  202. // around and enable scrolling
  203. if (newSizeH >= maxHeight)
  204. {
  205. if(!internalTextView.scrollEnabled){
  206. internalTextView.scrollEnabled = YES;
  207. [internalTextView flashScrollIndicators];
  208. }
  209. } else {
  210. internalTextView.scrollEnabled = NO;
  211. }
  212. }
  213. if ([delegate respondsToSelector:@selector(growingTextViewDidChange:)]) {
  214. [delegate growingTextViewDidChange:self];
  215. }
  216. }
  217. -(void)resizeTextView:(NSInteger)newSizeH
  218. {
  219. if ([delegate respondsToSelector:@selector(growingTextView:willChangeHeight:)]) {
  220. [delegate growingTextView:self willChangeHeight:newSizeH];
  221. }
  222. CGRect internalTextViewFrame = self.frame;
  223. internalTextViewFrame.size.height = newSizeH; // + padding
  224. self.frame = internalTextViewFrame;
  225. internalTextViewFrame.origin.y = contentInset.top - contentInset.bottom;
  226. internalTextViewFrame.origin.x = contentInset.left;
  227. internalTextViewFrame.size.width = internalTextView.contentSize.width;
  228. internalTextView.frame = internalTextViewFrame;
  229. }
  230. -(void)growDidStop
  231. {
  232. if ([delegate respondsToSelector:@selector(growingTextView:didChangeHeight:)]) {
  233. [delegate growingTextView:self didChangeHeight:self.frame.size.height];
  234. }
  235. }
  236. -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
  237. {
  238. [internalTextView becomeFirstResponder];
  239. }
  240. - (BOOL)becomeFirstResponder
  241. {
  242. [super becomeFirstResponder];
  243. return [self.internalTextView becomeFirstResponder];
  244. }
  245. -(BOOL)resignFirstResponder
  246. {
  247. [super resignFirstResponder];
  248. return [internalTextView resignFirstResponder];
  249. }
  250. -(BOOL)isFirstResponder
  251. {
  252. return [self.internalTextView isFirstResponder];
  253. }
  254. ///////////////////////////////////////////////////////////////////////////////////////////////////
  255. #pragma mark UITextView properties
  256. ///////////////////////////////////////////////////////////////////////////////////////////////////
  257. -(void)setText:(NSString *)newText
  258. {
  259. internalTextView.text = newText;
  260. // include this line to analyze the height of the textview.
  261. // fix from Ankit Thakur
  262. [self performSelector:@selector(textViewDidChange:) withObject:internalTextView];
  263. }
  264. -(NSString*) text
  265. {
  266. return internalTextView.text;
  267. }
  268. ///////////////////////////////////////////////////////////////////////////////////////////////////
  269. -(void)setFont:(UIFont *)afont
  270. {
  271. internalTextView.font= afont;
  272. [self setMaxNumberOfLines:maxNumberOfLines];
  273. [self setMinNumberOfLines:minNumberOfLines];
  274. }
  275. -(UIFont *)font
  276. {
  277. return internalTextView.font;
  278. }
  279. ///////////////////////////////////////////////////////////////////////////////////////////////////
  280. -(void)setTextColor:(UIColor *)color
  281. {
  282. internalTextView.textColor = color;
  283. }
  284. -(UIColor*)textColor{
  285. return internalTextView.textColor;
  286. }
  287. ///////////////////////////////////////////////////////////////////////////////////////////////////
  288. -(void)setBackgroundColor:(UIColor *)backgroundColor
  289. {
  290. [super setBackgroundColor:backgroundColor];
  291. internalTextView.backgroundColor = backgroundColor;
  292. }
  293. -(UIColor*)backgroundColor
  294. {
  295. return internalTextView.backgroundColor;
  296. }
  297. ///////////////////////////////////////////////////////////////////////////////////////////////////
  298. -(void)setTextAlignment:(UITextAlignment)aligment
  299. {
  300. internalTextView.textAlignment = aligment;
  301. }
  302. -(UITextAlignment)textAlignment
  303. {
  304. return internalTextView.textAlignment;
  305. }
  306. ///////////////////////////////////////////////////////////////////////////////////////////////////
  307. -(void)setSelectedRange:(NSRange)range
  308. {
  309. internalTextView.selectedRange = range;
  310. }
  311. -(NSRange)selectedRange
  312. {
  313. return internalTextView.selectedRange;
  314. }
  315. ///////////////////////////////////////////////////////////////////////////////////////////////////
  316. -(void)setEditable:(BOOL)beditable
  317. {
  318. internalTextView.editable = beditable;
  319. }
  320. -(BOOL)isEditable
  321. {
  322. return internalTextView.editable;
  323. }
  324. ///////////////////////////////////////////////////////////////////////////////////////////////////
  325. -(void)setReturnKeyType:(UIReturnKeyType)keyType
  326. {
  327. internalTextView.returnKeyType = keyType;
  328. }
  329. -(UIReturnKeyType)returnKeyType
  330. {
  331. return internalTextView.returnKeyType;
  332. }
  333. ///////////////////////////////////////////////////////////////////////////////////////////////////
  334. - (void)setEnablesReturnKeyAutomatically:(BOOL)enablesReturnKeyAutomatically
  335. {
  336. internalTextView.enablesReturnKeyAutomatically = enablesReturnKeyAutomatically;
  337. }
  338. - (BOOL)enablesReturnKeyAutomatically
  339. {
  340. return internalTextView.enablesReturnKeyAutomatically;
  341. }
  342. ///////////////////////////////////////////////////////////////////////////////////////////////////
  343. -(void)setDataDetectorTypes:(UIDataDetectorTypes)datadetector
  344. {
  345. internalTextView.dataDetectorTypes = datadetector;
  346. }
  347. -(UIDataDetectorTypes)dataDetectorTypes
  348. {
  349. return internalTextView.dataDetectorTypes;
  350. }
  351. ///////////////////////////////////////////////////////////////////////////////////////////////////
  352. - (BOOL)hasText{
  353. return [internalTextView hasText];
  354. }
  355. - (void)scrollRangeToVisible:(NSRange)range
  356. {
  357. [internalTextView scrollRangeToVisible:range];
  358. }
  359. /////////////////////////////////////////////////////////////////////////////////////////////////////
  360. /////////////////////////////////////////////////////////////////////////////////////////////////////
  361. #pragma mark -
  362. #pragma mark UITextViewDelegate
  363. ///////////////////////////////////////////////////////////////////////////////////////////////////
  364. - (BOOL)textViewShouldBeginEditing:(UITextView *)textView {
  365. if ([delegate respondsToSelector:@selector(growingTextViewShouldBeginEditing:)]) {
  366. return [delegate growingTextViewShouldBeginEditing:self];
  367. } else {
  368. return YES;
  369. }
  370. }
  371. ///////////////////////////////////////////////////////////////////////////////////////////////////
  372. - (BOOL)textViewShouldEndEditing:(UITextView *)textView {
  373. if ([delegate respondsToSelector:@selector(growingTextViewShouldEndEditing:)]) {
  374. return [delegate growingTextViewShouldEndEditing:self];
  375. } else {
  376. return YES;
  377. }
  378. }
  379. ///////////////////////////////////////////////////////////////////////////////////////////////////
  380. - (void)textViewDidBeginEditing:(UITextView *)textView {
  381. if ([delegate respondsToSelector:@selector(growingTextViewDidBeginEditing:)]) {
  382. [delegate growingTextViewDidBeginEditing:self];
  383. }
  384. }
  385. ///////////////////////////////////////////////////////////////////////////////////////////////////
  386. - (void)textViewDidEndEditing:(UITextView *)textView {
  387. if ([delegate respondsToSelector:@selector(growingTextViewDidEndEditing:)]) {
  388. [delegate growingTextViewDidEndEditing:self];
  389. }
  390. }
  391. ///////////////////////////////////////////////////////////////////////////////////////////////////
  392. - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range
  393. replacementText:(NSString *)atext {
  394. //weird 1 pixel bug when clicking backspace when textView is empty
  395. if(![textView hasText] && [atext isEqualToString:@""]) return NO;
  396. //Added by bretdabaker: sometimes we want to handle this ourselves
  397. if ([delegate respondsToSelector:@selector(growingTextView:shouldChangeTextInRange:replacementText:)])
  398. return [delegate growingTextView:self shouldChangeTextInRange:range replacementText:atext];
  399. if ([atext isEqualToString:@"\n"]) {
  400. if ([delegate respondsToSelector:@selector(growingTextViewShouldReturn:)]) {
  401. if (![delegate performSelector:@selector(growingTextViewShouldReturn:) withObject:self]) {
  402. return YES;
  403. } else {
  404. [textView resignFirstResponder];
  405. return NO;
  406. }
  407. }
  408. }
  409. return YES;
  410. }
  411. ///////////////////////////////////////////////////////////////////////////////////////////////////
  412. - (void)textViewDidChangeSelection:(UITextView *)textView {
  413. if ([delegate respondsToSelector:@selector(growingTextViewDidChangeSelection:)]) {
  414. [delegate growingTextViewDidChangeSelection:self];
  415. }
  416. }
  417. @end