iOS 模仿 簡書個(gè)人頁的滑動(dòng)

作為簡書的用戶,個(gè)人頁面肯定是常進(jìn)入的,昨天在群里聽朋友討論說,這個(gè)頁面實(shí)現(xiàn)有點(diǎn)繞,我很好奇,同時(shí)貌似我們后期也會(huì)有類似的需求,特此先學(xué)習(xí)模仿下。

簡書個(gè)人主頁

第一階段

我第一個(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、是否外面需要刷新呢?簡書這邊是沒有刷新的
ScrollerView 中的 subTableView 亂滑動(dòng)

當(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)下來。

ScrollView 不連貫

此時(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 為最底部的方法,而且封裝好啦

重點(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容