UIScrollView和UITableView的混合使用

需求效果:

2j.gif

demo拉取地址:demo

最簡(jiǎn)單的實(shí)現(xiàn)方式是,放一個(gè)tableview,這個(gè)tableview有一個(gè)headView,這個(gè)headview就是上圖所示的藍(lán)色頭部的View,但是這樣做的結(jié)果是,在tableView下拉刷新數(shù)據(jù)的時(shí)候,刷新動(dòng)畫(huà)會(huì)出現(xiàn)在headView的上方,這樣看著就異常令人難受了,當(dāng)時(shí)為了節(jié)省時(shí)間,就是這么實(shí)現(xiàn)的.現(xiàn)在有充足的時(shí)間的情況下,是不允許有瑕疵的.

于是就有了UIScrollView + UITableView的組合實(shí)現(xiàn)方案;

組合的方案實(shí)現(xiàn)遇到的問(wèn)題

UIScrollView和UITableView的組合使用問(wèn)題整理:
1.手勢(shì)沖突
2.tableview和scrollview一起滑動(dòng)
3.scrollview滑動(dòng)到底部之后,tableview上拉沒(méi)反應(yīng)
4.scrollview滑出了指定的頭部區(qū)域之后下拉沒(méi)反應(yīng)
5.tableview的下拉刷新無(wú)效

a.手勢(shì)事件的穿透

為解決手勢(shì)沖突問(wèn)題,自定義一個(gè)ScrollView,ArtScrollView,并將滑動(dòng)手勢(shì)的響應(yīng)傳遞到最下層的scrollview,
返回YES,則可以多個(gè)手勢(shì)一起觸發(fā)方法,返回NO則為互斥(比如外層UIScrollView名為mainScroll內(nèi)嵌的UIScrollView名為subScroll,當(dāng)我們拖動(dòng)subScroll時(shí),mainScroll是不會(huì)響應(yīng)手勢(shì)的(多個(gè)手勢(shì)默認(rèn)是互斥的),當(dāng)下面這個(gè)代理返回YES時(shí),subScroll和mainScroll就能同時(shí)響應(yīng)手勢(shì),同時(shí)滾動(dòng),這符合我們這里的需求)

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    return YES;
}

viewController中的所有代碼,這里可以忽略,demo中有全部的實(shí)現(xiàn)

#define SCREEN_WIDTH ([[UIScreen mainScreen] bounds].size.width)
#define SCREEN_HEIGHT ([[UIScreen mainScreen] bounds].size.height)

#import "ViewController.h"

//#import "RCDraggableButton.h"

//#import "YZDraggeMoveView.h"

//#import "YZClearUIView.h"

#import "Masonry.h"

//#import "SDWebImage.h"

#import "Toast.h"

#import "ArtScrollView.h"

#import "MJRefresh.h"



@interface ViewController ()<UIScrollViewDelegate,UITableViewDataSource,UITableViewDelegate>

@property(nonatomic,assign)CGFloat redHeight;
@property(nonatomic,assign)CGFloat blueHeight;
@property(nonatomic,strong)UIView * redView;
@property(nonatomic,strong)UIView * blueView;
@property(nonatomic,strong)ArtScrollView * scrollView;
@property(nonatomic,strong)UIScrollView * scrollInnerView;
@property(nonatomic,strong)NSMutableArray * array;
@property(nonatomic,strong)UITableView * tableView;

@property (nonatomic, assign) BOOL vccanScroll;   // 這里的布爾值類似一個(gè)鎖,初始化的默認(rèn)值是YES,當(dāng)用戶拖拽了tableview背后的scrollview并且拖拽到了scrollview的偏移距離大于blueview的時(shí)候vccanScroll值為NO,鎖住了scrollview,不讓scrollview進(jìn)行偏移,不管往上滑動(dòng)還是往下滑動(dòng),并將scrollview的偏移量改為blueview.height.當(dāng)且僅當(dāng)tableView.offset.y < 0的時(shí)候,也就是tableView被進(jìn)行了下拉操作的時(shí)候,這種情況下說(shuō)明tableview已經(jīng)進(jìn)入到了最頂端的位置,這時(shí)候,可以對(duì)scrollview進(jìn)行滑動(dòng)解鎖,也就是把vccanScroll的值再改為YES,這種情況下可以將scrollview可以正常上下滑動(dòng)了

@end



@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    _vccanScroll = YES;
    self.view.backgroundColor = [UIColor whiteColor];
    self.redHeight = 180;
    self.blueHeight = 200;
    self.array = [NSMutableArray arrayWithCapacity:0];
    
    for (int i = 0 ; i < 30; i ++) {
        [self.array addObject:[NSString stringWithFormat:@"need + %d",i]];
    }
    
    [self.view addSubview:self.redView];
    
    [self.view addSubview:self.scrollView];
    self.scrollView.backgroundColor = [UIColor purpleColor];

}


-(UIView *)redView {
    if (_redView == nil) {
        _redView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH  , _redHeight)];
        _redView.backgroundColor = [UIColor redColor];
    }
    return _redView;
}

-(UIView *)blueView {
    if (_blueView == nil) {
        _blueView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, _blueHeight)];
        _blueView.backgroundColor = [UIColor blueColor];
        UIButton * button = [UIButton buttonWithType:(UIButtonTypeCustom)];
        button.backgroundColor = [UIColor whiteColor];
        [button setTitle:@"change blueHeight" forState:(UIControlStateNormal)];
        [button setTitleColor:[UIColor redColor] forState:(UIControlStateNormal)];
        [button addTarget:self action:@selector(changeBlueValue) forControlEvents:(UIControlEventTouchUpInside)];
     //   [button mas_makeConstraints:^(MASConstraintMaker *make) {
       //     make.centerX.equalTo(_blueView.mas_centerX);
         //   make.centerY.equalTo(_blueView.mas_centerY);
           // make.width.equalTo(@200);
           // make.height.equalTo(@50);
        //}];
          button.frame = CGRectMake(0, 0, 200, 50);
        [_blueView addSubview:button];
    }
    return _blueView;
}

-(void)changeBlueValue {

    if (self.blueHeight == 240) {
        self.blueHeight = 200;
    }else
    {
        self.blueHeight = 240;
    }
    
    [self changeBindingFrame];
}

-(void)changeBindingFrame {
    _scrollView.contentSize = CGSizeMake(SCREEN_WIDTH, (SCREEN_HEIGHT - _redHeight) + _blueHeight);
    _scrollInnerView.frame = CGRectMake(0, 0, SCREEN_WIDTH, (SCREEN_HEIGHT - _redHeight) + _blueHeight);
    _scrollInnerView.contentSize = CGSizeMake(SCREEN_WIDTH, (SCREEN_HEIGHT - _redHeight) + _blueHeight);
    self.blueView.frame = CGRectMake(0, 0, SCREEN_WIDTH, _blueHeight);
    self.tableView.frame = CGRectMake(0, _blueHeight, SCREEN_WIDTH, SCREEN_HEIGHT - _redHeight);
}


-(ArtScrollView *)scrollView {
    if(_scrollView == nil){
        _scrollView = [[ArtScrollView alloc] initWithFrame:CGRectMake(0, _redHeight, SCREEN_WIDTH, (SCREEN_HEIGHT - _redHeight))];
        _scrollView.contentSize = CGSizeMake(SCREEN_WIDTH, (SCREEN_HEIGHT - _redHeight) + _blueHeight);
        _scrollView.backgroundColor = [UIColor grayColor];
        _scrollView.showsVerticalScrollIndicator = NO;
        _scrollView.delegate = self;
        _scrollView.bounces = NO;
        
        [_scrollView addSubview:self.scrollInnerView];
        
    }
    return _scrollView;
}

-(UIScrollView *)scrollInnerView {
    if (_scrollInnerView == nil) {
        _scrollInnerView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, (SCREEN_HEIGHT - _redHeight) + _blueHeight)];
        _scrollInnerView.contentSize = CGSizeMake(SCREEN_WIDTH, (SCREEN_HEIGHT - _redHeight) + _blueHeight);
        _scrollInnerView.showsVerticalScrollIndicator = NO;
        _scrollInnerView.delegate = self;
        _scrollInnerView.backgroundColor = [UIColor yellowColor];
        
        [_scrollInnerView addSubview:self.blueView];
        [_scrollInnerView addSubview:self.tableView];
    }
    return _scrollInnerView;
}

#pragma mark --------- tableView

- (UITableView *)tableView {
    if (_tableView == nil) {
        _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, _blueHeight, SCREEN_WIDTH, SCREEN_HEIGHT - _redHeight) style:(UITableViewStylePlain)];
        _tableView.delegate = self;
        _tableView.dataSource = self;
        _tableView.rowHeight = 55;
        MJRefreshNormalHeader *header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadData)];
        header.lastUpdatedTimeLabel.hidden = YES;
        _tableView.mj_header = header;
        _tableView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{
             [self loadDataMore];
         }];
        _tableView.showsVerticalScrollIndicator = NO;
    }
    
    return _tableView;
}

-(void)loadData {
    [self.tableView.mj_footer endRefreshing];
    [self.tableView.mj_header endRefreshing];
    [self.view makeToast:@"下拉刷新了一次" duration:1 position:CSToastPositionCenter style:[[CSToastStyle alloc] initWithDefaultStyle]];

}

-(void)loadDataMore {
    [self.tableView.mj_footer endRefreshing];
    [self.tableView.mj_header endRefreshing];
    [self.view makeToast:@"上拉加載了一次" duration:1 position:CSToastPositionCenter style:[[CSToastStyle alloc] initWithDefaultStyle]];
}



- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell * cell = [[UITableViewCell alloc] init];
    cell.textLabel.text = self.array[indexPath.row];
    return cell;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.array.count;
}



- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    
    CGFloat offsetY = scrollView.contentOffset.y;

    if (scrollView == self.scrollView) {
        
        CGFloat maxOffsetY = _blueHeight;
       if (offsetY >= maxOffsetY) {
            scrollView.contentOffset = CGPointMake(0, maxOffsetY);
            _vccanScroll = NO;
        }else {
            if (_vccanScroll == NO) {
                scrollView.contentOffset = CGPointMake(0, maxOffsetY);
            }
        }
    }else if(scrollView == self.tableView){
        CGPoint point = [scrollView.panGestureRecognizer translationInView:scrollView];
        CGFloat taboffsetY = point.y;
        if (offsetY < 0) {
            _vccanScroll = YES;
        }
           if (taboffsetY < 0) {
               if(self.scrollView.contentOffset.y < _blueHeight){
                   self.tableView.contentOffset = CGPointZero;
               }
           } else {
               if (offsetY > 0) {
                   self.scrollView.contentOffset = CGPointMake(0, _blueHeight);
               }else if (offsetY < 0){
                   if (self.scrollView.contentOffset.y > 0 && self.scrollView.contentOffset.y < _blueHeight) {
                       self.tableView.contentOffset = CGPointZero;
                   }
               }
              
           }
        
    }
}

b.scrollView的滑動(dòng)監(jiān)聽(tīng)

這里需要對(duì)scrollView的滑動(dòng)位置做監(jiān)聽(tīng),不但需要監(jiān)聽(tīng)scrollview的contentOffset.y值,還需要監(jiān)聽(tīng)tableView的contentOffset.y 同時(shí)需要做一些處理,因?yàn)閁ITableView繼承自UIScrollView,所以在tableView設(shè)置delegate時(shí)候,同樣能夠監(jiān)聽(tīng)到tableView的滑動(dòng)位置和狀態(tài)

UIKIT_EXTERN API_AVAILABLE(ios(2.0)) @interface UITableView : UIScrollView <NSCoding, UIDataSourceTranslating>

具體的監(jiān)聽(tīng)方法是scrollViewDidScroll:(UIScrollView *)scrollView,如果tableView和scrollView在同一個(gè)控制器中,可以簡(jiǎn)單的用
scrollView == self.tableView 和scrollView == self.scrollView來(lái)區(qū)分

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    CGFloat offsetY = scrollView.contentOffset.y;
}

c.tableview的上下拉操作的監(jiān)聽(tīng)

蘋(píng)果提供了一個(gè)很好用的方法來(lái)監(jiān)聽(tīng)scrollVIew的手勢(shì)操作,這里可以很方便的判斷出,當(dāng)前用戶對(duì)tableView的操作是上滑還是下滑,便于處理對(duì)應(yīng)的臨界值情況的效果

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
     CGPoint point = [scrollView.panGestureRecognizer translationInView:scrollView];
        CGFloat offsetY = point.y;
        if (offsetY < 0) {
            /// 上滑
            
        } else {
            /// 下滑
           
        }
}

d.底部scrollView的實(shí)際可滑動(dòng)狀態(tài)記錄

這里的布爾值類似一個(gè)鎖,初始化的默認(rèn)值是YES,當(dāng)用戶拖拽了tableview背后的scrollview并且拖拽到了scrollview的偏移距離大于blueview.height的時(shí)候vccanScroll值為NO,鎖住了scrollview,不讓scrollview進(jìn)行偏移,不管往上滑動(dòng)還是往下滑動(dòng),并將scrollview的偏移量改為blueview.height.當(dāng)且僅當(dāng)tableView.offset.y < 0的時(shí)候,也就是tableView被進(jìn)行了下拉操作的時(shí)候,這種情況下說(shuō)明tableview已經(jīng)進(jìn)入到了最頂端的位置,這時(shí)候,可以對(duì)scrollview進(jìn)行滑動(dòng)解鎖,也就是把vccanScroll的值再改為YES,這種情況下可以將scrollview可以正常上下滑動(dòng)了

@property (nonatomic, assign) BOOL vccanScroll;

網(wǎng)上搜索的答案都會(huì)稍微有點(diǎn)瑕疵,最后總結(jié)之后完善了一下,結(jié)果見(jiàn)上面的gif

流程監(jiān)聽(tīng)tableView的上下滑中參考:
監(jiān)聽(tīng)tableview滑動(dòng)

                                     ----------------- 真是嚼一路辛苦,飲一路汗水??

\color{red}{20210823更新:}

因?yàn)橹癲emo中viewDidLoad中使用的blueHeight初始化的值200.00沒(méi)有問(wèn)題
但是在實(shí)際接入中因?yàn)閎lueHeight的初始化值是根據(jù)一個(gè)label的高度動(dòng)態(tài)計(jì)算出來(lái)之后在viewDidLoad中賦值,我這里的blueHeight計(jì)算的打印值是225.027這樣的數(shù)據(jù),會(huì)導(dǎo)致scrollview上的blueView上滑出去之后tableView不會(huì)向上滑動(dòng)的bug

排查原因:
雖然blueHeight是225.027.但是在實(shí)際scrollview的- (void)scrollViewDidScroll:(UIScrollView *)scrollView 滑動(dòng)監(jiān)聽(tīng)方法中最大滑動(dòng)距離scrollView.contentOffset.y只能打印到225.00000,這種情況不知道是不是scrollview自身的bug
于是我將初始化的值寫(xiě)成定值225.000,不能下滑的異常情況就消失了

于是我的解決方案,在動(dòng)態(tài)計(jì)算完成blueHeight之后,將計(jì)算的最終值進(jìn)行取整之后再賦值給blueHeight可以避免這個(gè)問(wèn)題.

最后編輯于
?著作權(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)容