PageRenderTime 44ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Three20UI/Sources/TTPickerTextField.m

https://github.com/GetMoPix/three20
Objective C | 519 lines | 326 code | 125 blank | 68 comment | 64 complexity | a3877dab79989e7ae62dd79152170374 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/TTPickerTextField.h"
  17. // UI
  18. #import "Three20UI/TTPickerTextFieldDelegate.h"
  19. #import "Three20UI/TTTableViewDataSource.h"
  20. #import "Three20UI/TTPickerViewCell.h"
  21. #import "Three20UI/UIViewAdditions.h"
  22. #import "Three20Style/UIFontAdditions.h"
  23. // UINavigator
  24. #import "Three20UINavigator/TTGlobalNavigatorMetrics.h"
  25. // Core
  26. #import "Three20Core/TTCorePreprocessorMacros.h"
  27. static NSString* kEmpty = @" ";
  28. static NSString* kSelected = @"`";
  29. static const CGFloat kCellPaddingY = 3.0f;
  30. static const CGFloat kPaddingX = 8.0f;
  31. static const CGFloat kSpacingY = 6.0f;
  32. static const CGFloat kPaddingRatio = 1.75f;
  33. static const CGFloat kClearButtonSize = 38.0f;
  34. static const CGFloat kMinCursorWidth = 50.0f;
  35. ///////////////////////////////////////////////////////////////////////////////////////////////////
  36. ///////////////////////////////////////////////////////////////////////////////////////////////////
  37. ///////////////////////////////////////////////////////////////////////////////////////////////////
  38. @implementation TTPickerTextField
  39. @synthesize cellViews = _cellViews;
  40. @synthesize selectedCell = _selectedCell;
  41. @synthesize lineCount = _lineCount;
  42. ///////////////////////////////////////////////////////////////////////////////////////////////////
  43. - (id)initWithFrame:(CGRect)frame {
  44. self = [super initWithFrame:frame];
  45. if (self) {
  46. _cellViews = [[NSMutableArray alloc] init];
  47. _lineCount = 1;
  48. _cursorOrigin = CGPointZero;
  49. self.text = kEmpty;
  50. self.contentVerticalAlignment = UIControlContentVerticalAlignmentTop;
  51. self.clearButtonMode = UITextFieldViewModeNever;
  52. self.returnKeyType = UIReturnKeyDone;
  53. self.enablesReturnKeyAutomatically = NO;
  54. [self addTarget:self action:@selector(textFieldDidEndEditing)
  55. forControlEvents:UIControlEventEditingDidEnd];
  56. }
  57. return self;
  58. }
  59. ///////////////////////////////////////////////////////////////////////////////////////////////////
  60. - (void)dealloc {
  61. TT_RELEASE_SAFELY(_cellViews);
  62. [super dealloc];
  63. }
  64. ///////////////////////////////////////////////////////////////////////////////////////////////////
  65. - (CGFloat)layoutCells {
  66. CGFloat fontHeight = self.font.ttLineHeight;
  67. CGFloat lineIncrement = fontHeight + kCellPaddingY*2 + kSpacingY;
  68. CGFloat marginY = floor(fontHeight/kPaddingRatio);
  69. CGFloat marginLeft = self.leftView
  70. ? kPaddingX + self.leftView.width + kPaddingX/2
  71. : kPaddingX;
  72. CGFloat marginRight = kPaddingX + (self.rightView ? kClearButtonSize : 0);
  73. _cursorOrigin.x = marginLeft;
  74. _cursorOrigin.y = marginY;
  75. _lineCount = 1;
  76. if (self.width) {
  77. for (TTPickerViewCell* cell in _cellViews) {
  78. [cell sizeToFit];
  79. CGFloat lineWidth = _cursorOrigin.x + cell.frame.size.width + marginRight;
  80. if (lineWidth >= self.width) {
  81. _cursorOrigin.x = marginLeft;
  82. _cursorOrigin.y += lineIncrement;
  83. ++_lineCount;
  84. }
  85. cell.frame = CGRectMake(_cursorOrigin.x, _cursorOrigin.y-kCellPaddingY,
  86. cell.width, cell.height);
  87. _cursorOrigin.x += cell.frame.size.width + kPaddingX;
  88. }
  89. CGFloat remainingWidth = self.width - (_cursorOrigin.x + marginRight);
  90. if (remainingWidth < kMinCursorWidth) {
  91. _cursorOrigin.x = marginLeft;
  92. _cursorOrigin.y += lineIncrement;
  93. ++_lineCount;
  94. }
  95. }
  96. return _cursorOrigin.y + fontHeight + marginY;
  97. }
  98. ///////////////////////////////////////////////////////////////////////////////////////////////////
  99. - (void)updateHeight {
  100. CGFloat previousHeight = self.height;
  101. CGFloat newHeight = [self layoutCells];
  102. if (previousHeight && newHeight != previousHeight) {
  103. self.height = newHeight;
  104. [self setNeedsDisplay];
  105. if ([self.delegate respondsToSelector:@selector(textFieldDidResize:)]) {
  106. [(id)self.delegate textFieldDidResize:self];
  107. }
  108. [self scrollToVisibleLine:YES];
  109. }
  110. }
  111. ///////////////////////////////////////////////////////////////////////////////////////////////////
  112. - (CGFloat)marginY {
  113. return floor(self.font.ttLineHeight/kPaddingRatio);
  114. }
  115. ///////////////////////////////////////////////////////////////////////////////////////////////////
  116. - (CGFloat)topOfLine:(int)lineNumber {
  117. if (lineNumber == 0) {
  118. return 0;
  119. } else {
  120. CGFloat ttLineHeight = self.font.ttLineHeight;
  121. CGFloat lineSpacing = kCellPaddingY*2 + kSpacingY;
  122. CGFloat marginY = floor(ttLineHeight/kPaddingRatio);
  123. CGFloat lineTop = marginY + ttLineHeight*lineNumber + lineSpacing*lineNumber;
  124. return lineTop - lineSpacing;
  125. }
  126. }
  127. ///////////////////////////////////////////////////////////////////////////////////////////////////
  128. - (CGFloat)centerOfLine:(int)lineNumber {
  129. CGFloat lineTop = [self topOfLine:lineNumber];
  130. CGFloat ttLineHeight = self.font.ttLineHeight + kCellPaddingY*2 + kSpacingY;
  131. return lineTop + floor(ttLineHeight/2);
  132. }
  133. ///////////////////////////////////////////////////////////////////////////////////////////////////
  134. - (CGFloat)heightWithLines:(int)lines {
  135. CGFloat ttLineHeight = self.font.ttLineHeight;
  136. CGFloat lineSpacing = kCellPaddingY*2 + kSpacingY;
  137. CGFloat marginY = floor(ttLineHeight/kPaddingRatio);
  138. return marginY + ttLineHeight*lines + lineSpacing*(lines ? lines-1 : 0) + marginY;
  139. }
  140. ///////////////////////////////////////////////////////////////////////////////////////////////////
  141. - (void)selectLastCell {
  142. self.selectedCell = [_cellViews objectAtIndex:_cellViews.count-1];
  143. }
  144. ///////////////////////////////////////////////////////////////////////////////////////////////////
  145. - (NSString*)labelForObject:(id)object {
  146. NSString* label = nil;
  147. if ([_dataSource respondsToSelector:@selector(tableView:labelForObject:)]) {
  148. label = [_dataSource tableView:_tableView labelForObject:object];
  149. }
  150. return label ? label : [NSString stringWithFormat:@"%@", object];
  151. }
  152. ///////////////////////////////////////////////////////////////////////////////////////////////////
  153. //////////////////////////////////////////////////////////////////////////////////////////////////
  154. #pragma mark -
  155. #pragma mark UIView
  156. ///////////////////////////////////////////////////////////////////////////////////////////////////
  157. - (void)layoutSubviews {
  158. if (_dataSource) {
  159. [self layoutCells];
  160. } else {
  161. _cursorOrigin.x = kPaddingX;
  162. _cursorOrigin.y = [self marginY];
  163. if (self.leftView) {
  164. _cursorOrigin.x += self.leftView.width + kPaddingX/2;
  165. }
  166. }
  167. [super layoutSubviews];
  168. }
  169. ///////////////////////////////////////////////////////////////////////////////////////////////////
  170. - (CGSize)sizeThatFits:(CGSize)size {
  171. [self layoutIfNeeded];
  172. CGFloat height = [self heightWithLines:_lineCount];
  173. return CGSizeMake(size.width, height);
  174. }
  175. ///////////////////////////////////////////////////////////////////////////////////////////////////
  176. - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
  177. [super touchesBegan:touches withEvent:event];
  178. if (_dataSource) {
  179. UITouch* touch = [touches anyObject];
  180. if (touch.view == self) {
  181. self.selectedCell = nil;
  182. } else {
  183. if ([touch.view isKindOfClass:[TTPickerViewCell class]]) {
  184. self.selectedCell = (TTPickerViewCell*)touch.view;
  185. [self becomeFirstResponder];
  186. }
  187. }
  188. }
  189. }
  190. ///////////////////////////////////////////////////////////////////////////////////////////////////
  191. ///////////////////////////////////////////////////////////////////////////////////////////////////
  192. #pragma mark -
  193. #pragma mark UITextField
  194. ///////////////////////////////////////////////////////////////////////////////////////////////////
  195. - (void)setText:(NSString*)text {
  196. if (_dataSource) {
  197. [self updateHeight];
  198. }
  199. [super setText:text];
  200. }
  201. ///////////////////////////////////////////////////////////////////////////////////////////////////
  202. - (CGRect)textRectForBounds:(CGRect)bounds {
  203. if (_dataSource && [self.text isEqualToString:kSelected]) {
  204. // Hide the cursor while a cell is selected
  205. return CGRectMake(-10, 0, 0, 0);
  206. } else {
  207. CGRect frame = CGRectOffset(bounds, _cursorOrigin.x, _cursorOrigin.y);
  208. frame.size.width -= (_cursorOrigin.x + kPaddingX + (self.rightView ? kClearButtonSize : 0));
  209. return frame;
  210. }
  211. }
  212. ///////////////////////////////////////////////////////////////////////////////////////////////////
  213. - (CGRect)editingRectForBounds:(CGRect)bounds {
  214. return [self textRectForBounds:bounds];
  215. }
  216. ///////////////////////////////////////////////////////////////////////////////////////////////////
  217. - (CGRect)placeholderRectForBounds:(CGRect)bounds {
  218. return [self textRectForBounds:bounds];
  219. }
  220. ///////////////////////////////////////////////////////////////////////////////////////////////////
  221. - (CGRect)leftViewRectForBounds:(CGRect)bounds {
  222. if (self.leftView) {
  223. return CGRectMake(
  224. bounds.origin.x+kPaddingX, self.marginY,
  225. self.leftView.frame.size.width, self.leftView.frame.size.height);
  226. } else {
  227. return bounds;
  228. }
  229. }
  230. ///////////////////////////////////////////////////////////////////////////////////////////////////
  231. - (CGRect)rightViewRectForBounds:(CGRect)bounds {
  232. if (self.rightView) {
  233. return CGRectMake(bounds.size.width - kClearButtonSize, bounds.size.height - kClearButtonSize,
  234. kClearButtonSize, kClearButtonSize);
  235. } else {
  236. return bounds;
  237. }
  238. }
  239. ///////////////////////////////////////////////////////////////////////////////////////////////////
  240. ///////////////////////////////////////////////////////////////////////////////////////////////////
  241. #pragma mark -
  242. #pragma mark TTSearchTextField
  243. ///////////////////////////////////////////////////////////////////////////////////////////////////
  244. - (BOOL)hasText {
  245. return self.text.length && ![self.text isEqualToString:kEmpty]
  246. && ![self.text isEqualToString:kSelected];
  247. }
  248. ///////////////////////////////////////////////////////////////////////////////////////////////////
  249. - (void)showSearchResults:(BOOL)show {
  250. [super showSearchResults:show];
  251. if (show) {
  252. [self scrollToEditingLine:YES];
  253. } else {
  254. [self scrollToVisibleLine:YES];
  255. }
  256. }
  257. ///////////////////////////////////////////////////////////////////////////////////////////////////
  258. - (CGRect)rectForSearchResults:(BOOL)withKeyboard {
  259. UIView* superview = self.superviewForSearchResults;
  260. CGFloat y = superview.ttScreenY;
  261. CGFloat visibleHeight = [self heightWithLines:1];
  262. CGFloat keyboardHeight = withKeyboard ? TTKeyboardHeight() : 0;
  263. CGFloat tableHeight = TTScreenBounds().size.height - (y + visibleHeight + keyboardHeight);
  264. return CGRectMake(0, self.bottom-1, superview.frame.size.width, tableHeight+1);
  265. }
  266. ///////////////////////////////////////////////////////////////////////////////////////////////////
  267. - (BOOL)shouldUpdate:(BOOL)emptyText {
  268. if (emptyText && !self.hasText && !self.selectedCell && self.cells.count) {
  269. [self selectLastCell];
  270. return NO;
  271. } else if (emptyText && self.selectedCell) {
  272. [self removeSelectedCell];
  273. [super shouldUpdate:emptyText];
  274. return NO;
  275. } else if (!emptyText && !self.hasText && self.selectedCell) {
  276. [self removeSelectedCell];
  277. [super shouldUpdate:emptyText];
  278. return YES;
  279. } else {
  280. return [super shouldUpdate:emptyText];
  281. }
  282. }
  283. ///////////////////////////////////////////////////////////////////////////////////////////////////
  284. ///////////////////////////////////////////////////////////////////////////////////////////////////
  285. #pragma mark -
  286. #pragma mark UITableViewDelegate
  287. ///////////////////////////////////////////////////////////////////////////////////////////////////
  288. - (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
  289. [_tableView deselectRowAtIndexPath:indexPath animated:NO];
  290. id object = [_dataSource tableView:tableView objectForRowAtIndexPath:indexPath];
  291. [self addCellWithObject:object];
  292. }
  293. ///////////////////////////////////////////////////////////////////////////////////////////////////
  294. ///////////////////////////////////////////////////////////////////////////////////////////////////
  295. #pragma mark -
  296. #pragma mark UIControlEvents
  297. ///////////////////////////////////////////////////////////////////////////////////////////////////
  298. - (void)textFieldDidEndEditing {
  299. if (_selectedCell) {
  300. self.selectedCell = nil;
  301. }
  302. }
  303. ///////////////////////////////////////////////////////////////////////////////////////////////////
  304. ///////////////////////////////////////////////////////////////////////////////////////////////////
  305. - (NSArray*)cells {
  306. NSMutableArray* cells = [NSMutableArray array];
  307. for (TTPickerViewCell* cellView in _cellViews) {
  308. [cells addObject:cellView.object ? cellView.object : [NSNull null]];
  309. }
  310. return cells;
  311. }
  312. ///////////////////////////////////////////////////////////////////////////////////////////////////
  313. - (void)addCellWithObject:(id)object {
  314. TTPickerViewCell* cell = [[[TTPickerViewCell alloc] init] autorelease];
  315. NSString* label = [self labelForObject:object];
  316. cell.object = object;
  317. cell.label = label;
  318. cell.font = self.font;
  319. [_cellViews addObject:cell];
  320. [self addSubview:cell];
  321. // Reset text so the cursor moves to be at the end of the cellViews
  322. self.text = kEmpty;
  323. if ([self.delegate respondsToSelector:@selector(textField:didAddCellAtIndex:)]) {
  324. [(id)self.delegate textField:self didAddCellAtIndex:_cellViews.count-1];
  325. }
  326. }
  327. ///////////////////////////////////////////////////////////////////////////////////////////////////
  328. - (void)removeCellWithObject:(id)object {
  329. for (int i = 0; i < _cellViews.count; ++i) {
  330. TTPickerViewCell* cell = [_cellViews objectAtIndex:i];
  331. if (cell.object == object) {
  332. [_cellViews removeObjectAtIndex:i];
  333. [cell removeFromSuperview];
  334. if ([self.delegate respondsToSelector:@selector(textField:didRemoveCellAtIndex:)]) {
  335. [(id)self.delegate textField:self didRemoveCellAtIndex:i];
  336. }
  337. break;
  338. }
  339. }
  340. // Reset text so the cursor oves to be at the end of the cellViews
  341. self.text = self.text;
  342. }
  343. ///////////////////////////////////////////////////////////////////////////////////////////////////
  344. - (void)removeAllCells {
  345. while (_cellViews.count) {
  346. TTPickerViewCell* cell = [_cellViews objectAtIndex:0];
  347. [cell removeFromSuperview];
  348. [_cellViews removeObjectAtIndex:0];
  349. }
  350. _selectedCell = nil;
  351. }
  352. ///////////////////////////////////////////////////////////////////////////////////////////////////
  353. - (void)setSelectedCell:(TTPickerViewCell*)cell {
  354. if (_selectedCell) {
  355. _selectedCell.selected = NO;
  356. }
  357. _selectedCell = cell;
  358. if (_selectedCell) {
  359. _selectedCell.selected = YES;
  360. self.text = kSelected;
  361. } else if (self.cells.count) {
  362. self.text = kEmpty;
  363. }
  364. }
  365. ///////////////////////////////////////////////////////////////////////////////////////////////////
  366. - (void)removeSelectedCell {
  367. if (_selectedCell) {
  368. [self removeCellWithObject:_selectedCell.object];
  369. _selectedCell = nil;
  370. if (_cellViews.count) {
  371. self.text = kEmpty;
  372. } else {
  373. self.text = @"";
  374. }
  375. }
  376. }
  377. ///////////////////////////////////////////////////////////////////////////////////////////////////
  378. - (void)scrollToVisibleLine:(BOOL)animated {
  379. if (self.editing) {
  380. UIScrollView* scrollView = (UIScrollView*)[self ancestorOrSelfWithClass:[UIScrollView class]];
  381. if (scrollView) {
  382. [scrollView setContentOffset:CGPointMake(0, self.top) animated:animated];
  383. }
  384. }
  385. }
  386. ///////////////////////////////////////////////////////////////////////////////////////////////////
  387. - (void)scrollToEditingLine:(BOOL)animated {
  388. UIScrollView* scrollView = (UIScrollView*)[self ancestorOrSelfWithClass:[UIScrollView class]];
  389. if (scrollView) {
  390. CGFloat offset = _lineCount == 1 ? 0 : [self topOfLine:_lineCount-1];
  391. [scrollView setContentOffset:CGPointMake(0, self.top+offset) animated:animated];
  392. }
  393. }
  394. @end