UITableview有兩個相關代理UITableViewDelegate、UITableViewDataSource
dataSource是數據源代理,delegate則是相關操做代理git
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
經過返回值,告訴tableview的某個section應該顯示多少個單元格github
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
經過返回值,告訴tableview,indexPath索引下的單元格的高度,tableview單元格的寬度與tableview相同數組
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
經過返回cell,告訴tableview,indexPath索引下應該展示的單元格
以上即是tableview dataSource最基本的,也是必須實現的三個代理,經過這三個代理能夠展示一個最基本的tableview緩存
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
點擊單元格回調方法框架
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; if (cell == nil) { UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"]; } return cell; }
以上是tableview實現重用的基本寫法,經過建立一個帶有重用標識符的cell,tableview每次經過代理獲取cell時,都會先從重用池中獲取,節省內存消耗。 以上是tableview最基礎的幾個代理方法,下面經過代碼實現這幾個代理方法,一窺tableview內在工做的原理ide
ps:源代碼已上傳至github:MinScrollMenu佈局
introduce.gifatom
1 定義代理spa
- (NSInteger)numberOfMenuCount:(MinScrollMenu *)menu; - (CGFloat)scrollMenu:(MinScrollMenu*)menu widthForItemAtIndex:(NSInteger)index; - (MinScrollMenuItem *)scrollMenu:(MinScrollMenu*)menu itemAtIndex:(NSInteger)index; - (void)scrollMenu:(MinScrollMenu*)menu didSelectedItem: (MinScrollMenuItem *)item atIndex: (NSInteger)index;
模仿以前介紹的四個代理方法。代理
2 佈局
建立一個繼承UIView的子類,命名爲MinScrollMenu。
(1)添加一個scrollView屬性,初始化加到MinScrollMenu上,frame大小和父視圖同樣。正如系統的UITableView同樣,咱們也使用scrollView來實現功能
@property (nonatomic, strong) UIScrollView *scrollView;/*!< 橫向滾動的scrollView */
(2)添加一個繼承自UIView的屬性,命名爲contentView,初始化加到以前建立好的scrollView上。frame能夠先不設置,這個view主要用來裝載未來要顯示的單元格,frame大小須要之後計算。
@property (nonatomic, strong) UIView *contentView;/*!< 裝載item的view */
(3)如下幾個屬性主要用來緩存單元格數據源的數據
@property (nonatomic, strong) NSMutableArray *visibleItems;/*!< 屏幕範圍內的item數組 */ @property (nonatomic, strong) NSMutableSet *reuseableItems;/*!< 重用池 */ @property (nonatomic, strong) NSMutableDictionary *infoDict;/*!< 緩存item被選中信息 */ @property (nonatomic, strong) NSMutableDictionary *frameDict;/*!< 緩存item的frame */
3 處理數據源數據
(1) 根據代理獲取item個數
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(numberOfMenuCount:)]) { _count = [self.delegate numberOfMenuCount:self]; }
(2) 循環建立單元格item,由於是橫向滾動的,因此主要獲取寬度和改變x軸的值計算frame。計算出全部的item的frame並裝在字典中緩存,而後就能夠得出以前沒有設置的contentView的frame了,貼出代碼:
for (NSInteger i = 0; i < _count; ++i) { //獲取item的寬度 width = [self itemWidthWithIndex:i]; CGRect itemFrame = CGRectMake(x, y, width, height); // 超過屏幕可顯示範圍不加入到visibleItems數組 CGFloat maxX = CGRectGetMaxX(itemFrame); CGFloat overItemWidth = width*3; if (i < _count-3) { overItemWidth = width + [self itemWidthWithIndex:i+1] + [self itemWidthWithIndex:i+2]; } isOverScreenWidth = maxX > ScreenWidth + overItemWidth; if (!isOverScreenWidth) { // 獲取item,設置Frame, 添加到contentView上 MinScrollMenuItem *item = [self itemWithIndex:i]; if (item) { item.frame = itemFrame; [_contentView addSubview:item]; // 添加點擊手勢 UITapGestureRecognizer *tapGst = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapItem:)]; [item addGestureRecognizer:tapGst]; item.tag = ITEMTAG + i; // 加入到visibleItems數組 [_visibleItems addObject:item]; } } // 緩存數據 [_frameDict setObject:@(i) forKey:NSStringFromCGRect(itemFrame)]; [_infoDict setObject:@(NO) forKey:@(i)]; // 計算scrollView的contentSize scrollContentWidth = maxX; x += width; } _scrollView.contentSize = CGSizeMake(scrollContentWidth, height); _contentView.frame = CGRectMake(0, 0, scrollContentWidth, height);
完成以上代碼,運行一下。就能夠看見item顯示了,可是滾動處理尚未完成,因此手指拖動scrollView右邊區域仍是空白一片,接下來就是核心的滾動處理和重用機制的實現
(3) 重用和滾動處理
重用和滾動處理是同時進行的,當tableView向右滾動時,若是最左邊的item已經離開屏幕範圍,那麼就能夠將它放進重用池中存儲,同時也要根據item的標識符從重用池裏取出item,設置frame,添加到visibleItems數組。如此就能夠循環使用幾個item來展示n個item的內容了。
實現UIScrollView代理方法
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
經過這個方法能夠獲取當前scrollView移動的位移contentOffset
具體思路以下圖所示:
滾動.png
重用機制代碼:
內部查找重用的item:
NSSet *tempSet = [_reuseableItems filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"reuseIdentifer == %@", reuseItem.reuseIdentifer]]; // 查詢複用池中有沒有相同複用符的item if (![tempSet isSubsetOfSet:_reuseableItems] || tempSet.count == 0) { // 沒有則添加item到複用池中 [_reuseableItems addObject:reuseItem]; }
公開API實現的代碼:
- (MinScrollMenuItem *)dequeueItemWithIdentifer:(NSString *)identifer { NSSet *tempSet = [_reuseableItems filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"reuseIdentifer == %@", identifer]]; MinScrollMenuItem *item = tempSet.anyObject; return item; }
也能夠不用謂詞查詢,直接使用循環查找,由於重用池每種標識符item通常只須要一個就足夠了。因此Set元素個數比較少。
(4)數據刷新reloadData方法實現
思路以下圖所示;
數據刷新.png
(5)點擊item回調響應方法實現:
Menu內的實現:
首先,將遍歷以前保存選中狀態的字典,若是value是YES,則修改成NO
第二,遍歷屏幕顯示item數組visibleItems,將item的isSelected屬性設爲NO
第三,將選中的item狀態改成選中,經過tag值獲取index索引,保存到緩存字典中。
第四,回調代理方法,通知控制器
貼上具體代碼:
- (void)tapItem: (UITapGestureRecognizer *)tapGst { [_infoDict enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, NSNumber *obj, BOOL * _Nonnull stop) { *stop = obj.boolValue; if (*stop) { _infoDict[key] = @(NO); } }]; for (MinScrollMenuItem *item in _visibleItems) { item.isSelected = NO; [_infoDict setObject:@(NO) forKey:@(item.tag-ITEMTAG)]; } if ([tapGst.view isKindOfClass:[UIView class]]) { UIView *tempView = tapGst.view; MinScrollMenuItem *item = (MinScrollMenuItem *)tempView; if ([item isKindOfClass:[MinScrollMenuItem class]]) { item.isSelected = YES; [_infoDict setObject:@(YES) forKey:@(item.tag-ITEMTAG)]; if (self.delegate && [self.delegate respondsToSelector:@selector(scrollMenu:didSelectedItem:atIndex:)]) { [self.delegate scrollMenu:self didSelectedItem:item atIndex:item.tag - ITEMTAG]; } } } }
Item內的實現:
首先,item添加一個選中狀態的CALayer類做爲屬性,建立好添加到item的layer上,不要忘記了設置爲隱藏,hidden=YES。再提供一個對外開放的BOOL值isSelected屬性。
第二,重寫isSelected屬性set方法,被選中時修改layer的hidden爲NO便可。
後記:tableview的四個基本代理方法已經都實現了。經過這個橫向滾動的類tableview控件,對tableview的工做原理有了更深一層的認識,固然tableview還有不少功能沒有實現,可是基本框架完成了,一些功能性的東西后面陸續能夠添加。你們能夠經過github地址:MinScrollMenu 下載源碼查看,不嫌棄的點個星吧:)