FLEXMultiColumnTableView.m 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  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. #if !TARGET_OS_TV
  96. tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
  97. #endif
  98. [tableView registerClass:[FLEXDBQueryRowCell class]
  99. forCellReuseIdentifier:kFLEXDBQueryRowCellReuse
  100. ];
  101. [scrollView addSubview:tableView];
  102. [self addSubview:scrollView];
  103. self.contentScrollView = scrollView;
  104. self.contentTableView = tableView;
  105. }
  106. - (void)loadLeftView {
  107. UITableView *leftTableView = [UITableView new];
  108. leftTableView.delegate = self;
  109. leftTableView.dataSource = self;
  110. #if !TARGET_OS_TV
  111. leftTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
  112. #endif
  113. self.leftTableView = leftTableView;
  114. [self addSubview:leftTableView];
  115. UIView *leftHeader = [UIView new];
  116. leftHeader.backgroundColor = FLEXColor.secondaryBackgroundColor;
  117. self.leftHeader = leftHeader;
  118. [self addSubview:leftHeader];
  119. }
  120. #pragma mark - Data
  121. - (void)reloadData {
  122. [self loadLeftViewData];
  123. [self loadContentData];
  124. [self loadHeaderData];
  125. }
  126. - (void)loadHeaderData {
  127. // Remove existing headers, if any
  128. for (UIView *subview in self.headerScrollView.subviews) {
  129. [subview removeFromSuperview];
  130. }
  131. CGFloat xOffset = 0.0;
  132. for (NSInteger column = 0; column < self.numberOfColumns; column++) {
  133. CGFloat width = [self contentWidthForColumn:column] + self.columnMargin;
  134. FLEXTableColumnHeader *header = [[FLEXTableColumnHeader alloc]
  135. initWithFrame:CGRectMake(xOffset, 0, width, self.topHeaderHeight - 1)
  136. ];
  137. header.titleLabel.text = [self columnTitle:column];
  138. if (column == self.sortColumn) {
  139. header.sortType = self.sortType;
  140. }
  141. // Header tap gesture
  142. UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc]
  143. initWithTarget:self action:@selector(contentHeaderTap:)
  144. ];
  145. [header addGestureRecognizer:gesture];
  146. header.userInteractionEnabled = YES;
  147. [self.headerScrollView addSubview:header];
  148. xOffset += width;
  149. }
  150. }
  151. - (void)contentHeaderTap:(UIGestureRecognizer *)gesture {
  152. NSInteger newSortColumn = [self.headerScrollView.subviews indexOfObject:gesture.view];
  153. FLEXTableColumnHeaderSortType newType = FLEXNextTableColumnHeaderSortType(self.sortType);
  154. // Reset old header
  155. FLEXTableColumnHeader *oldHeader = (id)self.headerScrollView.subviews[self.sortColumn];
  156. oldHeader.sortType = FLEXTableColumnHeaderSortTypeNone;
  157. // Update new header
  158. FLEXTableColumnHeader *newHeader = (id)self.headerScrollView.subviews[newSortColumn];
  159. newHeader.sortType = newType;
  160. // Update self
  161. self.sortColumn = newSortColumn;
  162. self.sortType = newType;
  163. // Notify delegate
  164. [self.delegate multiColumnTableView:self didSelectHeaderForColumn:newSortColumn sortType:newType];
  165. }
  166. - (void)loadContentData {
  167. [self.contentTableView reloadData];
  168. }
  169. - (void)loadLeftViewData {
  170. [self.leftTableView reloadData];
  171. }
  172. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  173. // Alternating background color
  174. UIColor *backgroundColor = FLEXColor.primaryBackgroundColor;
  175. if (indexPath.row % 2 != 0) {
  176. backgroundColor = FLEXColor.secondaryBackgroundColor;
  177. }
  178. // Left side table view for row numbers
  179. if (tableView == self.leftTableView) {
  180. FLEXTableLeftCell *cell = [FLEXTableLeftCell cellWithTableView:tableView];
  181. cell.contentView.backgroundColor = backgroundColor;
  182. cell.titlelabel.text = [self rowTitle:indexPath.row];
  183. return cell;
  184. }
  185. // Right side table view for data
  186. else {
  187. self.rowData = [self.dataSource contentForRow:indexPath.row];
  188. FLEXDBQueryRowCell *cell = [tableView
  189. dequeueReusableCellWithIdentifier:kFLEXDBQueryRowCellReuse forIndexPath:indexPath
  190. ];
  191. cell.contentView.backgroundColor = backgroundColor;
  192. cell.data = [self.dataSource contentForRow:indexPath.row];
  193. NSAssert(cell.data.count == self.numberOfColumns, @"Count of data provided was incorrect");
  194. return cell;
  195. }
  196. }
  197. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  198. return [self.dataSource numberOfRowsInTableView:self];
  199. }
  200. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
  201. return [self.dataSource multiColumnTableView:self heightForContentCellInRow:indexPath.row];
  202. }
  203. // Scroll all scroll views in sync
  204. - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  205. if (scrollView == self.contentScrollView) {
  206. self.headerScrollView.contentOffset = scrollView.contentOffset;
  207. }
  208. else if (scrollView == self.headerScrollView) {
  209. self.contentScrollView.contentOffset = scrollView.contentOffset;
  210. }
  211. else if (scrollView == self.leftTableView) {
  212. self.contentTableView.contentOffset = scrollView.contentOffset;
  213. }
  214. else if (scrollView == self.contentTableView) {
  215. self.leftTableView.contentOffset = scrollView.contentOffset;
  216. }
  217. }
  218. #pragma mark UITableView Delegate
  219. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  220. if (tableView == self.leftTableView) {
  221. [self.contentTableView
  222. selectRowAtIndexPath:indexPath
  223. animated:NO
  224. scrollPosition:UITableViewScrollPositionNone
  225. ];
  226. }
  227. else if (tableView == self.contentTableView) {
  228. [self.delegate multiColumnTableView:self didSelectRow:indexPath.row];
  229. }
  230. }
  231. #pragma mark DataSource Accessor
  232. - (NSInteger)numberOfRows {
  233. return [self.dataSource numberOfRowsInTableView:self];
  234. }
  235. - (NSInteger)numberOfColumns {
  236. return [self.dataSource numberOfColumnsInTableView:self];
  237. }
  238. - (NSString *)columnTitle:(NSInteger)column {
  239. return [self.dataSource columnTitle:column];
  240. }
  241. - (NSString *)rowTitle:(NSInteger)row {
  242. return [self.dataSource rowTitle:row];
  243. }
  244. - (CGFloat)contentWidthForColumn:(NSInteger)column {
  245. return [self.dataSource multiColumnTableView:self widthForContentCellInColumn:column];
  246. }
  247. - (CGFloat)contentHeightForRow:(NSInteger)row {
  248. return [self.dataSource multiColumnTableView:self heightForContentCellInRow:row];
  249. }
  250. - (CGFloat)topHeaderHeight {
  251. return [self.dataSource heightForTopHeaderInTableView:self];
  252. }
  253. - (CGFloat)leftHeaderWidth {
  254. return [self.dataSource widthForLeftHeaderInTableView:self];
  255. }
  256. - (CGFloat)columnMargin {
  257. return kColumnMargin;
  258. }
  259. @end