FLEXMultiColumnTableView.m 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. //
  2. // PTMultiColumnTableView.m
  3. // PTMultiColumnTableViewDemo
  4. //
  5. // Created by Peng Tao on 15/11/16.
  6. // Copyright © 2015年 Peng Tao. All rights reserved.
  7. //
  8. #import "FLEXMultiColumnTableView.h"
  9. #import "FLEXDBQueryRowCell.h"
  10. #import "FLEXTableLeftCell.h"
  11. #import "FLEXColor.h"
  12. @interface FLEXMultiColumnTableView () <
  13. UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate
  14. >
  15. @property (nonatomic) UIScrollView *contentScrollView;
  16. @property (nonatomic) UIScrollView *headerScrollView;
  17. @property (nonatomic) UITableView *leftTableView;
  18. @property (nonatomic) UITableView *contentTableView;
  19. @property (nonatomic) UIView *leftHeader;
  20. /// \c NSNotFound if no column selected
  21. @property (nonatomic) NSInteger sortColumn;
  22. @property (nonatomic) FLEXTableColumnHeaderSortType sortType;
  23. @property (nonatomic) NSArray *rowData;
  24. @property (nonatomic, readonly) NSInteger numberOfColumns;
  25. @property (nonatomic, readonly) NSInteger numberOfRows;
  26. @property (nonatomic, readonly) CGFloat topHeaderHeight;
  27. @property (nonatomic, readonly) CGFloat leftHeaderWidth;
  28. @property (nonatomic, readonly) CGFloat columnMargin;
  29. @end
  30. static const CGFloat kColumnMargin = 1;
  31. @implementation FLEXMultiColumnTableView
  32. #pragma mark - Initialization
  33. - (instancetype)initWithFrame:(CGRect)frame {
  34. self = [super initWithFrame:frame];
  35. if (self) {
  36. self.autoresizingMask |= UIViewAutoresizingFlexibleWidth;
  37. self.autoresizingMask |= UIViewAutoresizingFlexibleHeight;
  38. self.autoresizingMask |= UIViewAutoresizingFlexibleTopMargin;
  39. self.backgroundColor = FLEXColor.groupedBackgroundColor;
  40. [self loadHeaderScrollView];
  41. [self loadContentScrollView];
  42. [self loadLeftView];
  43. }
  44. return self;
  45. }
  46. - (void)layoutSubviews {
  47. [super layoutSubviews];
  48. CGFloat width = self.frame.size.width;
  49. CGFloat height = self.frame.size.height;
  50. CGFloat topheaderHeight = self.topHeaderHeight;
  51. CGFloat leftHeaderWidth = self.leftHeaderWidth;
  52. CGFloat topInsets = 0.f;
  53. if (@available (iOS 11.0, *)) {
  54. topInsets = self.safeAreaInsets.top;
  55. }
  56. CGFloat contentWidth = 0.0;
  57. NSInteger rowsCount = self.numberOfColumns;
  58. for (int i = 0; i < rowsCount; i++) {
  59. contentWidth += [self contentWidthForColumn:i];
  60. }
  61. CGFloat contentHeight = height - topheaderHeight - topInsets;
  62. self.leftHeader.frame = CGRectMake(0, topInsets, self.leftHeaderWidth, self.topHeaderHeight);
  63. self.leftTableView.frame = CGRectMake(
  64. 0, topheaderHeight + topInsets, leftHeaderWidth, contentHeight
  65. );
  66. self.headerScrollView.frame = CGRectMake(
  67. leftHeaderWidth, topInsets, width - leftHeaderWidth, topheaderHeight
  68. );
  69. self.headerScrollView.contentSize = CGSizeMake(
  70. self.contentTableView.frame.size.width, self.headerScrollView.frame.size.height
  71. );
  72. self.contentTableView.frame = CGRectMake(
  73. 0, 0, contentWidth + self.numberOfColumns * self.columnMargin , contentHeight
  74. );
  75. self.contentScrollView.frame = CGRectMake(
  76. leftHeaderWidth, topheaderHeight + topInsets, width - leftHeaderWidth, contentHeight
  77. );
  78. self.contentScrollView.contentSize = self.contentTableView.frame.size;
  79. }
  80. #pragma mark - UI
  81. - (void)loadHeaderScrollView {
  82. UIScrollView *headerScrollView = [UIScrollView new];
  83. headerScrollView.delegate = self;
  84. headerScrollView.backgroundColor = FLEXColor.secondaryGroupedBackgroundColor;
  85. self.headerScrollView = headerScrollView;
  86. [self addSubview:headerScrollView];
  87. }
  88. - (void)loadContentScrollView {
  89. UIScrollView *scrollView = [UIScrollView new];
  90. scrollView.bounces = NO;
  91. scrollView.delegate = self;
  92. UITableView *tableView = [UITableView new];
  93. tableView.delegate = self;
  94. tableView.dataSource = self;
  95. tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
  96. [tableView registerClass:[FLEXDBQueryRowCell class]
  97. forCellReuseIdentifier:kFLEXDBQueryRowCellReuse
  98. ];
  99. [scrollView addSubview:tableView];
  100. [self addSubview:scrollView];
  101. self.contentScrollView = scrollView;
  102. self.contentTableView = tableView;
  103. }
  104. - (void)loadLeftView {
  105. UITableView *leftTableView = [UITableView new];
  106. leftTableView.delegate = self;
  107. leftTableView.dataSource = self;
  108. leftTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
  109. self.leftTableView = leftTableView;
  110. [self addSubview:leftTableView];
  111. UIView *leftHeader = [UIView new];
  112. leftHeader.backgroundColor = FLEXColor.secondaryBackgroundColor;
  113. self.leftHeader = leftHeader;
  114. [self addSubview:leftHeader];
  115. }
  116. #pragma mark - Data
  117. - (void)reloadData {
  118. [self loadLeftViewData];
  119. [self loadContentData];
  120. [self loadHeaderData];
  121. }
  122. - (void)loadHeaderData {
  123. // Remove existing headers, if any
  124. for (UIView *subview in self.headerScrollView.subviews) {
  125. [subview removeFromSuperview];
  126. }
  127. CGFloat xOffset = 0.0;
  128. for (NSInteger column = 0; column < self.numberOfColumns; column++) {
  129. CGFloat width = [self contentWidthForColumn:column] + self.columnMargin;
  130. FLEXTableColumnHeader *header = [[FLEXTableColumnHeader alloc]
  131. initWithFrame:CGRectMake(xOffset, 0, width, self.topHeaderHeight - 1)
  132. ];
  133. header.titleLabel.text = [self columnTitle:column];
  134. if (column == self.sortColumn) {
  135. header.sortType = self.sortType;
  136. }
  137. // Header tap gesture
  138. UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc]
  139. initWithTarget:self action:@selector(contentHeaderTap:)
  140. ];
  141. [header addGestureRecognizer:gesture];
  142. header.userInteractionEnabled = YES;
  143. [self.headerScrollView addSubview:header];
  144. xOffset += width;
  145. }
  146. }
  147. - (void)contentHeaderTap:(UIGestureRecognizer *)gesture {
  148. NSInteger newSortColumn = [self.headerScrollView.subviews indexOfObject:gesture.view];
  149. FLEXTableColumnHeaderSortType newType = FLEXNextTableColumnHeaderSortType(self.sortType);
  150. // Reset old header
  151. FLEXTableColumnHeader *oldHeader = (id)self.headerScrollView.subviews[self.sortColumn];
  152. oldHeader.sortType = FLEXTableColumnHeaderSortTypeNone;
  153. // Update new header
  154. FLEXTableColumnHeader *newHeader = (id)self.headerScrollView.subviews[newSortColumn];
  155. newHeader.sortType = newType;
  156. // Update self
  157. self.sortColumn = newSortColumn;
  158. self.sortType = newType;
  159. // Notify delegate
  160. [self.delegate multiColumnTableView:self didSelectHeaderForColumn:newSortColumn sortType:newType];
  161. }
  162. - (void)loadContentData {
  163. [self.contentTableView reloadData];
  164. }
  165. - (void)loadLeftViewData {
  166. [self.leftTableView reloadData];
  167. }
  168. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  169. // Alternating background color
  170. UIColor *backgroundColor = FLEXColor.primaryBackgroundColor;
  171. if (indexPath.row % 2 != 0) {
  172. backgroundColor = FLEXColor.secondaryBackgroundColor;
  173. }
  174. // Left side table view for row numbers
  175. if (tableView == self.leftTableView) {
  176. FLEXTableLeftCell *cell = [FLEXTableLeftCell cellWithTableView:tableView];
  177. cell.contentView.backgroundColor = backgroundColor;
  178. cell.titlelabel.text = [self rowTitle:indexPath.row];
  179. return cell;
  180. }
  181. // Right side table view for data
  182. else {
  183. self.rowData = [self.dataSource contentForRow:indexPath.row];
  184. FLEXDBQueryRowCell *cell = [tableView
  185. dequeueReusableCellWithIdentifier:kFLEXDBQueryRowCellReuse forIndexPath:indexPath
  186. ];
  187. cell.contentView.backgroundColor = backgroundColor;
  188. cell.data = [self.dataSource contentForRow:indexPath.row];
  189. NSAssert(cell.data.count == self.numberOfColumns, @"Count of data provided was incorrect");
  190. return cell;
  191. }
  192. }
  193. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  194. return [self.dataSource numberOfRowsInTableView:self];
  195. }
  196. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
  197. return [self.dataSource multiColumnTableView:self heightForContentCellInRow:indexPath.row];
  198. }
  199. // Scroll all scroll views in sync
  200. - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  201. if (scrollView == self.contentScrollView) {
  202. self.headerScrollView.contentOffset = scrollView.contentOffset;
  203. }
  204. else if (scrollView == self.headerScrollView) {
  205. self.contentScrollView.contentOffset = scrollView.contentOffset;
  206. }
  207. else if (scrollView == self.leftTableView) {
  208. self.contentTableView.contentOffset = scrollView.contentOffset;
  209. }
  210. else if (scrollView == self.contentTableView) {
  211. self.leftTableView.contentOffset = scrollView.contentOffset;
  212. }
  213. }
  214. #pragma mark UITableView Delegate
  215. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  216. if (tableView == self.leftTableView) {
  217. [self.contentTableView
  218. selectRowAtIndexPath:indexPath
  219. animated:NO
  220. scrollPosition:UITableViewScrollPositionNone
  221. ];
  222. }
  223. else if (tableView == self.contentTableView) {
  224. [self.delegate multiColumnTableView:self didSelectRow:indexPath.row];
  225. }
  226. }
  227. #pragma mark DataSource Accessor
  228. - (NSInteger)numberOfRows {
  229. return [self.dataSource numberOfRowsInTableView:self];
  230. }
  231. - (NSInteger)numberOfColumns {
  232. return [self.dataSource numberOfColumnsInTableView:self];
  233. }
  234. - (NSString *)columnTitle:(NSInteger)column {
  235. return [self.dataSource columnTitle:column];
  236. }
  237. - (NSString *)rowTitle:(NSInteger)row {
  238. return [self.dataSource rowTitle:row];
  239. }
  240. - (CGFloat)contentWidthForColumn:(NSInteger)column {
  241. return [self.dataSource multiColumnTableView:self widthForContentCellInColumn:column];
  242. }
  243. - (CGFloat)contentHeightForRow:(NSInteger)row {
  244. return [self.dataSource multiColumnTableView:self heightForContentCellInRow:row];
  245. }
  246. - (CGFloat)topHeaderHeight {
  247. return [self.dataSource heightForTopHeaderInTableView:self];
  248. }
  249. - (CGFloat)leftHeaderWidth {
  250. return [self.dataSource widthForLeftHeaderInTableView:self];
  251. }
  252. - (CGFloat)columnMargin {
  253. return kColumnMargin;
  254. }
  255. @end