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

/src/Three20UI/Sources/TTTextEditor.m

https://github.com/GetMoPix/three20
Objective C | 486 lines | 283 code | 123 blank | 80 comment | 36 complexity | f698dcef1789c1a99690b2fc85b9aaa2 MD5 | raw file
  1. //
  2. // Copyright 2009-2011 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 "Three20UI/TTTextEditor.h"
  17. // UI
  18. #import "Three20UI/TTTextEditorDelegate.h"
  19. #import "Three20UI/UIViewAdditions.h"
  20. #import "Three20Style/UIFontAdditions.h"
  21. // UI (private)
  22. #import "Three20UI/private/TTTextView.h"
  23. #import "Three20UI/private/TTTextEditorInternal.h"
  24. // Style
  25. #import "Three20Style/TTGlobalStyle.h"
  26. #import "Three20Style/TTDefaultStyleSheet.h"
  27. // Core
  28. #import "Three20Core/TTCorePreprocessorMacros.h"
  29. #import "Three20Core/TTGlobalCoreRects.h"
  30. static const CGFloat kPaddingX = 8.0f;
  31. static const CGFloat kPaddingY = 9.0f;
  32. // XXXjoe This number is very sensitive - it is specifically calculated for precise word wrapping
  33. // with 15pt normal helvetica. If you change this number at all, UITextView may wrap the text
  34. // before or after the TTTextEditor expands or contracts its height to match. Obviously,
  35. // hard-coding this value here sucks, and I need to implement a solution that works for any font.
  36. static const CGFloat kTextViewInset = 31.0f;
  37. static const CGFloat kUITextViewVerticalPadding = 6.0f;
  38. ///////////////////////////////////////////////////////////////////////////////////////////////////
  39. ///////////////////////////////////////////////////////////////////////////////////////////////////
  40. ///////////////////////////////////////////////////////////////////////////////////////////////////
  41. @implementation TTTextEditor
  42. @synthesize minNumberOfLines = _minNumberOfLines;
  43. @synthesize maxNumberOfLines = _maxNumberOfLines;
  44. @synthesize editing = _editing;
  45. @synthesize autoresizesToText = _autoresizesToText;
  46. @synthesize showsExtraLine = _showsExtraLine;
  47. @synthesize delegate = _delegate;
  48. ///////////////////////////////////////////////////////////////////////////////////////////////////
  49. - (id)initWithFrame:(CGRect)frame {
  50. self = [super initWithFrame:frame];
  51. if (self) {
  52. _internal = [[TTTextEditorInternal alloc] initWithTextEditor:self];
  53. _autoresizesToText = YES;
  54. _textField = [[UITextField alloc] init];
  55. _textField.delegate = _internal;
  56. [self addSubview:_textField];
  57. }
  58. return self;
  59. }
  60. ///////////////////////////////////////////////////////////////////////////////////////////////////
  61. - (void)dealloc {
  62. TT_RELEASE_SAFELY(_internal);
  63. TT_RELEASE_SAFELY(_textField);
  64. TT_RELEASE_SAFELY(_textView);
  65. [super dealloc];
  66. }
  67. ///////////////////////////////////////////////////////////////////////////////////////////////////
  68. ///////////////////////////////////////////////////////////////////////////////////////////////////
  69. #pragma mark -
  70. #pragma mark Private
  71. ///////////////////////////////////////////////////////////////////////////////////////////////////
  72. - (UIResponder*)activeTextField {
  73. if (_textView && !_textView.hidden) {
  74. return _textView;
  75. } else {
  76. return _textField;
  77. }
  78. }
  79. ///////////////////////////////////////////////////////////////////////////////////////////////////
  80. - (void)createTextView {
  81. if (!_textView) {
  82. _textView = [[TTTextView alloc] init];
  83. _textView.delegate = _internal;
  84. _textView.editable = YES;
  85. _textView.backgroundColor = [UIColor clearColor];
  86. _textView.scrollsToTop = NO;
  87. _textView.showsHorizontalScrollIndicator = NO;
  88. // UITextViews have extra padding on the top and bottom that we don't want, so we force
  89. // the content to take up slightly more space. This allows us to mimic the padding of the
  90. // UITextLabel control.
  91. _textView.contentInset = UIEdgeInsetsMake(
  92. -kUITextViewVerticalPadding, 0,
  93. -kUITextViewVerticalPadding, 0);
  94. _textView.font = _textField.font;
  95. _textView.autoresizesToText = _autoresizesToText;
  96. _textView.textColor = _textField.textColor;
  97. _textView.autocapitalizationType = _textField.autocapitalizationType;
  98. _textView.autocorrectionType = _textField.autocorrectionType;
  99. _textView.enablesReturnKeyAutomatically = _textField.enablesReturnKeyAutomatically;
  100. _textView.keyboardAppearance = _textField.keyboardAppearance;
  101. _textView.keyboardType = _textField.keyboardType;
  102. _textView.returnKeyType = _textField.returnKeyType;
  103. _textView.secureTextEntry = _textField.secureTextEntry;
  104. [self addSubview:_textView];
  105. }
  106. }
  107. ///////////////////////////////////////////////////////////////////////////////////////////////////
  108. - (CGFloat)heightThatFits:(BOOL*)overflowed numberOfLines:(NSInteger*)numberOfLines {
  109. CGFloat ttLineHeight = self.font.ttLineHeight;
  110. CGFloat minHeight = _minNumberOfLines * ttLineHeight;
  111. CGFloat maxHeight = _maxNumberOfLines * ttLineHeight;
  112. CGFloat maxWidth = self.width - kTextViewInset;
  113. NSString* text = _textField.hidden ? _textView.text : _textField.text;
  114. if (!text.length) {
  115. text = @"M";
  116. }
  117. CGSize textSize = [text sizeWithFont:self.font
  118. constrainedToSize:CGSizeMake(maxWidth, CGFLOAT_MAX)
  119. lineBreakMode:UILineBreakModeWordWrap];
  120. CGFloat newHeight = textSize.height;
  121. if ([text characterAtIndex:text.length-1] == 10) {
  122. newHeight += ttLineHeight;
  123. }
  124. if (_showsExtraLine) {
  125. newHeight += ttLineHeight;
  126. }
  127. if (overflowed) {
  128. *overflowed = maxHeight && newHeight > maxHeight;
  129. }
  130. if (numberOfLines) {
  131. *numberOfLines = floor(newHeight / ttLineHeight);
  132. }
  133. if (newHeight < minHeight) {
  134. newHeight = minHeight;
  135. }
  136. if (maxHeight && newHeight > maxHeight) {
  137. newHeight = maxHeight;
  138. }
  139. return newHeight + kPaddingY*2;
  140. }
  141. ///////////////////////////////////////////////////////////////////////////////////////////////////
  142. - (void)stopIgnoringBeginAndEnd {
  143. _internal.ignoreBeginAndEnd = NO;
  144. }
  145. ///////////////////////////////////////////////////////////////////////////////////////////////////
  146. - (void)constrainToText {
  147. NSInteger numberOfLines = 0;
  148. CGFloat oldHeight = self.height;
  149. CGFloat newHeight = [self heightThatFits:&_overflowed numberOfLines:&numberOfLines];
  150. CGFloat diff = newHeight - oldHeight;
  151. if (numberOfLines > 1 && !_textField.hidden) {
  152. [self createTextView];
  153. _textField.hidden = YES;
  154. _textView.hidden = NO;
  155. _textView.text = _textField.text;
  156. _internal.ignoreBeginAndEnd = YES;
  157. [_textView becomeFirstResponder];
  158. [self performSelector:@selector(stopIgnoringBeginAndEnd) withObject:nil afterDelay:0];
  159. } else if (numberOfLines == 1 && _textField.hidden) {
  160. _textField.hidden = NO;
  161. _textView.hidden = YES;
  162. _textField.text = _textView.text;
  163. _internal.ignoreBeginAndEnd = YES;
  164. [_textField becomeFirstResponder];
  165. [self performSelector:@selector(stopIgnoringBeginAndEnd) withObject:nil afterDelay:0];
  166. }
  167. _textView.overflowed = _overflowed;
  168. _textView.scrollEnabled = _overflowed;
  169. if (oldHeight && diff) {
  170. if ([_delegate respondsToSelector:@selector(textEditor:shouldResizeBy:)]) {
  171. if (![_delegate textEditor:self shouldResizeBy:diff]) {
  172. return;
  173. }
  174. }
  175. self.frame = TTRectContract(self.frame, 0, -diff);
  176. }
  177. }
  178. ///////////////////////////////////////////////////////////////////////////////////////////////////
  179. - (void)didBeginEditing {
  180. _editing = YES;
  181. }
  182. ///////////////////////////////////////////////////////////////////////////////////////////////////
  183. - (void)didEndEditing {
  184. _editing = NO;
  185. }
  186. ///////////////////////////////////////////////////////////////////////////////////////////////////
  187. - (void)didChangeText:(BOOL)insertReturn {
  188. if (insertReturn) {
  189. [self createTextView];
  190. _textField.hidden = YES;
  191. _textView.hidden = NO;
  192. _textView.text = [_textField.text stringByAppendingString:@"\n"];
  193. _internal.ignoreBeginAndEnd = YES;
  194. [_textView becomeFirstResponder];
  195. [self performSelector:@selector(stopIgnoringBeginAndEnd) withObject:nil afterDelay:0];
  196. }
  197. if (_autoresizesToText) {
  198. [self constrainToText];
  199. }
  200. }
  201. ///////////////////////////////////////////////////////////////////////////////////////////////////
  202. ///////////////////////////////////////////////////////////////////////////////////////////////////
  203. #pragma mark -
  204. #pragma mark UIResponder
  205. ///////////////////////////////////////////////////////////////////////////////////////////////////
  206. - (BOOL)isFirstResponder {
  207. return [[self activeTextField] isFirstResponder];
  208. }
  209. ///////////////////////////////////////////////////////////////////////////////////////////////////
  210. - (BOOL)canBecomeFirstResponder {
  211. return [[self activeTextField] canBecomeFirstResponder];
  212. }
  213. ///////////////////////////////////////////////////////////////////////////////////////////////////
  214. - (BOOL)becomeFirstResponder {
  215. return [[self activeTextField] becomeFirstResponder];
  216. }
  217. ///////////////////////////////////////////////////////////////////////////////////////////////////
  218. - (BOOL)resignFirstResponder {
  219. return [[self activeTextField] resignFirstResponder];
  220. }
  221. ///////////////////////////////////////////////////////////////////////////////////////////////////
  222. ///////////////////////////////////////////////////////////////////////////////////////////////////
  223. #pragma mark -
  224. #pragma mark UIView
  225. ///////////////////////////////////////////////////////////////////////////////////////////////////
  226. - (void)layoutSubviews {
  227. CGRect frame = CGRectMake(0, 2, self.width-kPaddingX*2, self.height);
  228. _textView.frame = CGRectOffset(TTRectContract(frame, 0, 14), 0, 7);
  229. _textField.frame = CGRectOffset(TTRectContract(frame, 9, 14), 9, 7);
  230. }
  231. ///////////////////////////////////////////////////////////////////////////////////////////////////
  232. - (CGSize)sizeThatFits:(CGSize)size {
  233. CGFloat height = [self heightThatFits:nil numberOfLines:nil];
  234. return CGSizeMake(size.width, height);
  235. }
  236. ///////////////////////////////////////////////////////////////////////////////////////////////////
  237. ///////////////////////////////////////////////////////////////////////////////////////////////////
  238. #pragma mark -
  239. #pragma mark UITextInputTraits
  240. ///////////////////////////////////////////////////////////////////////////////////////////////////
  241. - (UITextAutocapitalizationType)autocapitalizationType {
  242. return _textField.autocapitalizationType;
  243. }
  244. ///////////////////////////////////////////////////////////////////////////////////////////////////
  245. - (void)setAutocapitalizationType:(UITextAutocapitalizationType)autocapitalizationType {
  246. _textField.autocapitalizationType = autocapitalizationType;
  247. }
  248. ///////////////////////////////////////////////////////////////////////////////////////////////////
  249. - (UITextAutocorrectionType)autocorrectionType {
  250. return _textField.autocorrectionType;
  251. }
  252. ///////////////////////////////////////////////////////////////////////////////////////////////////
  253. - (void)setAutocorrectionType:(UITextAutocorrectionType)autocorrectionType {
  254. _textField.autocorrectionType = autocorrectionType;
  255. }
  256. ///////////////////////////////////////////////////////////////////////////////////////////////////
  257. - (BOOL)enablesReturnKeyAutomatically {
  258. return _textField.enablesReturnKeyAutomatically;
  259. }
  260. ///////////////////////////////////////////////////////////////////////////////////////////////////
  261. - (void)setEnablesReturnKeyAutomatically:(BOOL)enablesReturnKeyAutomatically {
  262. _textField.enablesReturnKeyAutomatically = enablesReturnKeyAutomatically;
  263. }
  264. ///////////////////////////////////////////////////////////////////////////////////////////////////
  265. - (UIKeyboardAppearance)keyboardAppearance {
  266. return _textField.keyboardAppearance;
  267. }
  268. ///////////////////////////////////////////////////////////////////////////////////////////////////
  269. - (void)setKeyboardAppearance:(UIKeyboardAppearance)keyboardAppearance {
  270. _textField.keyboardAppearance = keyboardAppearance;
  271. }
  272. ///////////////////////////////////////////////////////////////////////////////////////////////////
  273. - (UIKeyboardType)keyboardType {
  274. return _textField.keyboardType;
  275. }
  276. ///////////////////////////////////////////////////////////////////////////////////////////////////
  277. - (void)setKeyboardType:(UIKeyboardType)keyboardType {
  278. _textField.keyboardType = keyboardType;
  279. }
  280. ///////////////////////////////////////////////////////////////////////////////////////////////////
  281. - (UIReturnKeyType)returnKeyType {
  282. return _textField.returnKeyType;
  283. }
  284. ///////////////////////////////////////////////////////////////////////////////////////////////////
  285. - (void)setReturnKeyType:(UIReturnKeyType)returnKeyType {
  286. _textField.returnKeyType = returnKeyType;
  287. }
  288. ///////////////////////////////////////////////////////////////////////////////////////////////////
  289. - (BOOL)secureTextEntry {
  290. return _textField.secureTextEntry;
  291. }
  292. ///////////////////////////////////////////////////////////////////////////////////////////////////
  293. - (void)setSecureTextEntry:(BOOL)secureTextEntry {
  294. _textField.secureTextEntry = secureTextEntry;
  295. }
  296. ///////////////////////////////////////////////////////////////////////////////////////////////////
  297. ///////////////////////////////////////////////////////////////////////////////////////////////////
  298. #pragma mark -
  299. #pragma mark Public
  300. ///////////////////////////////////////////////////////////////////////////////////////////////////
  301. - (void)setDelegate:(id<TTTextEditorDelegate>)delegate {
  302. _delegate = delegate;
  303. _internal.delegate = delegate;
  304. }
  305. ///////////////////////////////////////////////////////////////////////////////////////////////////
  306. - (NSString*)text {
  307. if (_textView && !_textView.hidden) {
  308. return _textView.text;
  309. } else {
  310. return _textField.text;
  311. }
  312. }
  313. ///////////////////////////////////////////////////////////////////////////////////////////////////
  314. - (void)setText:(NSString*)text {
  315. _textField.text = _textView.text = text;
  316. if (_autoresizesToText) {
  317. [self constrainToText];
  318. }
  319. }
  320. ///////////////////////////////////////////////////////////////////////////////////////////////////
  321. - (NSString*)placeholder {
  322. return _textField.placeholder;
  323. }
  324. ///////////////////////////////////////////////////////////////////////////////////////////////////
  325. - (void)setPlaceholder:(NSString*)placeholder {
  326. _textField.placeholder = placeholder;
  327. }
  328. ///////////////////////////////////////////////////////////////////////////////////////////////////
  329. - (void)setAutoresizesToText:(BOOL)autoresizesToText {
  330. _autoresizesToText = autoresizesToText;
  331. _textView.autoresizesToText = _autoresizesToText;
  332. }
  333. ///////////////////////////////////////////////////////////////////////////////////////////////////
  334. - (UIFont*)font {
  335. return _textField.font;
  336. }
  337. ///////////////////////////////////////////////////////////////////////////////////////////////////
  338. - (void)setFont:(UIFont*)font {
  339. _textField.font = font;
  340. }
  341. ///////////////////////////////////////////////////////////////////////////////////////////////////
  342. - (UIColor*)textColor {
  343. return _textField.textColor;
  344. }
  345. ///////////////////////////////////////////////////////////////////////////////////////////////////
  346. - (void)setTextColor:(UIColor*)textColor {
  347. _textField.textColor = textColor;
  348. }
  349. ///////////////////////////////////////////////////////////////////////////////////////////////////
  350. - (void)scrollContainerToCursor:(UIScrollView*)scrollView {
  351. if (_textView.hasText) {
  352. if (scrollView.contentSize.height > scrollView.height) {
  353. NSRange range = _textView.selectedRange;
  354. if (range.location == _textView.text.length) {
  355. [scrollView scrollRectToVisible:CGRectMake(0,scrollView.contentSize.height-1,1,1)
  356. animated:NO];
  357. }
  358. } else {
  359. [scrollView scrollRectToVisible:CGRectMake(0,0,1,1) animated:NO];
  360. }
  361. }
  362. }
  363. @end