一、案例演示
IOS中提供的UITableView功能非常強(qiáng)大,section提供分組,cell提供顯示,幾乎可以應(yīng)付絕大部分場(chǎng)景。最近想模仿舊版的淘寶的商品詳情頁(yè)(最新的淘寶詳情頁(yè)商品詳情和圖文詳情是兩個(gè)頁(yè)面)寫(xiě)一個(gè)Demo,后來(lái)發(fā)現(xiàn)單純使用UITableView來(lái)布局是比較困難的。因?yàn)榕f版的淘寶詳情頁(yè)中,最外層的View肯定是一個(gè)UITableView,但是內(nèi)層的Tab中,圖文介紹、商品詳情和評(píng)價(jià)三個(gè)Tab對(duì)應(yīng)的內(nèi)容非常豐富,如果你把這三塊內(nèi)容放在一個(gè)section中的話(huà),將導(dǎo)致數(shù)據(jù)組織非常困難,并且UI的靈活度也大大降低。所以最后準(zhǔn)備嘗試使用UITableView嵌套UITableView的方式來(lái)組織UI,最外層是一個(gè)UITableView,三個(gè)Tab其實(shí)是一個(gè)橫向ScrollView,這個(gè)ScrollView里面包含三個(gè)UITableView。并且Tab中的內(nèi)容采用動(dòng)態(tài)可配置話(huà)的方式生成(下面詳解)。實(shí)現(xiàn)的效果如下:

二、項(xiàng)目詳解
2.1、大體思路
使內(nèi)層的UITableView(TAB欄里面)和外層的UITableView同時(shí)響應(yīng)用戶(hù)的手勢(shì)滑動(dòng)事件。當(dāng)用戶(hù)從頁(yè)面頂端從下往上滑動(dòng)到TAB欄的過(guò)程中,使外層的UITableView跟隨用戶(hù)手勢(shì)滑動(dòng),內(nèi)層的UITableView不跟隨手勢(shì)滑動(dòng)。當(dāng)用戶(hù)繼續(xù)往上滑動(dòng)的時(shí)候,讓外層的UITableView不跟隨手勢(shì)滑動(dòng),讓內(nèi)層的UITableView跟隨手勢(shì)滑動(dòng)。反之從下往上滑動(dòng)也一樣。
如上圖所示,外層的section0為價(jià)格區(qū),可以自定義。section1為sku區(qū),也可以自定義。section2為T(mén)AB區(qū)域,該區(qū)域采用Runtime反射機(jī)制,動(dòng)態(tài)配置完成。
2.2、具體實(shí)現(xiàn)
2.2.1、YXIgnoreHeaderTouchTableView
我們頂部的圖片其實(shí)是覆蓋在外層UITableView的tableHeaderView的下面,我們把tableHeaderView設(shè)置為透明。這樣實(shí)現(xiàn)是為了方便我們?cè)诨瑒?dòng)的時(shí)候,動(dòng)態(tài)的改變圖片的寬高,實(shí)現(xiàn)列表頭部能夠動(dòng)態(tài)拉伸的效果。但是我們對(duì)于UITableView不做處理的時(shí)候,該圖片是無(wú)法響應(yīng)點(diǎn)擊事件的,因?yàn)楸籺ableHeaderView提前消費(fèi)了。所以我們要不讓tableHeaderView不響應(yīng)點(diǎn)擊事件。我們?cè)赮XIgnoreHeaderTouchTableView的實(shí)現(xiàn)文件中重寫(xiě)以下方法。
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
if (self.tableHeaderView && CGRectContainsPoint(self.tableHeaderView.frame, point)) {
return NO;
}
return [super pointInside:point withEvent:event];
}
2.2.2、 YXIgnoreHeaderTouchAndRecognizeSimultaneousTableView
該文件繼承于YXIgnoreHeaderTouchTableView,除此之外,主要是為了讓外層的UITableView能夠顯示外層UITableView的滑動(dòng)事件。我們需要實(shí)現(xiàn)以下代理方法。
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
2.2.3、YXTabView
該文件是TAB區(qū)域主文件,顯示的標(biāo)題的內(nèi)容都是通過(guò)以下字典動(dòng)態(tài)生成。
if(section==2){
NSArray *tabConfigArray = @[@{
@"title":@"圖文介紹",
@"view":@"PicAndTextIntroduceView",
@"data":@"圖文介紹的數(shù)據(jù)",
@"position":@0
},@{
@"title":@"商品詳情",
@"view":@"ItemDetailView",
@"data":@"商品詳情的數(shù)據(jù)",
@"position":@1
},@{
@"title":@"評(píng)價(jià)(273)",
@"view":@"CommentView",
@"data":@"評(píng)價(jià)的數(shù)據(jù)",
@"position":@2
}];
YXTabView *tabView = [[YXTabView alloc] initWithTabConfigArray:tabConfigArray];
[cell.contentView addSubview:tabView];
}
title:TAB每個(gè)Item的標(biāo)題。
view:TAB每個(gè)Item的內(nèi)容。
data:TAB每個(gè)Item內(nèi)容渲染需要的數(shù)據(jù)。
position:TAB的位置。從0開(kāi)始。
該TAB其實(shí)是有YXTabTitleView(標(biāo)題欄)和一個(gè)橫向的ScrollView(內(nèi)層多個(gè)UITableView的容器)構(gòu)成。內(nèi)層多個(gè)UITableView通過(guò)以上配置文件動(dòng)態(tài)生成。如下如示:
for (int i=0; i<tabConfigArray.count; i++) {
NSDictionary *info = tabConfigArray[i];
NSString *clazzName = info[@"view"];
Class clazz = NSClassFromString(clazzName);
YXTabItemBaseView *itemBaseView = [[clazz alloc] init];
[itemBaseView renderUIWithInfo:tabConfigArray[i]];
[_tabContentView addSubview:itemBaseView];
}
2.2.4、YXTabItemBaseView
該文件是內(nèi)層UITableView都應(yīng)該繼承的BaseView,在該View中我們?cè)O(shè)置了內(nèi)層UITableView具體在什么時(shí)機(jī)不響應(yīng)用戶(hù)滑動(dòng)事件,什么時(shí)機(jī)應(yīng)該響應(yīng)用戶(hù)滑動(dòng)事件,什么時(shí)間通知外層UITableView響應(yīng)滑動(dòng)事件等等功能。
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
if (!self.canScroll) {
[scrollView setContentOffset:CGPointZero];
}
CGFloat offsetY = scrollView.contentOffset.y;
if (offsetY<0) {
[[NSNotificationCenter defaultCenter] postNotificationName:kLeaveTopNotificationName object:nil userInfo:@{@"canScroll":@"1"}];
[scrollView setContentOffset:CGPointZero];
self.canScroll = NO;
self.tableView.showsVerticalScrollIndicator = NO;
}
}
2.2.5、PicAndTextIntroduceView、ItemDetailView、CommentView
這三個(gè)文件都繼承于YXTabItemBaseView,但是在該文件中我們只需要注意UI的渲染就可以了。響應(yīng)事件的管理都在YXTabItemBaseView做好了。
就拿PicAndTextIntroduceView.m來(lái)看,基本上都是UI代碼:
@implementation PicAndTextIntroduceView
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 30;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 50.;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellId = @"cellId";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
}
cell.textLabel.text = self.info[@"data"];
return cell;
}
@end
2.2.6、內(nèi)外層滑動(dòng)事件的響應(yīng)和傳遞
外層UITableView在初始化的時(shí)候 需要監(jiān)聽(tīng)一個(gè)NSNotification,該通知是內(nèi)層UITableView傳遞給外層的,傳遞時(shí)機(jī)為從上往下活動(dòng),當(dāng)TAB欄取消置頂?shù)臅r(shí)候。通知外層UITableView可以開(kāi)始滾動(dòng)了。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(acceptMsg:) name:kLeaveTopNotificationName object:nil];
-(void)acceptMsg : (NSNotification *)notification{
//NSLog(@"%@",notification);
NSDictionary *userInfo = notification.userInfo;
NSString *canScroll = userInfo[@"canScroll"];
if ([canScroll isEqualToString:@"1"]) {
_canScroll = YES;
}
}
在scrollViewDidScroll方法中,需要實(shí)時(shí)監(jiān)控外層UItableView的滑動(dòng)時(shí)機(jī)。也要在適當(dāng)時(shí)機(jī)發(fā)送NSNotification給內(nèi)層UItableView,通知內(nèi)層UITableView是否可以滑動(dòng)。
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGFloat tabOffsetY = [_tableView rectForSection:2].origin.y-kTopBarHeight;
CGFloat offsetY = scrollView.contentOffset.y;
_isTopIsCanNotMoveTabViewPre = _isTopIsCanNotMoveTabView;
if (offsetY>=tabOffsetY) {
scrollView.contentOffset = CGPointMake(0, tabOffsetY);
_isTopIsCanNotMoveTabView = YES;
}else{
_isTopIsCanNotMoveTabView = NO;
}
if (_isTopIsCanNotMoveTabView != _isTopIsCanNotMoveTabViewPre) {
if (!_isTopIsCanNotMoveTabViewPre && _isTopIsCanNotMoveTabView) {
//NSLog(@"滑動(dòng)到頂端");
[[NSNotificationCenter defaultCenter] postNotificationName:kGoTopNotificationName object:nil userInfo:@{@"canScroll":@"1"}];
_canScroll = NO;
}
if(_isTopIsCanNotMoveTabViewPre && !_isTopIsCanNotMoveTabView){
//NSLog(@"離開(kāi)頂端");
if (!_canScroll) {
scrollView.contentOffset = CGPointMake(0, tabOffsetY);
}
}
}
}
三、完整源碼下載地址
github下載地址:https://github.com/yixiangboy/YX_UITableView_IN_UITableView
如果對(duì)你有用,star一下吧。
四、聯(lián)系方式
新浪微博
github
簡(jiǎn)書(shū)首頁(yè)
歡迎加好友、一起交流。