作為簡書的用戶,個(gè)人頁面肯定是常進(jìn)入的,昨天在群里聽朋友討論說,這個(gè)頁面實(shí)現(xiàn)有點(diǎn)繞,我很好奇,同時(shí)貌似我們后期也會(huì)有類似的需求,特此先學(xué)習(xí)模仿下。
第一階段
我第一個(gè)思路就是:
- 最底層是 一個(gè) UIScrollerView
- 上部是一個(gè) HeaderView 加一個(gè)自定義 選擇器
- 下部是一個(gè) UIScrollerView
- 下部的 UIScrollerView 中再套三個(gè) UITableView
但是大致做出后,就立馬就遇到幾個(gè)問題:
- 1、下部 SubTableView 和 最外層的 UIScrollerView 的效應(yīng)重疊,就是在底部的時(shí)候,想滑動(dòng)里面的最底層的 ScrollerView 的時(shí)候,外面那個(gè)卻滑動(dòng)啦
- 2、還有一個(gè)就是 下部 UIScrollerView 中三個(gè) SubUITableView 滑動(dòng)起來亂走。
- 3、傳值的不方便,如果是死的還好
- 4、是否外面需要刷新呢?簡書這邊是沒有刷新的
當(dāng)然最主要是解決前面兩個(gè)問題:
- 如何當(dāng)在底部的時(shí)候不滑動(dòng)下面的subTableView?
方法是從scrollViewDidScroll 滑動(dòng)中判斷位置,然后設(shè)置是否可以滑動(dòng)
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat maxMoveY = kHeaderViewHeight - kHeaderTitleHeight; // 最大滑動(dòng)距離
// 判斷其位置 讓其底部是否可以滑動(dòng)
if (self.backScrollView.contentOffset.y >= maxMoveY ) {
self.trendsTableView.scrollEnabled = YES;
self.articleTableView.scrollEnabled = YES;
self.moreTableView.scrollEnabled = YES;
}
else {
self.trendsTableView.scrollEnabled = NO;
self.articleTableView.scrollEnabled = NO;
self.moreTableView.scrollEnabled = NO;
}
// 當(dāng)滑到頭部時(shí),不讓其subTableView 滑動(dòng)
if (self.trendsTableView.contentOffset.y < 0) {
self.trendsTableView.scrollEnabled = NO;
}
if (self.articleTableView.contentOffset.y < 0) {
self.articleTableView.scrollEnabled = NO;
}
if (self.moreTableView.contentOffset.y < 0) {
self.moreTableView.scrollEnabled = NO;
}
}
當(dāng)然這里面還要判斷諸多咯,(判斷底部橫向ScrollerView),此處只是針對上述那個(gè)問題而做的。
- 如何不出現(xiàn) SCrollerView 亂動(dòng)呢?
方法是 KVO 出發(fā),通過 contentOffset 判斷,當(dāng)位置超出界限就停止滑出。
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(__unused id)object
change:(NSDictionary *)change
context:(void *)context {
CGFloat offsetX = self.bottomScrollerView.contentOffset.x;
CGFloat offsetY = self.bottomScrollerView.contentOffset.y;
CGFloat contentMaxWidth = self.bottomScrollerView.contentSize.width - SCREEN_WIDTH;
CGFloat contentMaxHeight = self.bottomScrollerView.contentSize.height - kHeaderTitleHeight - kNavBarHeight;
if (offsetX < 0 ) {
[self.bottomScrollerView setContentOffset:CGPointMake(0, offsetY)];
}
if (offsetY < 0) {
[self.bottomScrollerView setContentOffset:CGPointMake(offsetX, 0)];
}
if (offsetX >contentMaxWidth) {
[self.bottomScrollerView setContentOffset:CGPointMake(contentMaxWidth, offsetY)];
}
if (offsetY > contentMaxHeight) {
[self.bottomScrollerView setContentOffset:CGPointMake(offsetX, contentMaxHeight)];
}
}
不過實(shí)際中,只需要判斷左右不超出界限,基本就可以啦,繼續(xù)嘗試...
self.bottomScrollerView.bounces = NO; // 記住設(shè)置無反彈
這個(gè)是否有反彈效果,跟具體設(shè)置有關(guān),像上面那樣設(shè)置死了,為了不防止不錯(cuò)先錯(cuò)誤,需要設(shè)置為NO,目前此處只是思路,后續(xù)繼續(xù)優(yōu)化中。
第二階段
上述已經(jīng)實(shí)現(xiàn)的差不多啦,到目前來說,目前來說最大的問題就是當(dāng)?shù)撞康膕ubTableView 滑到上面后,要整體一起滑動(dòng)下來,單純滑動(dòng)subTableView 不好弄,需要拖動(dòng) headerView 才可以滑動(dòng)下來。
此時(shí),用一個(gè)雞賊的方法,當(dāng)其滑動(dòng)到那個(gè)位置 直接切換成為 最外層 ScrollerView 滑動(dòng)的狀態(tài):
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat maxMoveY = kHeaderViewHeight - kHeaderTitleHeight; // 最大滑動(dòng)距離
if (self.trendsTableView.contentOffset.y < 0) {
self.trendsTableView.scrollEnabled = NO;
[self.backScrollView setContentOffset:CGPointMake(0, maxMoveY - 1)];
}
if (self.articleTableView.contentOffset.y < 0) {
self.articleTableView.scrollEnabled = NO;
[self.backScrollView setContentOffset:CGPointMake(0, maxMoveY - 1)];
}
if (self.moreTableView.contentOffset.y < 0) {
self.moreTableView.scrollEnabled = NO;
[self.backScrollView setContentOffset:CGPointMake(0,maxMoveY - 1)];
}
}
這樣下來,大致確實(shí)是效果實(shí)現(xiàn)啦,但是上述那個(gè)問題會(huì)有一個(gè)停頓,感覺怪怪的,這里面有好多非系統(tǒng)的方法,還有諸多臨界條件的判斷,想著是否可以改善一下啦,特別是頭部滑動(dòng)和整體滑動(dòng)的連貫性啦,是否最外層 View 直接可以用 UICollectionView 或 UITableView 呢。
第三階段
對比他人寫的,在網(wǎng)上走了一圈,發(fā)現(xiàn)了幾個(gè)已經(jīng)寫好的,個(gè)人覺的實(shí)現(xiàn)的蠻好的,而且它們都一致的采用 UICollectionView 為最底部的方法,而且封裝好啦
- HHHorizontalPagingView
- SwipeTableView
- ZJScrollPageView: 功能很多,其中有一個(gè)就是簡書個(gè)人頁的這種,不錯(cuò)
重點(diǎn)看了HHHorizontalPagingView 的源碼,它們也有類似的處理方法解決我遇到的問題,其中一點(diǎn)用 hitTest 的方法,讓其做自己需要實(shí)現(xiàn)的事件,是可以在后期這樣的問題中用的:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *view = [super hitTest:point withEvent:event];
if ([view isDescendantOfView:self.headerView] || [view isDescendantOfView:self.segmentView]) {
}
return view;
}
isDescendantOfView:
方法來判定一個(gè)視圖是否在其父視圖的視圖層中,然后做相應(yīng)的處理。
用 UICollectionView 后,header 和 cell 的連接自然是沒有那個(gè)卡頓的,所以最后實(shí)現(xiàn)是 將上面那個(gè) ScrollerView 換成 UICollectionView 來做。
剛開始直接用UICollectionView 來寫,我感覺傳值老麻煩的,但是看完他們這邊封裝實(shí)現(xiàn)的后,發(fā)現(xiàn)他們都是將具體的 subTableView 抽離出來后,這樣更很直接傳值也碼洋麻煩。同時(shí)對比自己和他們寫的比較,發(fā)現(xiàn)自己缺少從封裝角度出發(fā)來實(shí)現(xiàn),當(dāng)然是否每一個(gè)需求實(shí)現(xiàn)都需要從封裝的角度出發(fā)呢?封裝抽取思想。。。
總的說來,如何去實(shí)現(xiàn),還是看自己項(xiàng)目的具體的需求,傳值是一個(gè)接口還是三個(gè)接口? 是否有刷新?這個(gè)真看情況而定,如想直接用,用上述兩個(gè)封裝好的之一,也是OK的。
備注:
https://github.com/weijingyunIOS/HHHorizontalPagingView
https://github.com/Roylee-ML/SwipeTableView