UITableview嵌套UITableView案例實(shí)踐(仿淘寶商品詳情頁(yè)實(shí)現(xiàn))

一、案例演示

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)的效果如下:

仿造淘寶詳情頁(yè)

二、項(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è)

歡迎加好友、一起交流。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容