TFPopup一个可完全自定义动画和弹框的小工具

需求

开发app这么久一直被弹框问题掐着脖子,每次有弹框的需求都得浪费一天半天去写弹出动画,而且每次改需求都比较头疼。

前一段时间忙里偷闲写了一个弹框的小工具,它把动画和弹框本身分开,弹框和动画完全不耦合,任何一个view都可以通过一两行代码弹出来,在这里分享一下,希望可以帮助到大家。

不说废话先看一些效果图(图有点多,没找到简书横向排列图片的写法,所以有点长。)

github传送门TFPopup

自定义-旋转缩放动画

cus-1.gif

自定义-自定义弹出方式和子视图动画
cus-2.gif

自定义-下拉弹簧效果
cus-3.gif

自定义-mask水波纹效果
cus-4.gif

自定义-使用此工具二次封装的toastview
cus-5.gif

自定义-自定义动画和自定义背景和自定义背景动画
cus-6.gif

例子-无任何动画弹出方式

exc1.gif

例子-滑动弹出方式-向上滑动
exc2.gif

例子-滑动弹出方式-向下滑动
exc3.gif

例子-缩放弹出方式
exc4.gif

例子-折叠弹出方式
exc5.gif

例子-折叠弹出方式-带背景
exc6.gif

例子-泡泡弹出方式
exc7.gif

例子-滑动弹出方式-向左滑动
exc8.gif

以上效果只是做一个示例,因为它基本上可以说是可以完全自定义动画,所以如果有特殊的弹出方式的需求,可以通过在此基础上自定义动画实现。

接下来以一个效果图->代码的方式简单介绍一下这个工具的用法:

普通弹框:

01.gif

对应代码:

//你已经写好了一个view
CGRect frame = CGRectMake(0, 0, 300, 200);
PopTableView *tableView = [[PopTableView alloc]initWithFrame:frame];
//将这个view弹出来
[tableView tf_showNormal:self.view animated:NO];

渐隐弹框:

02.gif

对应代码:

CGRect frame = CGRectMake(0, 0, 300, 200);
PopTableView *tableView = [[PopTableView alloc]initWithFrame:frame];
//将上面的NO改为YES
[tableView tf_showNormal:self.view animated:YES];

缩放方式:

03.gif

对应代码:

CGRect frame = CGRectMake(0, 0, 300, 200);
PopTableView *tableView = [[PopTableView alloc]initWithFrame:frame];
[tableView tf_showScale:self.view];

滑动方式(默认支持上下左右四个方向):

04.gif

对应代码:

CGRect frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 200);
PopTableView *tableView = [[PopTableView alloc]initWithFrame:frame];
[tableView tf_showSlide:self.view direction:PopupDirectionTop];

展开方式(默认支持上下左右四个方向):

05.gif

06.gif

对应代码:

    CGRect frame = CGRectMake(0, 100, [UIScreen mainScreen].bounds.size.width, 200);
    PopTableView *tableView = [[PopTableView alloc]initWithFrame:frame];
    [tableView tf_showFold:self.view targetFrame:frame
                 direction:PopupDirectionTop popupParam:nil];

泡泡方式(默认支持八个方向):

07.gif

对应代码:

    CGRect frame = CGRectMake(0, 0, 150, 200);
    PopTableView *tableView = [[PopTableView alloc]initWithFrame:frame];
    [tableView tf_showBubble:self.view basePoint:self.view.center
             bubbleDirection:PopupDirectionBottomLeft popupParam:nil];

以上示例代码中基本上都有一个参数popupParam:TFPopupParam,比如上面的代码。
这个参数可填可不填,但是如果想要更改更多效果的话,可以通过设置参数来达到目的,来简单看一下这个参数类的.h,因为有注释,就不赘述了


/* 【全局属性】时间
 duration,动画时间default=0.3
 autoDissmissDuration,自动消失时间,default=0
 */
@property(nonatomic,assign)NSTimeInterval duration;
@property(nonatomic,assign)NSTimeInterval autoDissmissDuration;


/* 【全局属性】背景
 disuseBackground,不使用背景,default=NO,如果设为YES则自定义代理的方法也会被禁止
 defaultBackgroundView,不自定义背景时的默认背景view,default=0.3alpha的黑色视图,如果自定义背景此值为nil
 backgroundColorClear,默认背景的背景色设为透明
 disuseBackgroundTouchHide,禁止点击背景消失弹框
 disuseShowBackgroundAlphaAnimation,不使用显示时的渐隐动画default=NO
 disuseShowBackgroundAlphaAnimation,不使用消失时的渐隐动画default=NO
 */
@property(nonatomic,assign)BOOL disuseBackground;
@property(nonatomic,assign)BOOL defaultBackgroundView;
@property(nonatomic,assign)BOOL backgroundColorClear;
@property(nonatomic,assign)BOOL disuseBackgroundTouchHide;
@property(nonatomic,assign)BOOL disuseShowBackgroundAlphaAnimation;
@property(nonatomic,assign)BOOL disuseHideBackgroundAlphaAnimation;


/* 【全局属性】弹框渐隐动画
 disuseShowPopupAlphaAnimation,不使用弹出时的渐隐动画default=NO
 disuseHidePopupAlphaAnimation,不使用消失时的渐隐动画default=NO
 disuseShowPopupFrameAnimation,不使用弹出时的frame动画default=NO
 disuseHidePopupFrameAnimation,不使用消失时的frame动画default=NO
 */
@property(nonatomic,assign)BOOL disuseShowPopupAlphaAnimation;
@property(nonatomic,assign)BOOL disuseHidePopupAlphaAnimation;
@property(nonatomic,assign)BOOL disuseShowPopupFrameAnimation;
@property(nonatomic,assign)BOOL disuseHidePopupFrameAnimation;


/* 【全局属性】弹框尺寸和区域
 popupAreaRect,弹框所在区域尺寸,default=父视图的bounds,此属性决定了弹框的位置和大小计算
 popupSize,弹框尺寸,default=弹框的frame.size
 offset,弹框偏移,offset.x正为右移,offset.y正为下移
 popOriginFrame,弹框初始frame
 popTargetFrame,弹框动画后的frame
 keepPopupOriginFrame,是否保持原有frame不变,值为YES时,不对frame进行计算和操作
 */
@property(nonatomic,assign)CGRect popupAreaRect;
@property(nonatomic,assign)CGSize popupSize;
@property(nonatomic,assign)CGPoint offset;
@property(nonatomic,assign)CGRect popOriginFrame;
@property(nonatomic,assign)CGRect popTargetFrame;
@property(nonatomic,assign)BOOL keepPopupOriginFrame;


/* 【全局属性】属性动画 和 默认缩放动画冲突
 showKeyPath,显示时的属性动画keyPath
 showFromValue,显示动画初始值
 showToValue,,显示动画结束值
 hideKeyPath,隐藏时的属性动画keyPath
 hideFromValue,隐藏动画初始值
 hideToValue,,隐藏动画结束值
 */
@property(nonatomic,  copy)NSString *showKeyPath;
@property(nonatomic,strong)id showFromValue;
@property(nonatomic,strong)id showToValue;
@property(nonatomic,  copy)NSString *hideKeyPath;
@property(nonatomic,strong)id hideFromValue;
@property(nonatomic,strong)id hideToValue;


/* 【泡泡弹出方式属性】
 basePoint,弹出泡泡基于哪个点
 bubbleDirection,弹出泡泡的方向,八个方向
 */
@property(nonatomic,assign)CGPoint basePoint;
@property(nonatomic,assign)PopupDirection bubbleDirection;


/* 【全局属性】遮罩
 maskShowFromPath,必须参数
 maskShowToPath,必须参数
 maskHideFromPath,非必填,为空时值为maskShowToPath
 maskHideToPath,非必填,为空时值为maskShowFromPath
 */
@property(nonatomic,strong)UIBezierPath *maskShowFromPath;
@property(nonatomic,strong)UIBezierPath *maskShowToPath;
@property(nonatomic,strong)UIBezierPath *maskHideFromPath;
@property(nonatomic,strong)UIBezierPath *maskHideToPath;

通过修改以上参数可以在上面例子基础上实现更多的效果,比如:

/* 【全局属性】属性动画 和 默认缩放动画冲突
 showKeyPath,显示时的属性动画keyPath
 showFromValue,显示动画初始值
 showToValue,,显示动画结束值
 hideKeyPath,隐藏时的属性动画keyPath
 hideFromValue,隐藏动画初始值
 hideToValue,隐藏动画结束值
 */
@property(nonatomic,  copy)NSString *showKeyPath;
@property(nonatomic,strong)id showFromValue;
@property(nonatomic,strong)id showToValue;
@property(nonatomic,  copy)NSString *hideKeyPath;
@property(nonatomic,strong)id hideFromValue;
@property(nonatomic,strong)id hideToValue;

上面代码中修改这三个值就可以实现对应keypath的动画

@property(nonatomic,  copy)NSString *showKeyPath;
@property(nonatomic,strong)id showFromValue;
@property(nonatomic,strong)id showToValue;

上面例子中缩放弹出的动画也就是下面三行代码实现效果

self.popupParam.showKeyPath = @"transform.scale";
self.popupParam.showFromValue = @(0.0);
self.popupParam.showToValue = @(1.0);

再比如上面效果动画中的水波纹效果也只是用到了下面几个属性(具体实现请看github的demo)

/* 【全局属性】遮罩
 maskShowFromPath,必须参数
 maskShowToPath,必须参数
 maskHideFromPath,非必填,为空时值为maskShowToPath
 maskHideToPath,非必填,为空时值为maskShowFromPath
 */
@property(nonatomic,strong)UIBezierPath *maskShowFromPath;
@property(nonatomic,strong)UIBezierPath *maskShowToPath;
@property(nonatomic,strong)UIBezierPath *maskHideFromPath;
@property(nonatomic,strong)UIBezierPath *maskHideToPath;

自定义动画

尽管这个工具已经支持了很多种默认的弹出动画方式,但是也不能解所有的需求,所以可以在此基础上完全自定义动画。
自定义动画的原理是,实现对应的代理,在代理方法中重写动画逻辑。先看几个代理


/* 动画过程代理
 * 代理包含了动画执行过程中的各个阶段,在这个阶段中你可以获取和修改参数以达到修改动画的目的
 */
@protocol TFPopupDelegate<NSObject>
@optional;
/* 监听获取参数前后
 * popup:弹框本类
 * tf_popupViewWillGetConfiguration:开始获取所有配置前调用
 * tf_popupViewDidGetConfiguration:获取完所有配置后调用
 */
- (void)tf_popupViewWillGetConfiguration:(UIView *)popup;
- (void)tf_popupViewDidGetConfiguration:(UIView *)popup;

/* 弹框show过程
 * popup:弹框本类
 * tf_popupViewWillShow:开始执行动画代码前调用
 * tf_popupViewDidShow:执行完动画代码后调用,此函数早于tf_popupViewShowAnimationDidFinish调用
 * tf_popupViewShowAnimationDidFinish:动画执行完成时调用
 */
- (BOOL)tf_popupViewWillShow:(UIView *)popup;//默认YES
- (void)tf_popupViewDidShow:(UIView *)popup;
- (void)tf_popupViewShowAnimationDidFinish:(UIView *)popup;


/* 弹框即将执行hide代码
 * popup:弹框本类
 * return:返回是否继续动画,如返回NO则需要自己定义消失方式和自己调用tf_remove
 */
- (BOOL)tf_popupViewWillHide:(UIView *)popup;//默认YES
/* 弹框已经执行完hide代码
 * popup:弹框本类
 * return:返回如果没有任何动画的情况下,是否移除背景和弹框
 */
- (BOOL)tf_popupViewDidHide:(UIView *)popup;//默认YES
/* hide动画执行完成时调用
 * popup:弹框本类
 * return:返回是否移除背景和弹框,如果返回NO则需要自己调用tf_remove
 */
- (BOOL)tf_popupViewHideAnimationDidFinish:(UIView *)popup;//默认YES

/* 点击默认背景时调用
 * popup:弹框本类
 * return:返回点击背景后是否调用tf_hide
 */
- (BOOL)tf_popupViewBackgroundDidTouch:(UIView *)popup;//默认YES

@end

以上代码都有注释就不细说了,只简单说一下它的原理,弹框弹出分为几个阶段

1.准备阶段
(1)获取弹框必要参数,比如:弹框的父试图,展示或消失时透明度变化配置,位置变化配置,基本动画设置等。
(2)获取背景层配置,比如:是否需要背景,使用自定义背景还是默认背景,背景点击事件
(3)补充参数,如果上述两步没有配置足够的参数,则在准备阶段设置默认参数,如默认动画时间,其他动画设置是否齐全
2.弹出阶段
(1)弹出前会有willshow代理回调,在此你基本可以获取弹出需要的所有参数,如需要改参数在这里修改即可
(2)弹出调用完成会有didshow代理回调,此时虽调用完成,但动画尚未完成
(3)动画完成会有animationdidfinish代理回调,此时动画已经完成,在此代理中做弹出后的后续工作即可
3.消失阶段
消失阶段和弹出阶段基本一样,都有三个代理方法,不同的是,消失阶段在动画结束后会自动将弹框移除父视图,如果需要完全自定义消失动画,需要自己调用移除视图的方法(工具中已提供方法,看demo即可)
4.其他
代理方法中有些代理方法需要返回bool值,这个值告诉工具是否要继续弹出、消失流程,如果要继续返回YES否则返回NO

自定义背景

我们可以实现自定义动画,但是有时候背景也需要自定义,比如在折叠弹框中,需要背景只覆盖下半部分,因为上半部分还需要切换菜单,或者说产品需要一个好看弹出背景。
自定义背景需要实现自定义背景代理:

/* 弹出背景视图代理,询问背景视图的数量,view,和frame
 * 弹框本身自动实现,默认为一个背景black-0.3透明度,尺寸为弹出区域尺寸,若将代理设为其他类则其他类需要实现以下方法
 */
@protocol TFPopupBackgroundDelegate<NSObject>
/* 弹出背景视图设置
 * popup:弹框本类
 * tf_popupBackgroundViewCount:背景视图的数量
 * backgroundViewAtIndex:返回每一个背景视图
 * backgroundViewFrameAtIndex:返回每一个背景视图的frame
 */
@optional
- (NSInteger)tf_popupBackgroundViewCount:(UIView *)popup;//默认1
//默认UIButton背景色为black-0.3透明度
- (UIView *)tf_popupView:(UIView *)popup backgroundViewAtIndex:(NSInteger)index;
- (CGRect)tf_popupView:(UIView *)popup backgroundViewFrameAtIndex:(NSInteger)index;//默认弹框区域大小
@end

同样的上面代理中都有注释就不在多赘述下面会实际结合例子,介绍下自定义动画和自定义背景具体怎么用

自定义动画和背景效果

cus-6.gif

代码实现


- (NSInteger)tf_popupBackgroundViewCount:(UIView *)popup{
    return 16;
}
- (UIView *)tf_popupView:(UIView *)popup backgroundViewAtIndex:(NSInteger)index{
    UIButton *bg = [UIButton buttonWithType:UIButtonTypeCustom];
    bg.backgroundColor = [UIColor colorWithRed:arc4random_uniform(255)/255.0 green:0 blue:0 alpha:1];
    bg.alpha = 0;
    return bg;
}
- (CGRect)tf_popupView:(UIView *)popup backgroundViewFrameAtIndex:(NSInteger)index{
    CGSize ss = [UIScreen mainScreen].bounds.size;
    CGFloat x = 0;
    CGFloat y = 0;
    if (index % 4 == 0) {
        x = 0;
    }else{
        x = (index % 4) / 4.0 * ss.width;
    }
    y = floor((index /4.0)) * (ss.height * 0.25);
    CGRect ff = CGRectMake(x, y, ss.width * 0.25, ss.height * 0.25);
    return ff;
}

- (BOOL)tf_popupViewWillShow:(UIView *)popup{
   
    CAAnimationGroup *group = [self groupAnimationIsShow:YES dur:0.3];
    group.openOberserBlock = YES;
    [group observerAnimationDidStop:^(CAAnimation *anima, BOOL finished) {
        
    }];
    [popup.layer addAnimation:group forKey:nil];
    
    for (NSInteger i = 0; i < popup.extension.backgroundViewCount; i++) {
        CGFloat delay = 0.03;
        UIView *view = [popup.extension.backgroundViewArray objectAtIndex:i];
        [UIView animateWithDuration:0.3 delay:(delay * i) options:UIViewAnimationOptionCurveLinear animations:^{
            view.alpha = 1;
        } completion:^(BOOL finished) {
            view.alpha = 1;
        }];
        CAAnimationGroup *gp = [self groupAnimationIsShow:YES dur:0.3];
        gp.beginTime = CACurrentMediaTime() + delay * i;
        gp.openOberserBlock = YES;
        [view.layer addAnimation:gp forKey:nil];
    }
    return YES;
}

- (BOOL)tf_popupViewWillHide:(UIView *)popup{
    
    CAAnimationGroup *group = [self groupAnimationIsShow:NO dur:0.3];
    group.openOberserBlock = YES;
    [group observerAnimationDidStop:^(CAAnimation *anima, BOOL finished) {
        [popup removeFromSuperview];
    }];
    [popup.layer addAnimation:group forKey:nil];
    
    for (NSInteger i = 0; i < popup.extension.backgroundViewCount; i++) {
        CGFloat delay = 0.03;
        UIView *view = [popup.extension.backgroundViewArray objectAtIndex:i];
        [UIView animateWithDuration:0.3 delay:(delay * i) options:UIViewAnimationOptionCurveLinear animations:^{
            view.alpha = 0;
        } completion:^(BOOL finished) {
            [view removeFromSuperview];
        }];
        
        CAAnimationGroup *gp = [self groupAnimationIsShow:NO dur:0.3];
        gp.beginTime = CACurrentMediaTime() + delay * i;
        gp.openOberserBlock = YES;
        [view.layer addAnimation:gp forKey:nil];
    }
    return YES;
}

上面代码中分别实现了自定义背景的三个代理方法和自定义动画的两个代理方法

原理:
1.先创建16个背景试图并设置好frame初始alpha=0
2.在将要展示动画的代理中拿到这十六个背景视图,每个视图有时差的添加一个缩放动画组
3.消失时候的动画同展示时候的动画道理一样。
4.demo中有效果示例和源码。

使用技巧

1.工具原理:使用category给弹框本身添加展示和消失的方法使它自己可以直接调用对应方法弹出来,弹出过程中需要设置很多参数,这时候设置了三个delegate分别用来:获取弹出参数,获取自定义背景,监听弹出过程。这三个代理默认设置为其本身,并且本身实现所有代理方法。所以要实现自定义动画效果需要自己实现相关代理方法,自己实现有两种方式可选:(1)将某个delegate设置为其他类,并在其他类实现对应代理的所有方法(2)新建一个类继承原有的弹框,并在子类重写需要重写的某个方法

最后贴出小工具的git地址:TFPopup
如果有帮助,请支持一下~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335