UIVeiw與UIScrollView嵌套,手勢/滾動沖突的解決

前言

在項目開發(fā)過程中,遇到一個這樣的需求:

  • 在視圖向上拖動時,使得視圖暫時不到頂,而是停留在某個高度處,

  • 此時如果向上拖動,則可以到達(dá)頂部

  • 達(dá)到頂部后,視圖中的子視圖才可以滾動(內(nèi)容足夠多)

  • 在向下拖動時,子視圖全部展示在頂部時,才允許外部視圖向下滾動

具體過程如圖:

image

問題

實現(xiàn)這一功能,只能通過UIScrollView的嵌套或者UIView中添加UIScrollView方式(我能想到的,有更好的做法,可以留言指出)

但不管采用哪種方案,有一個重要的問題需要處理:事件沖突

在一般的情況下,只有UIScrollView及其子類才具備滾動的功能,不管是UIScrollView的嵌套還是在普通的UIView中添加UIScrollView,都會出現(xiàn)沖突

方案


采用UIView中添加UICollectionView,然后給UICollectionView添加手勢,通過位置,攔截事件響應(yīng)的對象。

實現(xiàn)

  • 這里需要用到兩個重要的概念(非常關(guān)鍵)

1.這個是UIView關(guān)于手勢事件的拓展方法,返回值為NO時,表示不觸發(fā)手勢事件,該方法在此處運用時,即禁掉自定義添加的拖動手勢,響應(yīng)UICollecionView的滾動手勢,我們可以在這個方法中獲取到view的當(dāng)前位置,以及手勢的方向,根據(jù)這兩個因素,就可以決定是否響應(yīng)事件了
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
        UIPanGestureRecognizer *recognizer = (UIPanGestureRecognizer *)gestureRecognizer;
        if ([self.delegate respondsToSelector:@selector(fetchContainerViewWithStartY)]) {
            /*通過代理獲取view的當(dāng)前位置*/
            CGFloat startY = [self.baseCollectionViewdelegate fetchContainerViewWithStartY];
            CGPoint point = [recognizer translationInView:recognizer.view];//處理方向
            /*
             1.外層view是否在最頂部即frame的y值是否為0(在停止時,y值只有三種情況:0, 150, ScreenHeight-49)
             2.scrollview的偏移值 ( contentOffset.y < 0 偏下  >0 偏上)
             3.滑動方向:向上還是向下 ( point.y > 0:向下, point.y > 0:向上)
             */
if (startY <= 20) {
                if (point.y > 0) {//向下
                    if (self.contentOffset.y > 0) {// <0 偏下(目前的設(shè)置,不可能出現(xiàn))   >0 偏上
                        return YES;
                    } else {
                        return NO;
                    }
                } else {
                    return YES;
                }
            } else {
                return NO;
            }
        }
    }
    return YES;
}

2.是否支持多種手勢事件共存,這個也是解決問題的關(guān)鍵,這里需要返回YES,允許支持,我們對UICollectionView添加了自定義的拖動手勢以及UICollectionView本身自帶了系統(tǒng)的事件
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer

  • 在自定義的手勢事件中,根據(jù)手勢的狀態(tài),滾動方向以及位置來處理視圖的frame,在項目中還配合了一個拖動的小按鈕,所以在方法中,還有一個回調(diào)的事件


- (void)moveView:(UIPanGestureRecognizer *)recognizer {
    NSLog(@"手勢滑動 > recognizer.state : %ld", recognizer.state);
    if (CGRectGetMinY(self.frame) > 20) {
        CGPoint location = [recognizer translationInView:self.superview];
        CGFloat y = location.y + CGRectGetMinY(self.frame);
        if (recognizer.state == UIGestureRecognizerStateBegan) {
            self.startMoveViewFrameY = CGRectGetMinY(self.frame);
        } else if (recognizer.state == UIGestureRecognizerStateChanged) {
            if (y < 20) {
                y = 20;
            } else if (y > SCREEN_HEIGHT - 49.f) {
                y = SCREEN_HEIGHT - 49.f;
            }
            self.frame = CGRectMake(0, y, SCREEN_WIDTH, SCREEN_HEIGHT - 69.f);
            if ([self.delegate respondsToSelector:@selector(scrollWithY:panDirection:animations:)]) {
                //回調(diào)處理,拖動的小圖標(biāo)
                [self.delegate scrollWithY:y panDirection:HZPanDirectionNone animations:NO];
            }
        } else if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateCancelled) {
            CGPoint locan = [recognizer translationInView:self.collectionView];
            HZPanDirection panDirection = HZPanDirectionNone;
            if (self.startMoveViewFrameY < CGRectGetMinY(self.frame)) {//向下
                y = SCREEN_HEIGHT - 49.f;
                panDirection = HZPanDirectionDown;
            } else {
                panDirection = HZPanDirectionUp;
                if (y < 150) {
                    y = 20;
                } else {
                    y = 150.f;
                }
            }
            [UIView animateWithDuration:1.f animations:^{
                self.frame = CGRectMake(0, y, SCREEN_WIDTH, SCREEN_HEIGHT - 69.f);
            } completion:^(BOOL finished) {
                
            }];
            if ([self.delegate respondsToSelector:@selector(scrollWithY:panDirection:animations:)]) {
                //回調(diào)處理,拖動的小圖標(biāo)
                [self.delegate scrollWithY:y panDirection:panDirection animations:YES];
            }
        }
    } else if (CGRectGetMinY(self.frame) == 20) {
        CGPoint point = [recognizer translationInView:recognizer.view];//處理方向
        if (point.y > 0 && self.collectionView.contentOffset.y <= 0) {//向下
            [UIView animateWithDuration:1.f animations:^{
                self.frame = CGRectMake(0, SCREEN_HEIGHT - 49.f, SCREEN_WIDTH, SCREEN_HEIGHT - 69.f);
            } completion:^(BOOL finished) {
                
            }];
            if ([self.delegate respondsToSelector:@selector(scrollWithY:panDirection:animations:)]) {
                //回調(diào)處理,拖動的小圖標(biāo)
                [self.delegate scrollWithY:SCREEN_HEIGHT - 49.f panDirection:HZPanDirectionDown animations:YES];
            }
        }
    }
    [recognizer setTranslation:CGPointZero inView:self.superview];
}

這里是闡述比較核心的幾個方法,具體的使用,可以下載demo : 傳送門

TODO:接下來兩周會整理以下模塊

  • 自定義UICollectionViewFlowLayout以及轉(zhuǎn)場動畫,實現(xiàn)效果
    (圖片老是上傳失敗,直接過地址:http://7qnbrb.com1.z0.glb.clouddn.com/catransition.gif

  • 模塊化開發(fā)方案示例(每個模塊都是能單獨運行的工程,cocoapod管理)

  • 采用線程池隊列上傳文件的解決方案(優(yōu)勢:可以控制上傳文件的任務(wù)數(shù),避免一張一張上傳導(dǎo)致效率不高,也防止開啟大量線程同時上傳導(dǎo)致線程/內(nèi)存爆炸??)

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

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

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