一個(gè)實(shí)用的TableView滑動(dòng)效果

首先看效果

QQ20201024-140424-HD的副本.gif

關(guān)鍵點(diǎn)

自定義手勢(shì)

很明顯的需要一個(gè)自定義拖動(dòng)手勢(shì)

//拖拽手勢(shì)
    UIPanGestureRecognizer *tableViewPan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureAction:)];
    tableViewPan.delegate = self;
    self.animationPanGes = tableViewPan;
    [self addGestureRecognizer:tableViewPan];

跟隨拖拽手勢(shì)移動(dòng)視圖

這里移動(dòng)視圖并不一定要是tableView,雖然拖拽手勢(shì)是添加在tableView上面,但是可以設(shè)置為tableView的父視圖,具體自行實(shí)踐

@protocol MTVerticalAnimationTableViewDelegate <NSObject>
/**跟隨拖動(dòng)手勢(shì)移動(dòng)的View*/
- (UIView *)mt_verticalAnimationTableViewPanGestureMovingView:(MTVerticalAnimationTableView *)tableView;
@end

這里通過(guò)代理來(lái)控制移動(dòng)的視圖

處理自定義拖動(dòng)手勢(shì)與tableView的拖動(dòng)手勢(shì)沖突

  1. 設(shè)置tableView的拖動(dòng)手勢(shì)在自定義拖動(dòng)手勢(shì)失效之后生效
//tableView滾動(dòng)手勢(shì)依賴(lài)于自定義手勢(shì)失敗
[self.panGestureRecognizer requireGestureRecognizerToFail:tableViewPan];
  1. 手勢(shì)代理設(shè)置自定義手勢(shì)失效條件
//列表滾動(dòng)  只響應(yīng)tableView拖動(dòng)手勢(shì)
if (self.contentOffset.y > 0) {
    return NO;
}

//contentOffset.y = 0時(shí)  1.向上拖動(dòng)甥绿,tableView拖動(dòng)手勢(shì)響應(yīng)  2.向下拖動(dòng)盛卡,自定義拖動(dòng)手勢(shì)響應(yīng)
if (self.contentOffset.y == 0 && translatedPoint.y < 0) {
    return NO;
}
  1. 設(shè)置自定義手勢(shì)生效時(shí)的操作
/**拖動(dòng)手勢(shì)中*/
CGRect frame = movingView.frame;
frame.origin.y += translation.y;
//改變frame
movingView.frame = frame
  1. 設(shè)置自定義手勢(shì)結(jié)束時(shí)的操作
/**拖動(dòng)手勢(shì)結(jié)束*/
CGRect frame = movingView.frame;
frame.origin.y += translation.y;

 //確定最終Y
    MTAnimationTableViewState state = MTAnimationTableViewStateBottom;
    if (frame.origin.y > self.centerStateOriginY && frame.origin.y <= self.bottomStateOriginY) {
        frame.origin.y = self.bottomStateOriginY;
    } else {
        if (self.state == MTAnimationTableViewStateTop && frame.origin.y > self.topStateOriginY) {
            frame.origin.y = self.centerStateOriginY;
            state = MTAnimationTableViewStateCenter;
        } else {
            frame.origin.y = self.topStateOriginY;
            state = MTAnimationTableViewStateTop;
        }
    }

//改變frame
movingView.frame = frame

所有代碼

MTVerticalAnimationTableView.h文件

#import <UIKit/UIKit.h>

/*
 MTAnimationTableViewStateTop
 1.列表向上滑動(dòng) 正常滑動(dòng)
 2.列表向下滑動(dòng) a.沒(méi)有滑動(dòng)到頂部骑素,正常滑動(dòng)  b.滑動(dòng)到頂部,整體向下
 
 MTAnimationTableViewStateCenter
    1.列表向上滑動(dòng) 禁止滑動(dòng) 整體向上
    2.列表向下滑動(dòng) 禁止滑動(dòng) 整體向下
 
 MTAnimationTableViewStateBottom
 */
typedef enum : NSUInteger {
    MTAnimationTableViewStateTop,
    MTAnimationTableViewStateCenter,
    MTAnimationTableViewStateBottom,
} MTAnimationTableViewState;

@protocol MTVerticalAnimationTableViewDelegate;

@interface MTVerticalAnimationTableView : UITableView
@property (nonatomic, weak)id<MTVerticalAnimationTableViewDelegate>animationDelegate;
/**位置枚舉*/
@property (nonatomic, assign, readonly)MTAnimationTableViewState state;

/**Top PanGestureMovingView的frame.origin.y   默認(rèn) = (安全區(qū)域高度+導(dǎo)航欄高度)*/
@property (nonatomic, assign)CGFloat topStateOriginY;
/**Center PanGestureMovingView的frame.origin.y  默認(rèn) = (安全區(qū)域高度+導(dǎo)航欄高度-topStateOriginY)/2*/
@property (nonatomic, assign)CGFloat centerStateOriginY;
/**Bottom PanGestureMovingView的frame.origin.y  默認(rèn) = (屏幕高度)*/
@property (nonatomic, assign)CGFloat bottomStateOriginY;

/**創(chuàng)建*/
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style state:(MTAnimationTableViewState)state;

/**顯示*/
- (void)showAnimationTableViewCompletion:(dispatch_block_t)completion;

/**隱藏*/
- (void)hiddenAnimationTableViewCompletion:(dispatch_block_t)completion;
@end


@protocol MTVerticalAnimationTableViewDelegate <NSObject>
/**跟隨拖動(dòng)手勢(shì)移動(dòng)的View*/
- (UIView *)mt_verticalAnimationTableViewPanGestureMovingView:(MTVerticalAnimationTableView *)tableView;

@optional
/**拖動(dòng)手勢(shì)移動(dòng)的View動(dòng)畫(huà)結(jié)束*/
- (void)mt_verticalAnimationTableViewPanGestureEnded:(MTVerticalAnimationTableView *)tableView;
@end

MTVerticalAnimationTableView.m文件

#import "MTVerticalAnimationTableView.h"

@interface MTVerticalAnimationTableView ()<UIGestureRecognizerDelegate>
/**拖動(dòng)手勢(shì)*/
@property (nonatomic, strong)UIPanGestureRecognizer *animationPanGes;

/**位置枚舉*/
@property (nonatomic, assign, readwrite)MTAnimationTableViewState state;
@end

@implementation MTVerticalAnimationTableView
- (void)dealloc{
    NSLog(@"MTAnimationTableView dealloc");
}

#pragma mark - Public
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style state:(MTAnimationTableViewState)state{
    self = [super initWithFrame:frame style:style];
    if (self) {
        self.state = state;
        //設(shè)置默認(rèn)限制
        [self setupAnimationTableViewStateOriginY];
        //設(shè)置頭部
        [self setupSubViews:style];
    }
    return self;
}

/**顯示*/
- (void)showAnimationTableViewCompletion:(dispatch_block_t)completion {
    /**已經(jīng)彈出不做處理*/
    if (self.state != MTAnimationTableViewStateBottom) {
        if (completion) {
            completion();
        }
        return;
    }
    
    if ([self.animationDelegate respondsToSelector:@selector(mt_verticalAnimationTableViewPanGestureMovingView:)]) {
        CGRect frame = [self _panGestureMovingViewFrame];
        frame.origin.y = self.centerStateOriginY;
        [self _animationFrame:frame changeState:MTAnimationTableViewStateCenter completion:completion];
    }
}

/**隱藏*/
- (void)hiddenAnimationTableViewCompletion:(dispatch_block_t)completion {
    CGRect frame = [self _panGestureMovingViewFrame];
    frame.origin.y = self.bottomStateOriginY;
    [self _animationFrame:frame changeState:MTAnimationTableViewStateBottom completion:completion];
}

#pragma mark - UI
/**設(shè)置默認(rèn)限制*/
- (void)setupAnimationTableViewStateOriginY {
    CGFloat ret = 64;
    if (@available(iOS 11.0,*)) {
        CGFloat navBarHeight = 44.0f;
        ret = [UIApplication sharedApplication].statusBarFrame.size.height + navBarHeight;

    }
    self.topStateOriginY = ret;
    self.centerStateOriginY = ([UIScreen mainScreen].bounds.size.height+ret)/2;
    self.bottomStateOriginY = [UIScreen mainScreen].bounds.size.height;
}

- (void)setupSubViews:(UITableViewStyle)style {
    //拖拽手勢(shì)
    UIPanGestureRecognizer *tableViewPan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureAction:)];
    tableViewPan.delegate = self;
    self.animationPanGes = tableViewPan;
    [self addGestureRecognizer:tableViewPan];
    
    //tableView滾動(dòng)手勢(shì)依賴(lài)于自定義手勢(shì)失敗
    [self.panGestureRecognizer requireGestureRecognizerToFail:tableViewPan];
}

#pragma mark - Action
/**拖動(dòng)手勢(shì)*/
- (void)panGestureAction:(UIPanGestureRecognizer *)panGes {
    CGPoint translation = [panGes translationInView:panGes.view];
    //拖動(dòng)一直是在遞增,所以每次都要用setTranslation:inView:方法置為0
    [panGes setTranslation:CGPointMake(0, 0) inView:panGes.view];
    
    if(panGes.state ==UIGestureRecognizerStateBegan) {
        
    } else if(panGes.state ==UIGestureRecognizerStateChanged) {
        [self panGestureRecognizerChanging:translation];
    } else if(panGes.state ==UIGestureRecognizerStateEnded) {
        [self panGestureRecognizerDidChange:translation];
    }
}

/**拖動(dòng)手勢(shì)中*/
- (void)panGestureRecognizerChanging:(CGPoint)translation {
    CGRect frame = [self _panGestureMovingViewFrame];
    frame.origin.y += translation.y;
    
    //不小于最小Y
    if ([self _compareCGfloat:frame.origin.y CGFloat2:self.topStateOriginY] == NSOrderedAscending) {
        frame.origin.y = self.topStateOriginY;
    }
    //不大于最大Y
    if ([self _compareCGfloat:frame.origin.y CGFloat2:self.bottomStateOriginY] == NSOrderedDescending) {
        frame.origin.y = self.bottomStateOriginY;
    }

    //改變frame
    if ([self.animationDelegate respondsToSelector:@selector(mt_verticalAnimationTableViewPanGestureMovingView:)]) {
        [self.animationDelegate mt_verticalAnimationTableViewPanGestureMovingView:self].frame = frame;
    }
}

/**拖動(dòng)手勢(shì)結(jié)束*/
- (void)panGestureRecognizerDidChange:(CGPoint)translation {
    CGRect frame = [self _panGestureMovingViewFrame];
    frame.origin.y += translation.y;
    //不小于最小Y
    if ([self _compareCGfloat:frame.origin.y CGFloat2:self.topStateOriginY] == NSOrderedAscending) {
        frame.origin.y = self.topStateOriginY;
    }
    //不大于最大Y
    if ([self _compareCGfloat:frame.origin.y CGFloat2:self.bottomStateOriginY] == NSOrderedDescending) {
        frame.origin.y = self.bottomStateOriginY;
    }
    
    //    //確定最終Y
    //    MTAnimationTableViewState state = MTAnimationTableViewStateBottom;
    //    if (frame.origin.y < self.centerStateOriginY) {
    //        frame.origin.y = self.topStateOriginY;
    //        state = MTAnimationTableViewStateTop;
    //    } else if(frame.origin.y >= self.centerStateOriginY && frame.origin.y < (self.bottomStateOriginY+self.centerStateOriginY)/2) {
    //        frame.origin.y = self.centerStateOriginY;
    //        state = MTAnimationTableViewStateCenter;
    //    } else {
    //        frame.origin.y = self.bottomStateOriginY;
    //    }
    
    //確定最終Y
    MTAnimationTableViewState state = MTAnimationTableViewStateBottom;
    if ([self _compareCGfloat:frame.origin.y CGFloat2:self.centerStateOriginY] == NSOrderedDescending && [self _compareCGfloat:frame.origin.y CGFloat2:self.bottomStateOriginY] != NSOrderedDescending) {
        frame.origin.y = self.bottomStateOriginY;
    } else {
        if (self.state == MTAnimationTableViewStateTop && [self _compareCGfloat:frame.origin.y CGFloat2:self.topStateOriginY] == NSOrderedDescending) {
            frame.origin.y = self.centerStateOriginY;
            state = MTAnimationTableViewStateCenter;
        } else {
            frame.origin.y = self.topStateOriginY;
            state = MTAnimationTableViewStateTop;
        }
    }
    
    //動(dòng)畫(huà)
    [self _animationFrame:frame changeState:state completion:^{
        //動(dòng)畫(huà)結(jié)束
        if ([self.animationDelegate respondsToSelector:@selector(mt_verticalAnimationTableViewPanGestureEnded:)]) {
            [self.animationDelegate mt_verticalAnimationTableViewPanGestureEnded:self];
        }
    }];
}

#pragma mark - Private
- (void)_animationFrame:(CGRect)frame changeState:(MTAnimationTableViewState)changeState completion:(dispatch_block_t)completion{
    [UIView animateWithDuration:0.3 animations:^{
        //改變frame
        if ([self.animationDelegate respondsToSelector:@selector(mt_verticalAnimationTableViewPanGestureMovingView:)]) {
            [self.animationDelegate mt_verticalAnimationTableViewPanGestureMovingView:self].frame = frame;
        }
    } completion:^(BOOL finished) {
        if (finished) {
            self.state = changeState;
        
            if (completion) {
                completion();
            }
        }
    }];
}

- (CGRect)_panGestureMovingViewFrame {
    if ([self.animationDelegate respondsToSelector:@selector(mt_verticalAnimationTableViewPanGestureMovingView:)]) {
        UIView *moveingView = [self.animationDelegate mt_verticalAnimationTableViewPanGestureMovingView:self];
        return moveingView.frame;
    }
    return CGRectZero;
}

/**CGFloat比較方法  解決精度丟失問(wèn)題*/
- (NSComparisonResult)_compareCGfloat:(CGFloat)float1 CGFloat2:(CGFloat)float2 {
    /**精度差值  決定是否相等*/
    float positiveDifference = 0.001;
    float negativeDifference = -0.001;
    CGFloat result = float1 - float2;
    if (result < negativeDifference) {
        return NSOrderedAscending;
    } else if(result > positiveDifference) {
        return NSOrderedDescending;
    } else {
        return NSOrderedSame;
    }
}

#pragma mark - UIGestureRecognizerDelegate
/**處理自定義手勢(shì)生效失效*/
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    if (gestureRecognizer == self.animationPanGes && self.state == MTAnimationTableViewStateTop) {
        CGPoint translatedPoint = [self.animationPanGes translationInView:self.animationPanGes.view];
        
        //列表滾動(dòng)  只響應(yīng)tableView拖動(dòng)手勢(shì)
        if (self.contentOffset.y > 0) {
            return NO;
        }
        
        //contentOffset.y = 0時(shí)  1.向上拖動(dòng)闽颇,tableView拖動(dòng)手勢(shì)響應(yīng)  2.向下拖動(dòng),自定義拖動(dòng)手勢(shì)響應(yīng)
        if (self.contentOffset.y == 0 && translatedPoint.y < 0) {
            return NO;
        }
    }

    return YES;
}

- (void)setTopStateOriginY:(CGFloat)topStateOriginY {
    _topStateOriginY = ceil(topStateOriginY);
}

@end
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末寄锐,一起剝皮案震驚了整個(gè)濱河市兵多,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌橄仆,老刑警劉巖剩膘,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異盆顾,居然都是意外死亡怠褐,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)您宪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)奈懒,“玉大人,你說(shuō)我怎么就攤上這事蚕涤】鹋猓” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵揖铜,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我达皿,道長(zhǎng)天吓,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任峦椰,我火速辦了婚禮龄寞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘汤功。我一直安慰自己物邑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布滔金。 她就那樣靜靜地躺著色解,像睡著了一般。 火紅的嫁衣襯著肌膚如雪餐茵。 梳的紋絲不亂的頭發(fā)上科阎,一...
    開(kāi)封第一講書(shū)人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音忿族,去河邊找鬼锣笨。 笑死蝌矛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的错英。 我是一名探鬼主播入撒,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼椭岩!你這毒婦竟也來(lái)了衅金?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤簿煌,失蹤者是張志新(化名)和其女友劉穎氮唯,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體姨伟,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡惩琉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了夺荒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瞒渠。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖技扼,靈堂內(nèi)的尸體忽然破棺而出伍玖,到底是詐尸還是另有隱情,我是刑警寧澤剿吻,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布窍箍,位于F島的核電站,受9級(jí)特大地震影響丽旅,放射性物質(zhì)發(fā)生泄漏椰棘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一榄笙、第九天 我趴在偏房一處隱蔽的房頂上張望邪狞。 院中可真熱鬧,春花似錦茅撞、人聲如沸帆卓。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)剑令。三九已至,卻和暖如春蠕蚜,著一層夾襖步出監(jiān)牢的瞬間尚洽,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工靶累, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腺毫,地道東北人癣疟。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像潮酒,于是被迫代替她去往敵國(guó)和親睛挚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354