PageRenderTime 44ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/Classes/UVTextEditor.m

https://github.com/appsocial/uservoice-iphone-sdk
Objective C | 582 lines | 423 code | 110 blank | 49 comment | 72 complexity | 90cff88482d8943239d7596ba7e3b257 MD5 | raw file
  1. //
  2. // Copyright 2009 Facebook
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. #import "UVTextEditor.h"
  17. #import "UIFont+UVExtras.h"
  18. #import "UIView+UVExtras.h"
  19. #import <QuartzCore/QuartzCore.h>
  20. ///////////////////////////////////////////////////////////////////////////////////////////////////
  21. static CGFloat kPaddingX = 8;
  22. static CGFloat kPaddingY = 9;
  23. // XXXjoe This number is very sensitive - it is specifically calculated for precise word wrapping
  24. // with 15pt normal helvetica. If you change this number at all, UITextView may wrap the text
  25. // before or after the UVTextEditor expands or contracts its height to match. Obviously,
  26. // hard-coding this value here sucks, and I need to implement a solution that works for any font.
  27. static CGFloat kTextViewInset = 31;
  28. static const CGFloat kUITextViewVerticalPadding = 6;
  29. CGRect UVRectContract(CGRect rect, CGFloat dx, CGFloat dy) {
  30. return CGRectMake(rect.origin.x, rect.origin.y, rect.size.width - dx, rect.size.height - dy);
  31. }
  32. ///////////////////////////////////////////////////////////////////////////////////////////////////
  33. @interface UVTextView : UITextView {
  34. BOOL _autoresizesToText;
  35. BOOL _overflowed;
  36. }
  37. @property(nonatomic) BOOL autoresizesToText;
  38. @property(nonatomic) BOOL overflowed;
  39. @end
  40. @implementation UVTextView
  41. @synthesize autoresizesToText = _autoresizesToText, overflowed = _overflowed;
  42. - (void)setContentOffset:(CGPoint)offset animated:(BOOL)animated {
  43. if (_autoresizesToText) {
  44. if (!_overflowed) {
  45. // In autosizing mode, we don't ever allow the text view to scroll past zero
  46. // unless it has past its maximum number of lines
  47. [super setContentOffset:CGPointZero animated:animated];
  48. } else {
  49. // If there is an overflow, we force the text view to keep the cursor at the bottom of the
  50. // view.
  51. [super setContentOffset: CGPointMake(offset.x, self.contentSize.height - self.height)
  52. animated: animated];
  53. }
  54. } else {
  55. [super setContentOffset:offset animated:animated];
  56. }
  57. }
  58. @end
  59. ///////////////////////////////////////////////////////////////////////////////////////////////////
  60. @interface UVTextEditorInternal : NSObject <UITextViewDelegate, UITextFieldDelegate> {
  61. UVTextEditor* _textEditor;
  62. id<UVTextEditorDelegate> _delegate;
  63. BOOL _ignoreBeginAndEnd;
  64. }
  65. @property(nonatomic,assign) id<UVTextEditorDelegate> delegate;
  66. @property(nonatomic) BOOL ignoreBeginAndEnd;
  67. - (id)initWithTextEditor:(UVTextEditor*)textEditor;
  68. @end
  69. @implementation UVTextEditorInternal
  70. @synthesize delegate = _delegate, ignoreBeginAndEnd = _ignoreBeginAndEnd;
  71. ///////////////////////////////////////////////////////////////////////////////////////////////////
  72. // NSObject
  73. - (id)initWithTextEditor:(UVTextEditor*)textEditor {
  74. if (self = [super init]) {
  75. _textEditor = textEditor;
  76. _delegate = nil;
  77. _ignoreBeginAndEnd = NO;
  78. }
  79. return self;
  80. }
  81. - (void)dealloc {
  82. [super dealloc];
  83. }
  84. ///////////////////////////////////////////////////////////////////////////////////////////////////
  85. // UITextViewDelegate
  86. - (BOOL)textViewShouldBeginEditing:(UITextView *)textView {
  87. if (!_ignoreBeginAndEnd
  88. && [_delegate respondsToSelector:@selector(textEditorShouldBeginEditing:)]) {
  89. return [_delegate textEditorShouldBeginEditing:_textEditor];
  90. } else {
  91. return YES;
  92. }
  93. }
  94. - (BOOL)textViewShouldEndEditing:(UITextView *)textView {
  95. if (!_ignoreBeginAndEnd
  96. && [_delegate respondsToSelector:@selector(textEditorShouldEndEditing:)]) {
  97. return [_delegate textEditorShouldEndEditing:_textEditor];
  98. } else {
  99. return YES;
  100. }
  101. }
  102. - (void)textViewDidBeginEditing:(UITextView *)textView {
  103. if (!_ignoreBeginAndEnd) {
  104. [_textEditor performSelector:@selector(didBeginEditing)];
  105. if ([_delegate respondsToSelector:@selector(textEditorDidBeginEditing:)]) {
  106. [_delegate textEditorDidBeginEditing:_textEditor];
  107. }
  108. }
  109. }
  110. - (void)textViewDidEndEditing:(UITextView *)textView {
  111. if (!_ignoreBeginAndEnd) {
  112. [_textEditor performSelector:@selector(didEndEditing)];
  113. if ([_delegate respondsToSelector:@selector(textEditorDidEndEditing:)]) {
  114. [_delegate textEditorDidEndEditing:_textEditor];
  115. }
  116. }
  117. }
  118. - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range
  119. replacementText:(NSString *)text {
  120. if ([text isEqualToString:@"\n"]) {
  121. if ([_delegate respondsToSelector:@selector(textEditorShouldReturn:)]) {
  122. if (![_delegate performSelector:@selector(textEditorShouldReturn:) withObject:_textEditor]) {
  123. return NO;
  124. }
  125. }
  126. }
  127. if ([_delegate respondsToSelector:@selector(textEditor:shouldChangeTextInRange:replacementText:)]) {
  128. return [_delegate textEditor:_textEditor shouldChangeTextInRange:range replacementText:text];
  129. } else {
  130. return YES;
  131. }
  132. }
  133. - (void)textViewDidChange:(UITextView *)textView {
  134. [_textEditor performSelector:@selector(didChangeText:) withObject:NO];
  135. if ([_delegate respondsToSelector:@selector(textEditorDidChange:)]) {
  136. [_delegate textEditorDidChange:_textEditor];
  137. }
  138. }
  139. - (void)textViewDidChangeSelection:(UITextView *)textView {
  140. }
  141. ///////////////////////////////////////////////////////////////////////////////////////////////////
  142. // UITextFieldDelegate
  143. - (BOOL)textFieldShouldBeginEditing:(UITextField*)textField {
  144. if (!_ignoreBeginAndEnd && [_delegate respondsToSelector:@selector(textEditorShouldBeginEditing:)]) {
  145. return [_delegate textEditorShouldBeginEditing:_textEditor];
  146. } else {
  147. return YES;
  148. }
  149. }
  150. - (BOOL)textFieldShouldEndEditing:(UITextField*)textField {
  151. if (!_ignoreBeginAndEnd && [_delegate respondsToSelector:@selector(textEditorShouldEndEditing:)]) {
  152. return [_delegate textEditorShouldEndEditing:_textEditor];
  153. } else {
  154. return YES;
  155. }
  156. }
  157. - (void)textFieldDidBeginEditing:(UITextField*)textField {
  158. if (!_ignoreBeginAndEnd) {
  159. [_textEditor performSelector:@selector(didBeginEditing)];
  160. if ([_delegate respondsToSelector:@selector(textEditorDidBeginEditing:)]) {
  161. [_delegate textEditorDidBeginEditing:_textEditor];
  162. }
  163. }
  164. }
  165. - (void)textFieldDidEndEditing:(UITextField*)textField {
  166. if (!_ignoreBeginAndEnd) {
  167. [_textEditor performSelector:@selector(didEndEditing)];
  168. if ([_delegate respondsToSelector:@selector(textEditorDidEndEditing:)]) {
  169. [_delegate textEditorDidEndEditing:_textEditor];
  170. }
  171. }
  172. }
  173. - (BOOL)textField:(UITextField*)textField shouldChangeCharactersInRange:(NSRange)range
  174. replacementString:(NSString*)string {
  175. BOOL shouldChange = YES;
  176. if ([_delegate respondsToSelector:@selector(textEditor:shouldChangeTextInRange:replacementText:)]) {
  177. shouldChange = [_delegate textEditor:_textEditor shouldChangeTextInRange:range
  178. replacementText:string];
  179. }
  180. if (shouldChange) {
  181. [self performSelector:@selector(textViewDidChange:) withObject:nil afterDelay:0];
  182. }
  183. return shouldChange;
  184. }
  185. - (BOOL)textFieldShouldReturn:(UITextField *)textField {
  186. if ([_delegate respondsToSelector:@selector(textEditorShouldReturn:)]) {
  187. if (![_delegate performSelector:@selector(textEditorShouldReturn:) withObject:_textEditor]) {
  188. return NO;
  189. }
  190. }
  191. [_textEditor performSelector:@selector(didChangeText:) withObject:(id)YES];
  192. if ([_delegate respondsToSelector:@selector(textEditorDidChange:)]) {
  193. [_delegate textEditorDidChange:_textEditor];
  194. }
  195. return YES;
  196. }
  197. @end
  198. ///////////////////////////////////////////////////////////////////////////////////////////////////
  199. @implementation UVTextEditor
  200. @synthesize delegate = _delegate, minNumberOfLines = _minNumberOfLines,
  201. maxNumberOfLines = _maxNumberOfLines, editing = _editing,
  202. autoresizesToText = _autoresizesToText, showsExtraLine= _showsExtraLine;
  203. ///////////////////////////////////////////////////////////////////////////////////////////////////
  204. // private
  205. - (UIResponder*)activeTextField {
  206. if (_textView && !_textView.hidden) {
  207. return _textView;
  208. } else {
  209. return _textField;
  210. }
  211. }
  212. - (void)createTextView {
  213. NSLog(@"Create textview in UVTextEditor");
  214. if (!_textView) {
  215. _textView = [[UVTextView alloc] init];
  216. _textView.delegate = _internal;
  217. _textView.editable = YES;
  218. _textView.backgroundColor = [UIColor clearColor];
  219. _textView.scrollsToTop = NO;
  220. _textView.showsHorizontalScrollIndicator = NO;
  221. // UITextViews have extra padding on the top and bottom that we don't want, so we force
  222. // the content to take up slightly more space. This allows us to mimic the padding of the
  223. // UITextLabel control.
  224. _textView.contentInset = UIEdgeInsetsMake(-kUITextViewVerticalPadding, 0,
  225. -kUITextViewVerticalPadding, 0);
  226. _textView.font = _textField.font;
  227. _textView.autoresizesToText = _autoresizesToText;
  228. _textView.textColor = _textField.textColor;
  229. _textView.autocapitalizationType = _textField.autocapitalizationType;
  230. _textView.autocorrectionType = _textField.autocorrectionType;
  231. _textView.enablesReturnKeyAutomatically = _textField.enablesReturnKeyAutomatically;
  232. _textView.keyboardAppearance = _textField.keyboardAppearance;
  233. _textView.keyboardType = _textField.keyboardType;
  234. _textView.returnKeyType = _textField.returnKeyType;
  235. _textView.secureTextEntry = _textField.secureTextEntry;
  236. [self addSubview:_textView];
  237. }
  238. }
  239. - (CGFloat)heightThatFits:(BOOL*)overflowed numberOfLines:(NSInteger*)numberOfLines {
  240. CGFloat ttLineHeight = self.font.ttLineHeight;
  241. CGFloat minHeight = _minNumberOfLines * ttLineHeight;
  242. CGFloat maxHeight = _maxNumberOfLines * ttLineHeight;
  243. CGFloat maxWidth = self.width - kTextViewInset;
  244. NSString* text = _textField.hidden ? _textView.text : _textField.text;
  245. if (!text.length) {
  246. text = @"M";
  247. }
  248. CGSize textSize = [text sizeWithFont:self.font
  249. constrainedToSize:CGSizeMake(maxWidth, CGFLOAT_MAX)
  250. lineBreakMode:UILineBreakModeWordWrap];
  251. CGFloat newHeight = textSize.height;
  252. if ([text characterAtIndex:text.length-1] == 10) {
  253. newHeight += ttLineHeight;
  254. }
  255. if (_showsExtraLine) {
  256. newHeight += ttLineHeight;
  257. }
  258. if (overflowed) {
  259. *overflowed = maxHeight && newHeight > maxHeight;
  260. }
  261. if (numberOfLines) {
  262. *numberOfLines = floor(newHeight / ttLineHeight);
  263. }
  264. if (newHeight < minHeight) {
  265. newHeight = minHeight;
  266. }
  267. if (maxHeight && newHeight > maxHeight) {
  268. newHeight = maxHeight;
  269. }
  270. return newHeight + kPaddingY*2;
  271. }
  272. - (void)stopIgnoringBeginAndEnd {
  273. _internal.ignoreBeginAndEnd = NO;
  274. }
  275. - (void)constrainToText {
  276. NSInteger numberOfLines = 0;
  277. CGFloat oldHeight = self.height;
  278. CGFloat newHeight = [self heightThatFits:&_overflowed numberOfLines:&numberOfLines];
  279. CGFloat diff = newHeight - oldHeight;
  280. if (numberOfLines > 1 && !_textField.hidden) {
  281. [self createTextView];
  282. _textField.hidden = YES;
  283. _textView.hidden = NO;
  284. _textView.text = _textField.text;
  285. _internal.ignoreBeginAndEnd = YES;
  286. [_textView becomeFirstResponder];
  287. [self performSelector:@selector(stopIgnoringBeginAndEnd) withObject:nil afterDelay:0];
  288. } else if (numberOfLines == 1 && _textField.hidden) {
  289. _textField.hidden = NO;
  290. _textView.hidden = YES;
  291. _textField.text = _textView.text;
  292. _internal.ignoreBeginAndEnd = YES;
  293. [_textField becomeFirstResponder];
  294. [self performSelector:@selector(stopIgnoringBeginAndEnd) withObject:nil afterDelay:0];
  295. }
  296. _textView.overflowed = _overflowed;
  297. _textView.scrollEnabled = _overflowed;
  298. if (oldHeight && diff) {
  299. if ([_delegate respondsToSelector:@selector(textEditor:shouldResizeBy:)]) {
  300. if (![_delegate textEditor:self shouldResizeBy:diff]) {
  301. return;
  302. }
  303. }
  304. self.frame = UVRectContract(self.frame, 0, -diff);
  305. }
  306. }
  307. - (void)didBeginEditing {
  308. _editing = YES;
  309. }
  310. - (void)didEndEditing {
  311. _editing = NO;
  312. }
  313. - (void)didChangeText:(BOOL)insertReturn {
  314. if (insertReturn) {
  315. [self createTextView];
  316. _textField.hidden = YES;
  317. _textView.hidden = NO;
  318. _textView.text = [_textField.text stringByAppendingString:@"\n"];
  319. _internal.ignoreBeginAndEnd = YES;
  320. [_textView becomeFirstResponder];
  321. [self performSelector:@selector(stopIgnoringBeginAndEnd) withObject:nil afterDelay:0];
  322. }
  323. if (_autoresizesToText) {
  324. [self constrainToText];
  325. }
  326. }
  327. ///////////////////////////////////////////////////////////////////////////////////////////////////
  328. // NSObject
  329. - (id)initWithFrame:(CGRect)frame {
  330. if (self = [super initWithFrame:frame]) {
  331. _delegate = nil;
  332. _internal = [[UVTextEditorInternal alloc] initWithTextEditor:self];
  333. _textView = nil;
  334. _autoresizesToText = YES;
  335. _showsExtraLine = NO;
  336. _minNumberOfLines = 0;
  337. _maxNumberOfLines = 0;
  338. _editing = NO;
  339. _overflowed = NO;
  340. _textField = [[UITextField alloc] init];
  341. _textField.delegate = _internal;
  342. [self addSubview:_textField];
  343. }
  344. return self;
  345. }
  346. - (void)dealloc {
  347. [_internal release];
  348. [_textField release];
  349. [_textView release];
  350. [super dealloc];
  351. }
  352. ///////////////////////////////////////////////////////////////////////////////////////////////////
  353. // UIResponder
  354. - (BOOL)becomeFirstResponder {
  355. return [[self activeTextField] becomeFirstResponder];
  356. }
  357. - (BOOL)resignFirstResponder {
  358. return [[self activeTextField] resignFirstResponder];
  359. }
  360. ///////////////////////////////////////////////////////////////////////////////////////////////////
  361. // UIView
  362. - (void)layoutSubviews {
  363. CGRect frame = CGRectMake(0, 2, self.width-kPaddingX*2, self.height);
  364. _textView.frame = CGRectOffset(UVRectContract(frame, 0, 14), 0, 7);
  365. _textField.frame = CGRectOffset(UVRectContract(frame, 9, 14), 9, 7);
  366. //The rounded corner part, where you specify your view's corner radius:
  367. _textField.layer.cornerRadius = 10;
  368. _textField.clipsToBounds = YES;
  369. }
  370. - (CGSize)sizeThatFits:(CGSize)size {
  371. CGFloat height = [self heightThatFits:nil numberOfLines:nil];
  372. return CGSizeMake(size.width, height);
  373. }
  374. ///////////////////////////////////////////////////////////////////////////////////////////////////
  375. // UITextInputTraits
  376. - (UITextAutocapitalizationType)autocapitalizationType {
  377. return _textField.autocapitalizationType;
  378. }
  379. - (void)setAutocapitalizationType:(UITextAutocapitalizationType)autocapitalizationType {
  380. _textField.autocapitalizationType = autocapitalizationType;
  381. }
  382. - (UITextAutocorrectionType)autocorrectionType {
  383. return _textField.autocorrectionType;
  384. }
  385. - (void)setAutocorrectionType:(UITextAutocorrectionType)autocorrectionType {
  386. _textField.autocorrectionType = autocorrectionType;
  387. }
  388. - (BOOL)enablesReturnKeyAutomatically {
  389. return _textField.enablesReturnKeyAutomatically;
  390. }
  391. - (void)setEnablesReturnKeyAutomatically:(BOOL)enablesReturnKeyAutomatically {
  392. _textField.enablesReturnKeyAutomatically = enablesReturnKeyAutomatically;
  393. }
  394. - (UIKeyboardAppearance)keyboardAppearance {
  395. return _textField.keyboardAppearance;
  396. }
  397. - (void)setKeyboardAppearance:(UIKeyboardAppearance)keyboardAppearance {
  398. _textField.keyboardAppearance = keyboardAppearance;
  399. }
  400. - (UIKeyboardType)keyboardType {
  401. return _textField.keyboardType;
  402. }
  403. - (void)setKeyboardType:(UIKeyboardType)keyboardType {
  404. _textField.keyboardType = keyboardType;
  405. }
  406. - (UIReturnKeyType)returnKeyType {
  407. return _textField.returnKeyType;
  408. }
  409. - (void)setReturnKeyType:(UIReturnKeyType)returnKeyType {
  410. _textField.returnKeyType = returnKeyType;
  411. }
  412. - (BOOL)secureTextEntry {
  413. return _textField.secureTextEntry;
  414. }
  415. - (void)setSecureTextEntry:(BOOL)secureTextEntry {
  416. _textField.secureTextEntry = secureTextEntry;
  417. }
  418. ///////////////////////////////////////////////////////////////////////////////////////////////////
  419. // public
  420. - (void)setDelegate:(id<UVTextEditorDelegate>)delegate {
  421. _delegate = delegate;
  422. _internal.delegate = delegate;
  423. }
  424. - (NSString*)text {
  425. if (_textView && !_textView.hidden) {
  426. return _textView.text;
  427. } else {
  428. return _textField.text;
  429. }
  430. }
  431. - (void)setText:(NSString*)text {
  432. _textField.text = _textView.text = text;
  433. if (_autoresizesToText) {
  434. [self constrainToText];
  435. }
  436. }
  437. - (NSString*)placeholder {
  438. return _textField.placeholder;
  439. }
  440. - (void)setPlaceholder:(NSString*)placeholder {
  441. _textField.placeholder = placeholder;
  442. }
  443. - (void)setAutoresizesToText:(BOOL)autoresizesToText {
  444. _autoresizesToText = autoresizesToText;
  445. _textView.autoresizesToText = _autoresizesToText;
  446. }
  447. - (UIFont*)font {
  448. return _textField.font;
  449. }
  450. - (void)setFont:(UIFont*)font {
  451. _textField.font = font;
  452. }
  453. - (UIColor*)textColor {
  454. return _textField.textColor;
  455. }
  456. - (void)setTextColor:(UIColor*)textColor {
  457. _textField.textColor = textColor;
  458. }
  459. - (void)scrollContainerToCursor:(UIScrollView*)scrollView {
  460. if (_textView.hasText) {
  461. if (scrollView.contentSize.height > scrollView.height) {
  462. NSRange range = _textView.selectedRange;
  463. if (range.location == _textView.text.length) {
  464. [scrollView scrollRectToVisible:CGRectMake(0,scrollView.contentSize.height-1,1,1)
  465. animated:NO];
  466. }
  467. } else {
  468. [scrollView scrollRectToVisible:CGRectMake(0,0,1,1) animated:NO];
  469. }
  470. }
  471. }
  472. @end