PageRenderTime 48ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/TeamTalk/Pods/HPGrowingTextView/class/HPGrowingTextView.m

https://gitlab.com/lisit1003/TTiOSClient
Objective C | 666 lines | 459 code | 138 blank | 69 comment | 58 complexity | 5b1d8430d64fbaded16c00a1c1a65b46 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 maxHeight;
  38. @synthesize minHeight;
  39. @synthesize font;
  40. @synthesize textColor;
  41. @synthesize textAlignment;
  42. @synthesize selectedRange;
  43. @synthesize editable;
  44. @synthesize dataDetectorTypes;
  45. @synthesize animateHeightChange;
  46. @synthesize animationDuration;
  47. @synthesize returnKeyType;
  48. @dynamic placeholder;
  49. @dynamic placeholderColor;
  50. // having initwithcoder allows us to use HPGrowingTextView in a Nib. -- aob, 9/2011
  51. - (id)initWithCoder:(NSCoder *)aDecoder
  52. {
  53. if ((self = [super initWithCoder:aDecoder])) {
  54. [self commonInitialiser];
  55. }
  56. return self;
  57. }
  58. - (id)initWithFrame:(CGRect)frame {
  59. if ((self = [super initWithFrame:frame])) {
  60. [self commonInitialiser];
  61. }
  62. return self;
  63. }
  64. #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
  65. - (id)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer {
  66. if ((self = [super initWithFrame:frame])) {
  67. [self commonInitialiser:textContainer];
  68. }
  69. return self;
  70. }
  71. -(void)commonInitialiser {
  72. [self commonInitialiser:nil];
  73. }
  74. -(void)commonInitialiser:(NSTextContainer *)textContainer
  75. #else
  76. -(void)commonInitialiser
  77. #endif
  78. {
  79. // Initialization code
  80. CGRect r = self.frame;
  81. r.origin.y = 0;
  82. r.origin.x = 0;
  83. #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
  84. internalTextView = [[HPTextViewInternal alloc] initWithFrame:r textContainer:textContainer];
  85. #else
  86. internalTextView = [[HPTextViewInternal alloc] initWithFrame:r];
  87. #endif
  88. internalTextView.delegate = self;
  89. internalTextView.scrollEnabled = NO;
  90. internalTextView.font = [UIFont fontWithName:@"Helvetica" size:13];
  91. internalTextView.contentInset = UIEdgeInsetsZero;
  92. internalTextView.showsHorizontalScrollIndicator = NO;
  93. internalTextView.text = @"-";
  94. internalTextView.contentMode = UIViewContentModeRedraw;
  95. [self addSubview:internalTextView];
  96. minHeight = internalTextView.frame.size.height;
  97. minNumberOfLines = 1;
  98. animateHeightChange = YES;
  99. animationDuration = 0.1f;
  100. internalTextView.text = @"";
  101. [self setMaxNumberOfLines:3];
  102. [self setPlaceholderColor:[UIColor lightGrayColor]];
  103. internalTextView.displayPlaceHolder = YES;
  104. }
  105. -(CGSize)sizeThatFits:(CGSize)size
  106. {
  107. if (self.text.length == 0) {
  108. size.height = minHeight;
  109. }
  110. return size;
  111. }
  112. -(void)layoutSubviews
  113. {
  114. [super layoutSubviews];
  115. CGRect r = self.bounds;
  116. r.origin.y = 0;
  117. r.origin.x = contentInset.left;
  118. r.size.width -= contentInset.left + contentInset.right;
  119. internalTextView.frame = r;
  120. }
  121. -(void)setContentInset:(UIEdgeInsets)inset
  122. {
  123. contentInset = inset;
  124. CGRect r = self.frame;
  125. r.origin.y = inset.top - inset.bottom;
  126. r.origin.x = inset.left;
  127. r.size.width -= inset.left + inset.right;
  128. internalTextView.frame = r;
  129. [self setMaxNumberOfLines:maxNumberOfLines];
  130. [self setMinNumberOfLines:minNumberOfLines];
  131. }
  132. -(UIEdgeInsets)contentInset
  133. {
  134. return contentInset;
  135. }
  136. -(void)setMaxNumberOfLines:(int)n
  137. {
  138. if(n == 0 && maxHeight > 0) return; // the user specified a maxHeight themselves.
  139. // Use internalTextView for height calculations, thanks to Gwynne <http://blog.darkrainfall.org/>
  140. NSString *saveText = internalTextView.text, *newText = @"-";
  141. internalTextView.delegate = nil;
  142. internalTextView.hidden = YES;
  143. for (int i = 1; i < n; ++i)
  144. newText = [newText stringByAppendingString:@"\n|W|"];
  145. internalTextView.text = newText;
  146. maxHeight = [self measureHeight];
  147. internalTextView.text = saveText;
  148. internalTextView.hidden = NO;
  149. internalTextView.delegate = self;
  150. [self sizeToFit];
  151. maxNumberOfLines = n;
  152. }
  153. -(int)maxNumberOfLines
  154. {
  155. return maxNumberOfLines;
  156. }
  157. - (void)setMaxHeight:(int)height
  158. {
  159. maxHeight = height;
  160. maxNumberOfLines = 0;
  161. }
  162. -(void)setMinNumberOfLines:(int)m
  163. {
  164. if(m == 0 && minHeight > 0) return; // the user specified a minHeight themselves.
  165. // Use internalTextView for height calculations, thanks to Gwynne <http://blog.darkrainfall.org/>
  166. NSString *saveText = internalTextView.text, *newText = @"-";
  167. internalTextView.delegate = nil;
  168. internalTextView.hidden = YES;
  169. for (int i = 1; i < m; ++i)
  170. newText = [newText stringByAppendingString:@"\n|W|"];
  171. internalTextView.text = newText;
  172. minHeight = [self measureHeight];
  173. internalTextView.text = saveText;
  174. internalTextView.hidden = NO;
  175. internalTextView.delegate = self;
  176. [self sizeToFit];
  177. minNumberOfLines = m;
  178. }
  179. -(int)minNumberOfLines
  180. {
  181. return minNumberOfLines;
  182. }
  183. - (void)setMinHeight:(int)height
  184. {
  185. minHeight = height;
  186. minNumberOfLines = 0;
  187. }
  188. - (NSString *)placeholder
  189. {
  190. return internalTextView.placeholder;
  191. }
  192. - (void)setPlaceholder:(NSString *)placeholder
  193. {
  194. [internalTextView setPlaceholder:placeholder];
  195. [internalTextView setNeedsDisplay];
  196. }
  197. - (UIColor *)placeholderColor
  198. {
  199. return internalTextView.placeholderColor;
  200. }
  201. - (void)setPlaceholderColor:(UIColor *)placeholderColor
  202. {
  203. [internalTextView setPlaceholderColor:placeholderColor];
  204. }
  205. - (void)textViewDidChange:(UITextView *)textView
  206. {
  207. [self refreshHeight];
  208. }
  209. - (void)refreshHeight
  210. {
  211. //size of content, so we can set the frame of self
  212. NSInteger newSizeH = [self measureHeight];
  213. if (newSizeH < minHeight || !internalTextView.hasText) {
  214. newSizeH = minHeight; //not smalles than minHeight
  215. }
  216. else if (maxHeight && newSizeH > maxHeight) {
  217. newSizeH = maxHeight; // not taller than maxHeight
  218. }
  219. if (internalTextView.frame.size.height != newSizeH)
  220. {
  221. // if our new height is greater than the maxHeight
  222. // sets not set the height or move things
  223. // around and enable scrolling
  224. if (newSizeH >= maxHeight)
  225. {
  226. if(!internalTextView.scrollEnabled){
  227. internalTextView.scrollEnabled = YES;
  228. [internalTextView flashScrollIndicators];
  229. }
  230. } else {
  231. internalTextView.scrollEnabled = NO;
  232. }
  233. // [fixed] Pasting too much text into the view failed to fire the height change,
  234. // thanks to Gwynne <http://blog.darkrainfall.org/>
  235. if (newSizeH <= maxHeight)
  236. {
  237. if(animateHeightChange) {
  238. if ([UIView resolveClassMethod:@selector(animateWithDuration:animations:)]) {
  239. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000
  240. [UIView animateWithDuration:animationDuration
  241. delay:0
  242. options:(UIViewAnimationOptionAllowUserInteraction|
  243. UIViewAnimationOptionBeginFromCurrentState)
  244. animations:^(void) {
  245. [self resizeTextView:newSizeH];
  246. }
  247. completion:^(BOOL finished) {
  248. if ([delegate respondsToSelector:@selector(growingTextView:didChangeHeight:)]) {
  249. [delegate growingTextView:self didChangeHeight:newSizeH];
  250. }
  251. }];
  252. #endif
  253. } else {
  254. [UIView beginAnimations:@"" context:nil];
  255. [UIView setAnimationDuration:animationDuration];
  256. [UIView setAnimationDelegate:self];
  257. [UIView setAnimationDidStopSelector:@selector(growDidStop)];
  258. [UIView setAnimationBeginsFromCurrentState:YES];
  259. [self resizeTextView:newSizeH];
  260. [UIView commitAnimations];
  261. }
  262. } else {
  263. [self resizeTextView:newSizeH];
  264. // [fixed] The growingTextView:didChangeHeight: delegate method was not called at all when not animating height changes.
  265. // thanks to Gwynne <http://blog.darkrainfall.org/>
  266. if ([delegate respondsToSelector:@selector(growingTextView:didChangeHeight:)]) {
  267. [delegate growingTextView:self didChangeHeight:newSizeH];
  268. }
  269. }
  270. }
  271. }
  272. // Display (or not) the placeholder string
  273. BOOL wasDisplayingPlaceholder = internalTextView.displayPlaceHolder;
  274. internalTextView.displayPlaceHolder = self.internalTextView.text.length == 0;
  275. if (wasDisplayingPlaceholder != internalTextView.displayPlaceHolder) {
  276. [internalTextView setNeedsDisplay];
  277. }
  278. // scroll to caret (needed on iOS7)
  279. if ([self respondsToSelector:@selector(snapshotViewAfterScreenUpdates:)])
  280. {
  281. [self performSelector:@selector(resetScrollPositionForIOS7) withObject:nil afterDelay:0.1f];
  282. }
  283. // Tell the delegate that the text view changed
  284. if ([delegate respondsToSelector:@selector(growingTextViewDidChange:)]) {
  285. [delegate growingTextViewDidChange:self];
  286. }
  287. }
  288. // Code from apple developer forum - @Steve Krulewitz, @Mark Marszal, @Eric Silverberg
  289. - (CGFloat)measureHeight
  290. {
  291. if ([self respondsToSelector:@selector(snapshotViewAfterScreenUpdates:)])
  292. {
  293. return ceilf([self.internalTextView sizeThatFits:self.internalTextView.frame.size].height);
  294. }
  295. else {
  296. return self.internalTextView.contentSize.height;
  297. }
  298. }
  299. - (void)resetScrollPositionForIOS7
  300. {
  301. CGRect r = [internalTextView caretRectForPosition:internalTextView.selectedTextRange.end];
  302. CGFloat caretY = MAX(r.origin.y - internalTextView.frame.size.height + r.size.height + 8, 0);
  303. if (internalTextView.contentOffset.y < caretY && r.origin.y != INFINITY)
  304. internalTextView.contentOffset = CGPointMake(0, caretY);
  305. }
  306. -(void)resizeTextView:(NSInteger)newSizeH
  307. {
  308. if ([delegate respondsToSelector:@selector(growingTextView:willChangeHeight:)]) {
  309. [delegate growingTextView:self willChangeHeight:newSizeH];
  310. }
  311. CGRect internalTextViewFrame = self.frame;
  312. internalTextViewFrame.size.height = newSizeH; // + padding
  313. self.frame = internalTextViewFrame;
  314. internalTextViewFrame.origin.y = contentInset.top - contentInset.bottom;
  315. internalTextViewFrame.origin.x = contentInset.left;
  316. if(!CGRectEqualToRect(internalTextView.frame, internalTextViewFrame)) internalTextView.frame = internalTextViewFrame;
  317. }
  318. - (void)growDidStop
  319. {
  320. // scroll to caret (needed on iOS7)
  321. if ([self respondsToSelector:@selector(snapshotViewAfterScreenUpdates:)])
  322. {
  323. [self resetScrollPositionForIOS7];
  324. }
  325. if ([delegate respondsToSelector:@selector(growingTextView:didChangeHeight:)]) {
  326. [delegate growingTextView:self didChangeHeight:self.frame.size.height];
  327. }
  328. }
  329. -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
  330. {
  331. [internalTextView becomeFirstResponder];
  332. }
  333. - (BOOL)becomeFirstResponder
  334. {
  335. [super becomeFirstResponder];
  336. return [self.internalTextView becomeFirstResponder];
  337. }
  338. -(BOOL)resignFirstResponder
  339. {
  340. [super resignFirstResponder];
  341. return [internalTextView resignFirstResponder];
  342. }
  343. -(BOOL)isFirstResponder
  344. {
  345. return [self.internalTextView isFirstResponder];
  346. }
  347. ///////////////////////////////////////////////////////////////////////////////////////////////////
  348. #pragma mark UITextView properties
  349. ///////////////////////////////////////////////////////////////////////////////////////////////////
  350. -(void)setText:(NSString *)newText
  351. {
  352. internalTextView.text = newText;
  353. // include this line to analyze the height of the textview.
  354. // fix from Ankit Thakur
  355. [self performSelector:@selector(textViewDidChange:) withObject:internalTextView];
  356. }
  357. -(NSString*) text
  358. {
  359. return internalTextView.text;
  360. }
  361. ///////////////////////////////////////////////////////////////////////////////////////////////////
  362. -(void)setFont:(UIFont *)afont
  363. {
  364. internalTextView.font= afont;
  365. [self setMaxNumberOfLines:maxNumberOfLines];
  366. [self setMinNumberOfLines:minNumberOfLines];
  367. }
  368. -(UIFont *)font
  369. {
  370. return internalTextView.font;
  371. }
  372. ///////////////////////////////////////////////////////////////////////////////////////////////////
  373. -(void)setTextColor:(UIColor *)color
  374. {
  375. internalTextView.textColor = color;
  376. }
  377. -(UIColor*)textColor{
  378. return internalTextView.textColor;
  379. }
  380. ///////////////////////////////////////////////////////////////////////////////////////////////////
  381. -(void)setBackgroundColor:(UIColor *)backgroundColor
  382. {
  383. [super setBackgroundColor:backgroundColor];
  384. internalTextView.backgroundColor = backgroundColor;
  385. }
  386. -(UIColor*)backgroundColor
  387. {
  388. return internalTextView.backgroundColor;
  389. }
  390. ///////////////////////////////////////////////////////////////////////////////////////////////////
  391. -(void)setTextAlignment:(NSTextAlignment)aligment
  392. {
  393. internalTextView.textAlignment = aligment;
  394. }
  395. -(NSTextAlignment)textAlignment
  396. {
  397. return internalTextView.textAlignment;
  398. }
  399. ///////////////////////////////////////////////////////////////////////////////////////////////////
  400. -(void)setSelectedRange:(NSRange)range
  401. {
  402. internalTextView.selectedRange = range;
  403. }
  404. -(NSRange)selectedRange
  405. {
  406. return internalTextView.selectedRange;
  407. }
  408. ///////////////////////////////////////////////////////////////////////////////////////////////////
  409. - (void)setIsScrollable:(BOOL)isScrollable
  410. {
  411. internalTextView.scrollEnabled = isScrollable;
  412. }
  413. - (BOOL)isScrollable
  414. {
  415. return internalTextView.scrollEnabled;
  416. }
  417. ///////////////////////////////////////////////////////////////////////////////////////////////////
  418. -(void)setEditable:(BOOL)beditable
  419. {
  420. internalTextView.editable = beditable;
  421. }
  422. -(BOOL)isEditable
  423. {
  424. return internalTextView.editable;
  425. }
  426. ///////////////////////////////////////////////////////////////////////////////////////////////////
  427. -(void)setReturnKeyType:(UIReturnKeyType)keyType
  428. {
  429. internalTextView.returnKeyType = keyType;
  430. }
  431. -(UIReturnKeyType)returnKeyType
  432. {
  433. return internalTextView.returnKeyType;
  434. }
  435. ///////////////////////////////////////////////////////////////////////////////////////////////////
  436. - (void)setKeyboardType:(UIKeyboardType)keyType
  437. {
  438. internalTextView.keyboardType = keyType;
  439. }
  440. - (UIKeyboardType)keyboardType
  441. {
  442. return internalTextView.keyboardType;
  443. }
  444. ///////////////////////////////////////////////////////////////////////////////////////////////////
  445. - (void)setEnablesReturnKeyAutomatically:(BOOL)enablesReturnKeyAutomatically
  446. {
  447. internalTextView.enablesReturnKeyAutomatically = enablesReturnKeyAutomatically;
  448. }
  449. - (BOOL)enablesReturnKeyAutomatically
  450. {
  451. return internalTextView.enablesReturnKeyAutomatically;
  452. }
  453. ///////////////////////////////////////////////////////////////////////////////////////////////////
  454. -(void)setDataDetectorTypes:(UIDataDetectorTypes)datadetector
  455. {
  456. internalTextView.dataDetectorTypes = datadetector;
  457. }
  458. -(UIDataDetectorTypes)dataDetectorTypes
  459. {
  460. return internalTextView.dataDetectorTypes;
  461. }
  462. ///////////////////////////////////////////////////////////////////////////////////////////////////
  463. - (BOOL)hasText{
  464. return [internalTextView hasText];
  465. }
  466. - (void)scrollRangeToVisible:(NSRange)range
  467. {
  468. [internalTextView scrollRangeToVisible:range];
  469. }
  470. /////////////////////////////////////////////////////////////////////////////////////////////////////
  471. /////////////////////////////////////////////////////////////////////////////////////////////////////
  472. #pragma mark -
  473. #pragma mark UITextViewDelegate
  474. ///////////////////////////////////////////////////////////////////////////////////////////////////
  475. - (BOOL)textViewShouldBeginEditing:(UITextView *)textView {
  476. if ([delegate respondsToSelector:@selector(growingTextViewShouldBeginEditing:)]) {
  477. return [delegate growingTextViewShouldBeginEditing:self];
  478. } else {
  479. return YES;
  480. }
  481. }
  482. ///////////////////////////////////////////////////////////////////////////////////////////////////
  483. - (BOOL)textViewShouldEndEditing:(UITextView *)textView {
  484. if ([delegate respondsToSelector:@selector(growingTextViewShouldEndEditing:)]) {
  485. return [delegate growingTextViewShouldEndEditing:self];
  486. } else {
  487. return YES;
  488. }
  489. }
  490. ///////////////////////////////////////////////////////////////////////////////////////////////////
  491. - (void)textViewDidBeginEditing:(UITextView *)textView {
  492. if ([delegate respondsToSelector:@selector(growingTextViewDidBeginEditing:)]) {
  493. [delegate growingTextViewDidBeginEditing:self];
  494. }
  495. }
  496. ///////////////////////////////////////////////////////////////////////////////////////////////////
  497. - (void)textViewDidEndEditing:(UITextView *)textView {
  498. if ([delegate respondsToSelector:@selector(growingTextViewDidEndEditing:)]) {
  499. [delegate growingTextViewDidEndEditing:self];
  500. }
  501. }
  502. ///////////////////////////////////////////////////////////////////////////////////////////////////
  503. - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range
  504. replacementText:(NSString *)atext {
  505. //weird 1 pixel bug when clicking backspace when textView is empty
  506. if(![textView hasText] && [atext isEqualToString:@""]) return NO;
  507. //Added by bretdabaker: sometimes we want to handle this ourselves
  508. if ([delegate respondsToSelector:@selector(growingTextView:shouldChangeTextInRange:replacementText:)])
  509. return [delegate growingTextView:self shouldChangeTextInRange:range replacementText:atext];
  510. if ([atext isEqualToString:@"\n"]) {
  511. if ([delegate respondsToSelector:@selector(growingTextViewShouldReturn:)]) {
  512. if (![delegate performSelector:@selector(growingTextViewShouldReturn:) withObject:self]) {
  513. return YES;
  514. } else {
  515. [textView resignFirstResponder];
  516. return NO;
  517. }
  518. }
  519. }
  520. return YES;
  521. }
  522. ///////////////////////////////////////////////////////////////////////////////////////////////////
  523. - (void)textViewDidChangeSelection:(UITextView *)textView {
  524. if ([delegate respondsToSelector:@selector(growingTextViewDidChangeSelection:)]) {
  525. [delegate growingTextViewDidChangeSelection:self];
  526. }
  527. }
  528. @end