PageRenderTime 26ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/ExpandTableView/JKExpandTableView.m

https://gitlab.com/gaurav1981/JKExpandTableView
Objective C | 391 lines | 290 code | 64 blank | 37 comment | 57 complexity | c34dff1fb924747ded73e571eccb7641 MD5 | raw file
  1. //
  2. // JKExpandTableView.m
  3. // ExpandTableView
  4. //
  5. // Created by Jack Kwok on 7/5/13.
  6. // Copyright (c) 2013 Jack Kwok. All rights reserved.
  7. //
  8. #import "JKExpandTableView.h"
  9. #import "JKParentTableViewCell.h"
  10. #import "JKMultiSelectSubTableViewCell.h"
  11. #import "JKSingleSelectSubTableViewCell.h"
  12. @implementation JKExpandTableView
  13. @synthesize tableViewDelegate, expansionStates;
  14. #define HEIGHT_FOR_CELL 44.0
  15. - (id)initWithFrame:(CGRect)frame dataSource:dataDelegate tableViewDelegate:tableDelegate {
  16. self = [super initWithFrame:frame style:UITableViewStylePlain];
  17. //self = [super initWithFrame:frame style:UITableViewStyleGrouped];
  18. if (self) {
  19. // Initialization code
  20. [self initialize];
  21. }
  22. return self;
  23. }
  24. -(id)initWithCoder:(NSCoder *)coder {
  25. self = [super initWithCoder:coder];
  26. if (self) {
  27. // Initialization code
  28. [self initialize];
  29. }
  30. return self;
  31. }
  32. /* not working. override animation for insert and delete for custom animation
  33. - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
  34. {
  35. for (NSIndexPath *indexPath in indexPaths)
  36. {
  37. UITableViewCell *cell = [self tableView:self cellForRowAtIndexPath:indexPath];
  38. [cell setFrame:CGRectMake(320, cell.frame.origin.y, cell.frame.size.width, cell.frame.size.height)];
  39. [UIView beginAnimations:NULL context:nil];
  40. [UIView setAnimationDuration:1];
  41. [cell setFrame:CGRectMake(0, cell.frame.origin.y, cell.frame.size.width, cell.frame.size.height)];
  42. [UIView commitAnimations];
  43. }
  44. }
  45. */
  46. - (void) initialize {
  47. [self setDataSource:self];
  48. [self setDelegate:self];
  49. self.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
  50. // Trick to hide UITableView Empty Cell Separator Lines (stuff below last nonempty cell)
  51. UIView *footer = [[UIView alloc] initWithFrame:CGRectZero];
  52. self.tableFooterView = footer;
  53. }
  54. - (id) getDataSourceDelegate {
  55. return dataSourceDelegate;
  56. }
  57. - (void) setDataSourceDelegate:(id) deleg {
  58. dataSourceDelegate = deleg;
  59. [self initExpansionStates];
  60. }
  61. - (void) initExpansionStates
  62. {
  63. // all collapsed initially
  64. expansionStates = [[NSMutableArray alloc] initWithCapacity:[self.dataSourceDelegate numberOfParentCells]];
  65. for(int i = 0; i<[self.dataSourceDelegate numberOfParentCells]; i++) {
  66. [expansionStates addObject:@"NO"];
  67. }
  68. }
  69. - (void) expandForParentAtRow: (NSInteger) row {
  70. NSUInteger parentIndex = [self parentIndexForRow:row];
  71. if ([[self.expansionStates objectAtIndex:parentIndex] boolValue]) {
  72. return;
  73. }
  74. // update expansionStates so backing data is ready before calling insertRowsAtIndexPaths
  75. [self.expansionStates replaceObjectAtIndex:parentIndex withObject:@"YES"];
  76. [self insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:(row + 1) inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
  77. }
  78. - (void) collapseForParentAtRow: (NSInteger) row {
  79. NSUInteger parentIndex = [self parentIndexForRow:row];
  80. if (![[self.expansionStates objectAtIndex:parentIndex] boolValue]) {
  81. return;
  82. }
  83. // update expansionStates so backing data is ready before calling deleteRowsAtIndexPaths
  84. [self.expansionStates replaceObjectAtIndex:parentIndex withObject:@"NO"];
  85. [self deleteRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:(row + 1) inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
  86. }
  87. - (void) animateParentCellIconExpand: (BOOL) expand forCell: (JKParentTableViewCell *) cell {
  88. if ([self.dataSourceDelegate respondsToSelector:@selector(shouldRotateIconForParentOnToggle)] &&
  89. ([self.dataSourceDelegate shouldRotateIconForParentOnToggle] == NO)) {
  90. } else {
  91. if (expand) {
  92. [cell rotateIconToExpanded];
  93. } else {
  94. [cell rotateIconToCollapsed];
  95. }
  96. }
  97. }
  98. - (NSUInteger) rowForParentIndex:(NSUInteger) parentIndex {
  99. NSUInteger row = 0;
  100. NSUInteger currentParentIndex = 0;
  101. if (parentIndex == 0) {
  102. return 0;
  103. }
  104. while (currentParentIndex < parentIndex) {
  105. BOOL expanded = [[self.expansionStates objectAtIndex:currentParentIndex] boolValue];
  106. if (expanded) {
  107. row++;
  108. }
  109. currentParentIndex++;
  110. row++;
  111. }
  112. return row;
  113. }
  114. - (NSUInteger) parentIndexForRow:(NSUInteger) row {
  115. NSUInteger parentIndex = -1;
  116. NSUInteger i = 0;
  117. while (i <= row) {
  118. parentIndex ++;
  119. i++;
  120. if ([[self.expansionStates objectAtIndex:parentIndex] boolValue]) {
  121. i++;
  122. }
  123. }
  124. NSLog(@"parentIndexForRow row: %ld parentIndex: %ld", (long)row, (long)parentIndex);
  125. return parentIndex;
  126. }
  127. - (BOOL) isExpansionCell:(NSUInteger) row {
  128. if (row < 1) {
  129. return NO;
  130. }
  131. NSUInteger parentIndex = [self parentIndexForRow:row];
  132. NSUInteger parentIndex2 = [self parentIndexForRow:(row-1)];
  133. return (parentIndex == parentIndex2);
  134. }
  135. #pragma mark - UITableViewDataSource
  136. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  137. {
  138. // Return the number of sections.
  139. return 1;
  140. }
  141. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  142. {
  143. // returns sum of parent cells and expanded cells
  144. NSInteger parentCount = [self.dataSourceDelegate numberOfParentCells];
  145. NSCountedSet * countedSet = [[NSCountedSet alloc] initWithArray:self.expansionStates];
  146. NSUInteger expandedParentCount = [countedSet countForObject:@"YES"];
  147. return parentCount + expandedParentCount;
  148. }
  149. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  150. {
  151. static NSString *CellIdentifier_Parent = @"CellReuseId_Parent";
  152. static NSString *CellIdentifier_MultiSelect = @"CellReuseId_MultiSelectExpand";
  153. static NSString *CellIdentifier_SingleSelect = @"CellReuseId_SingleSelectExpand";
  154. NSInteger row = indexPath.row;
  155. NSUInteger parentIndex = [self parentIndexForRow:row];
  156. BOOL isExpansionCell = [self isExpansionCell:row];
  157. if (isExpansionCell) {
  158. BOOL isMultiSelect = [self.tableViewDelegate shouldSupportMultipleSelectableChildrenAtParentIndex:parentIndex];
  159. if (isMultiSelect) {
  160. JKMultiSelectSubTableViewCell *cell = (JKMultiSelectSubTableViewCell *)[self dequeueReusableCellWithIdentifier:CellIdentifier_MultiSelect];
  161. if (cell == nil) {
  162. cell = [[JKMultiSelectSubTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier_MultiSelect];
  163. } else {
  164. NSLog(@"reusing existing JKMultiSelectSubTableViewCell");
  165. }
  166. if ([self.tableViewDelegate respondsToSelector:@selector(backgroundColor)]) {
  167. UIColor * bgColor = [self.tableViewDelegate backgroundColor];
  168. [cell setSubTableBackgroundColor:bgColor];
  169. }
  170. if ([self.tableViewDelegate respondsToSelector:@selector(foregroundColor)]) {
  171. UIColor * fgColor = [self.tableViewDelegate foregroundColor];
  172. [cell setSubTableForegroundColor:fgColor];
  173. }
  174. if ([self.tableViewDelegate respondsToSelector:@selector(selectionIndicatorIcon)]) {
  175. [cell setSelectionIndicatorImg:[self.tableViewDelegate selectionIndicatorIcon]];
  176. }
  177. NSLog(@"cellForRowAtIndexPath MultiSelect parentIndex: %ld", (long)parentIndex);
  178. [cell setParentIndex:parentIndex];
  179. [cell setDelegate:self];
  180. [cell reload];
  181. return cell;
  182. } else {
  183. JKSingleSelectSubTableViewCell *cell = (JKSingleSelectSubTableViewCell *)[self dequeueReusableCellWithIdentifier:CellIdentifier_SingleSelect];
  184. if (cell == nil) {
  185. cell = [[JKSingleSelectSubTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier_SingleSelect];
  186. } else {
  187. NSLog(@"reusing existing JKSingleSelectSubTableViewCell");
  188. }
  189. if ([self.tableViewDelegate respondsToSelector:@selector(backgroundColor)]) {
  190. UIColor * bgColor = [self.tableViewDelegate backgroundColor];
  191. [cell setSubTableBackgroundColor:bgColor];
  192. }
  193. if ([self.tableViewDelegate respondsToSelector:@selector(foregroundColor)]) {
  194. UIColor * fgColor = [self.tableViewDelegate foregroundColor];
  195. [cell setSubTableForegroundColor:fgColor];
  196. }
  197. if ([self.tableViewDelegate respondsToSelector:@selector(selectionIndicatorIcon)]) {
  198. [cell setSelectionIndicatorImg:[self.tableViewDelegate selectionIndicatorIcon]];
  199. }
  200. if ([self.tableViewDelegate respondsToSelector:@selector(fontForChildren)]) {
  201. UIFont *font = [self.tableViewDelegate fontForChildren];
  202. [cell setSubTableFont:font];
  203. }
  204. NSLog(@"cellForRowAtIndexPath SingleSelect parentIndex: %ld", (long)parentIndex);
  205. [cell setParentIndex:parentIndex];
  206. [cell setDelegate:self];
  207. [cell reload];
  208. return cell;
  209. }
  210. } else {
  211. // regular parent cell
  212. JKParentTableViewCell *cell = (JKParentTableViewCell *)[self dequeueReusableCellWithIdentifier:CellIdentifier_Parent];
  213. if (cell == nil) {
  214. cell = [[JKParentTableViewCell alloc] initWithReuseIdentifier:CellIdentifier_Parent];
  215. } else {
  216. NSLog(@"reusing existing JKParentTableViewCell");
  217. }
  218. if ([self.tableViewDelegate respondsToSelector:@selector(backgroundColor)]) {
  219. UIColor * bgColor = [self.tableViewDelegate backgroundColor];
  220. [cell setCellBackgroundColor:bgColor];
  221. }
  222. if ([self.tableViewDelegate respondsToSelector:@selector(foregroundColor)]) {
  223. UIColor * fgColor = [self.tableViewDelegate foregroundColor];
  224. [cell setCellForegroundColor:fgColor];
  225. }
  226. if ([self.tableViewDelegate respondsToSelector:@selector(selectionIndicatorIcon)]) {
  227. [cell setSelectionIndicatorImg:[self.tableViewDelegate selectionIndicatorIcon]];
  228. }
  229. if ([self.tableViewDelegate respondsToSelector:@selector(fontForParents)]) {
  230. UIFont * font = [self.tableViewDelegate fontForParents];
  231. [cell.label setFont:font];
  232. }
  233. NSString * labelStr = [self.dataSourceDelegate labelForParentCellAtIndex:parentIndex];
  234. [[cell label] setText:labelStr];
  235. if ([self.dataSourceDelegate respondsToSelector:@selector(iconForParentCellAtIndex:)]) {
  236. UIImage *icon = [self.dataSourceDelegate iconForParentCellAtIndex:parentIndex];
  237. [[cell iconImage] setImage:icon];
  238. }
  239. [cell setParentIndex:parentIndex];
  240. [cell selectionIndicatorState:[self hasSelectedChild:parentIndex]];
  241. //[cell setupDisplay];
  242. return cell;
  243. }
  244. }
  245. #pragma mark - Table view delegate
  246. -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
  247. NSInteger row = indexPath.row;
  248. NSLog(@"heightForRowAtIndexPath row: %ld", (long)row);
  249. // if cell is expanded, the cell height would be a multiple of the number of child cells
  250. BOOL isExpansionCell = [self isExpansionCell:row];
  251. if (isExpansionCell) {
  252. NSInteger parentIndex = [self parentIndexForRow:row];
  253. NSInteger numberOfChildren = [self.dataSourceDelegate numberOfChildCellsUnderParentIndex:parentIndex];
  254. return HEIGHT_FOR_CELL * numberOfChildren;
  255. } else {
  256. return HEIGHT_FOR_CELL;
  257. }
  258. }
  259. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  260. {
  261. // if parent , expand/collpase then notify delegate (always check respond to selector)
  262. UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
  263. if ([selectedCell isKindOfClass:[JKParentTableViewCell class]]) {
  264. JKParentTableViewCell * pCell = (JKParentTableViewCell *) selectedCell;
  265. if ([[self.expansionStates objectAtIndex:[pCell parentIndex]] boolValue]) {
  266. [self collapseForParentAtRow:indexPath.row];
  267. [self animateParentCellIconExpand:NO forCell:pCell]; // TODO handle the case where there is no child.
  268. } else {
  269. [self expandForParentAtRow:indexPath.row];
  270. [self animateParentCellIconExpand:YES forCell:pCell];
  271. }
  272. if ([self.tableViewDelegate respondsToSelector:@selector(tableView:didSelectParentCellAtIndex:)]) {
  273. [self.tableViewDelegate tableView:tableView didSelectParentCellAtIndex:[pCell parentIndex]];
  274. }
  275. } else {
  276. // ignore clicks on child because the sub table should handle it.
  277. }
  278. }
  279. #pragma mark - JKMultiSelectSubTableViewCellDelegate
  280. - (NSInteger) numberOfChildrenUnderParentIndex:(NSInteger)parentIndex {
  281. return [self.dataSourceDelegate numberOfChildCellsUnderParentIndex:parentIndex];
  282. }
  283. - (BOOL) isSelectedForChildIndex:(NSInteger)childIndex underParentIndex:(NSInteger)parentIndex {
  284. return [self.dataSourceDelegate shouldDisplaySelectedStateForCellAtChildIndex:childIndex withinParentCellIndex:parentIndex];
  285. }
  286. - (void) didSelectRowAtChildIndex:(NSInteger)childIndex
  287. selected:(BOOL)isSwitchedOn
  288. underParentIndex:(NSInteger)parentIndex {
  289. // check if at least one child is selected. if yes, set the parent checkmark to indicate at least one chlid selected
  290. if (isSwitchedOn &&
  291. [self.tableViewDelegate respondsToSelector:@selector(tableView:didSelectCellAtChildIndex:withInParentCellIndex:)]) {
  292. [self.tableViewDelegate tableView:self didSelectCellAtChildIndex:childIndex withInParentCellIndex:parentIndex];
  293. }
  294. if (!isSwitchedOn &&
  295. [self.tableViewDelegate respondsToSelector:@selector(tableView:didDeselectCellAtChildIndex:withInParentCellIndex:)]) {
  296. [self.tableViewDelegate tableView:self didDeselectCellAtChildIndex:childIndex withInParentCellIndex:parentIndex];
  297. }
  298. NSIndexPath * indexPath = [NSIndexPath indexPathForRow:[self rowForParentIndex:parentIndex] inSection:0];
  299. UITableViewCell *selectedCell = [self cellForRowAtIndexPath:indexPath];
  300. if ([selectedCell isKindOfClass:[JKParentTableViewCell class]]) {
  301. JKParentTableViewCell * pCell = (JKParentTableViewCell *) selectedCell;
  302. [pCell selectionIndicatorState:[self hasSelectedChild:parentIndex]];
  303. }
  304. }
  305. - (NSString *) labelForChildIndex:(NSInteger)childIndex underParentIndex:(NSInteger)parentIndex {
  306. return [self.dataSourceDelegate labelForCellAtChildIndex:childIndex withinParentCellIndex:parentIndex];
  307. }
  308. - (UIImage *) iconForChildIndex:(NSInteger)childIndex underParentIndex:(NSInteger)parentIndex {
  309. if ([self.dataSourceDelegate respondsToSelector:@selector(iconForCellAtChildIndex:withinParentCellIndex:)]) {
  310. return [self.dataSourceDelegate iconForCellAtChildIndex:childIndex withinParentCellIndex:parentIndex];
  311. } else {
  312. return nil;
  313. }
  314. }
  315. - (BOOL) hasSelectedChild:(NSUInteger) parentIndex {
  316. NSInteger numberOfChildren = [self.dataSourceDelegate numberOfChildCellsUnderParentIndex:parentIndex];
  317. BOOL result = NO;
  318. for (int i = 0; i < numberOfChildren ; i++) {
  319. if ([self.dataSourceDelegate shouldDisplaySelectedStateForCellAtChildIndex:i withinParentCellIndex:parentIndex]) {
  320. result = YES;
  321. break;
  322. }
  323. }
  324. return result;
  325. }
  326. @end