2018年6月25日更新:
群里有人提到點按header上的UIButton無法橫向滾動的問題
8月9日更新:
1、代碼重構(gòu),三個的tableView分別解耦到三個控制器中管理
2、添加MJRefresh,點擊右上角開關(guān)可動態(tài)添加/移除刷新控件,最新代碼可到GitHub 上下載

這兩天比較閑,簡單實現(xiàn)了一下個人中心頁面scrollView嵌套的效果,也就是下邊這個頁面,大家感受一下先:

首先講下實現(xiàn)思路,很多人看到這個界面覺得是多個scrollView嵌套實現(xiàn)的,其實蘋果不推薦scrollView的嵌套,特別是有同方向滾動行為的scrollView嵌套,因為系統(tǒng)也不知道你當(dāng)前想要哪個scrollView滾動。那我就在想能不能想個辦法繞過這個問題,最終我采取了這樣的方案,底部一個橫向滑動的scrollView,上邊放著左中右三個tableView,這樣就避開了scrollView同方向滑動沖突問題,每個tableView上都有個空白的headerView占位使用(看了下邊的講解就明白了),然后再在控制器view上方一個view做真實的headerView,放控制器的view上是為了不讓它隨任何一個tableView滑動而滑動,而是在某些時候改變其y值來制造和三個tableView共同滾動的假象!接下來請看具體講解和代碼實現(xiàn):
控制器的view上是個橫向滑動的scrollView,上邊放著三個tableView,主要代碼如下
// 底部橫向滑動的scrollView
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:scrollView];
scrollView.backgroundColor = [UIColor colorWithWhite:0.998 alpha:1];
// 綁定代理
scrollView.delegate = self;
// 設(shè)置滑動區(qū)域
scrollView.contentSize = CGSizeMake(3 * WZBScreenWidth, 0);
scrollView.pagingEnabled = YES;
self.scrollView = scrollView;
// 創(chuàng)建三個tableView
self.leftTableView = [self tableViewWithX:0];
self.centerTableView = [self tableViewWithX:WZBScreenWidth];
self.rightTableView = [self tableViewWithX:WZBScreenWidth * 2];
// 創(chuàng)建tableView
- (UITableView *)tableViewWithX:(CGFloat)x {
UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectMake(x, 0, WZBScreenWidth, WZBScreenHeight - 0)];
[self.scrollView addSubview:tableView];
tableView.backgroundColor = [UIColor colorWithWhite:0.998 alpha:1];
tableView.showsVerticalScrollIndicator = NO;
// 代理&&數(shù)據(jù)源
tableView.delegate = self;
tableView.dataSource = self;
// 創(chuàng)建一個假的headerView,高度等于控制器view上那個headerView的高度(這里暫且寫死150+44)
UIView *headerView = [[UIView alloc] initWithFrame:(CGRect){0, 0, WZBScreenWidth, 194}];
tableView.tableHeaderView = headerView;
return tableView;
}
然后為每個tableView創(chuàng)建一個假的headerView,高度等于控制器view上那個headerView的高度(這里暫且寫死150+44),為什么這樣做?因為我要在控制器的view上放一個headerView,這樣就能方便的控制headerView的y值,使其達(dá)到預(yù)期的效果,這樣也創(chuàng)造出了一個假象,就是其實上邊的headerView跟三個tableView沒有什么關(guān)系,只是在滑動的時候動態(tài)的改變headerView的y值,當(dāng)tableView滑動到某個位置的時候,讓其能夠停留在這個位置。監(jiān)聽tableView滑動的代碼下邊會講到。如圖:

headerView分為兩部分,一部分是上邊的死數(shù)據(jù),其實就是一個自定義view,上邊幾個label。下邊部分是個類似segmeng的自定義控件,是用我之前寫的WZBSegmentedControl實現(xiàn)的,具體請看干貨!老司機(jī)工作中用到的自定義控件,總有一個適合你的(一),創(chuàng)建代碼為:
// 創(chuàng)建segmentedControl
WZBSegmentedControl *sectionView = [WZBSegmentedControl segmentWithFrame:(CGRect){0, 150, WZBScreenWidth, 44} titles:@[@"動態(tài)", @"文章", @"更多"] tClick:^(NSInteger index) {
// 改變scrollView的contentOffset
self.scrollView.contentOffset = CGPointMake(index * WZBScreenWidth, 0);
// 刷新最大OffsetY
[self reloadMaxOffsetY];
}];
// 賦值給全局變量
self.sectionView = sectionView;
// 設(shè)置其他顏色
[sectionView setNormalColor:[UIColor blackColor] selectColor:[UIColor redColor] sliderColor:[UIColor redColor] edgingColor:[UIColor clearColor] edgingWidth:0];
// 去除圓角
sectionView.layer.cornerRadius = sectionView.backgroundView.layer.cornerRadius = .0f;
// 加兩條線
for (NSInteger i = 0; i < 2; i++) {
UIView *line = [UIView new];
line.backgroundColor = WZBColor(228, 227, 230);
line.frame = CGRectMake(0, 43.5 * i, WZBScreenWidth, 0.5);
[sectionView addSubview:line];
}

重點是下邊的一些代理方法的監(jiān)聽:
一、正在滾動的時候(本demo定死了headerView的高度為150+44)
如果發(fā)現(xiàn)是tableView在滑動,判斷滑動偏移量是否大于150,如果不大于150,則同步三個tableView的偏移量為當(dāng)前tableView的偏移量,這樣做是讓三個tableView看起來像是在同步移動,然后改變headerView的y值(讓headerView也往上走)。如果偏移量大于等于150,則讓headerView的y值等于-150,就相當(dāng)于停留在這個位置了。如果發(fā)現(xiàn)當(dāng)前滾動的是底部橫向那個scrollView,則需要同步segment的偏移量,代碼如下:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// 如果當(dāng)前滑動的是tableView
if ([scrollView isKindOfClass:[UITableView class]]) {
CGFloat contentOffsetY = scrollView.contentOffset.y;
// 如果滑動沒有超過150
if (contentOffsetY < 150) {
// 讓這三個tableView的便宜量相等
self.leftTableView.contentOffset = self.centerTableView.contentOffset = self.rightTableView.contentOffset = scrollView.contentOffset;
// 改變headerView的y值
CGRect frame = self.headerView.frame;
CGFloat y = -self.rightTableView.contentOffset.y;
frame.origin.y = y;
self.headerView.frame = frame;
// 一旦大于等于150了,讓headerView的y值等于-150,就停留在上邊了
} else if (contentOffsetY >= 150) {
CGRect frame = self.headerView.frame;
frame.origin.y = -150;
self.headerView.frame = frame;
}
}
if (scrollView == self.scrollView) {
// 改變segmentdControl
[self.sectionView setContentOffset:(CGPoint){scrollView.contentOffset.x / 3, 0}];
return;
}
}
這一步做完之后其實是不夠嚴(yán)謹(jǐn)?shù)?,事實證明scrollViewDidScroll:這個代理方法調(diào)用的并不足夠頻繁,導(dǎo)致一些bug,就是三個tableView并沒有同步,可能其中一個tableView滑了上去,而另外兩個還沒有跟上去。這時候如果左右滑動,就會暴露這個問題,如圖:


二、解決辦法就是在開始左右滑動的時候,同步一下三個tableView的偏移量
// 開始拖拽
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
if (scrollView == self.scrollView) {
// 刷新最大OffsetY
[self reloadMaxOffsetY];
}
}
// 刷新最大OffsetY,讓三個tableView同步
- (void)reloadMaxOffsetY {
// 計算出最大偏移量
CGFloat maxOffsetY = MAXValue(self.leftTableView.contentOffset.y, self.centerTableView.contentOffset.y, self.rightTableView.contentOffset.y);
// 如果最大偏移量大于150,處理下每個tableView的偏移量
if (maxOffsetY > 150) {
if (self.leftTableView.contentOffset.y < 150) {
self.leftTableView.contentOffset = CGPointMake(0, 150);
}
if (self.centerTableView.contentOffset.y < 150) {
self.centerTableView.contentOffset = CGPointMake(0, 150);
}
if (self.rightTableView.contentOffset.y < 150) {
self.rightTableView.contentOffset = CGPointMake(0, 150);
}
}
}
這時候,就解決了那個bug,基本功能也完成了,接下來解釋一下位于導(dǎo)航欄上的頭像處理:
為了能讓頭像自由的放大/縮小,并動態(tài)改變其y值,達(dá)到預(yù)期效果,我用了兩個view實現(xiàn),一個是底部普通的view做導(dǎo)航欄titleView,然后再在這個view上放imageView展示頭像,然后監(jiān)聽
scrollViewDidScroll:方法,一邊改變imageView的大小,一邊改變y值,就實現(xiàn)了簡書的那種效果,注意最大和最小的邊界值,代碼如下:
// 加載頭部頭像
UIView *avatarView = [[UIView alloc] initWithFrame:(CGRect){0, 0, 35, 35}];
avatarView.backgroundColor = [UIColor clearColor];
UIImageView *avatar = [[UIImageView alloc] initWithFrame:(CGRect){0, 26.5, 35, 35}];
avatar.image = [UIImage imageNamed:@"demo1"];
avatar.layer.masksToBounds = YES;
avatar.layer.cornerRadius = 35 / 2;
[avatarView addSubview:avatar];
self.navigationItem.titleView = avatarView;
avatar.transform = CGAffineTransformMakeScale(2, 2);
self.avatar = avatar;
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// 處理頂部頭像
CGFloat scale = scrollView.contentOffset.y / 80;
// 如果大于80,==1,小于0,==0
if (scrollView.contentOffset.y > 80) {
scale = 1;
} else if (scrollView.contentOffset.y <= 0) {
scale = 0;
}
// 縮放
self.avatar.transform = CGAffineTransformMakeScale(2 - scale, 2 - scale);
// 同步y(tǒng)值
CGRect frame = self.avatar.frame;
frame.origin.y = (1 - scale) * 8;
self.avatar.frame = frame;
}
打完收工,最后附上源碼:JianShuMianPage 歡迎star
我的更多文章:老司機(jī)帶你飛
喜歡可以隨手點個喜歡或者關(guān)注一下哦!
您的支持是我最大的動力??!