这个仿写自今日头条应用内推送提示的AlertView
,请先看效果图,如下
弹窗的消失有这几种方法:
- 点击右上角的关闭符号
- 左右滑动一定范围
- 调用系统的
dismiss
方法。
使用到的主要方法:
-
UIView
的spring
动画 - 通过
runtime
对UITapGestrueRecognize
的封装 -
GCD
定时器
请看主要代码
- 手势操作部分
#pragma mark -- 配置手势操作
- (void)recognizeConfigs{
__weak typeof(self) weakSelf = self;
UIPanGestureRecognizer *panGestrue = [UIPanGestureRecognizer recognizeWithAction:^(id gestrueRecognize) {
UIPanGestureRecognizer *recognizer = (UIPanGestureRecognizer *)gestrueRecognize;
CGPoint translation = [recognizer translationInView:self];
CGPoint viewNewPoint = CGPointMake(recognizer.view.center.x + translation.x,recognizer.view.center.y + translation.y);
CGFloat y = isIphoneX?(kScreenHeight - ( 34 + 49 + 10 + AlertHeight/2)):(kScreenHeight - (49 + 10 + AlertHeight/2));
//TODO:给拖拽控件设置范围
viewNewPoint.y = y;//固定 y 值,控件只能在水平方向上移动
recognizer.view.center = viewNewPoint;
[recognizer setTranslation:CGPointZero inView:[UIApplication sharedApplication].keyWindow];
switch (recognizer.state) {
case UIGestureRecognizerStateBegan:
{
NSLog(@"手势已经开始");
if (weakSelf.gestrueDidStart) {
weakSelf.gestrueDidStart();//手势开始时,暂停定时器
}
}
break;
case UIGestureRecognizerStateEnded:
{
NSLog(@"手势已经结束");
if (recognizer.view.center.x < 30 || recognizer.view.center.x > kScreenWidth - 30) {
[self dismissWithNoAnimation];
if (weakSelf.gestrueDidEndWithTimerInvalidate) {//手势结束,且 alert 消失,此时销毁定时器
weakSelf.gestrueDidEndWithTimerInvalidate();
}
}else{
[UIView animateWithDuration:0.5
animations:^{
recognizer.view.center = CGPointMake(kScreenWidth/2, self.center.y);
}];
if (weakSelf.gestrueDidEnd) {//手势结束,但 alert 没有消失,此时继续开始计时操作
weakSelf.gestrueDidEnd();
}
}
}
break;
default:
break;
}
}];
panGestrue.cancelsTouchesInView = NO;
[self addGestureRecognizer:panGestrue];
}
- 弹窗的
dismiss
部分
#pragma mark -- 延迟执行的 dismiss,延迟时间由使用者自己设定
+ (void)dismissAlertWithDelay:(NSTimeInterval)delayTime complete:(void (^)(void))complete{
BottomAlertView *bSelf = [BottomAlertView shareView];
__weak typeof(bSelf) weakSelf = bSelf;
/*
*每次启动定时器,先销毁之前的定时器
*/
// if (bSelf.timer) {
// dispatch_cancel(bSelf.timer);
// }
if (delayTime < 1) {
delayTime = 1;
}
/*
*GCD定时器
*/
__block NSTimeInterval userDelayTime = delayTime;
dispatch_queue_t queue = dispatch_get_main_queue();
bSelf.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.0 * NSEC_PER_SEC));
uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
dispatch_source_set_timer(bSelf.timer, start, interval, 0);
dispatch_source_set_event_handler(bSelf.timer, ^{
NSLog(@"=== %f ===",userDelayTime);
if (userDelayTime == 0) {
// 取消定时器
dispatch_cancel(weakSelf.timer);
[weakSelf.baseView dismiss];
if (complete) {
complete();
}
}
userDelayTime--;
});
// 启动定时器
dispatch_resume(bSelf.timer);
[bSelf.baseView setGestrueDidStart:^{
//手势开始,暂停定时器
dispatch_suspend(weakSelf.timer);
}];
[bSelf.baseView setGestrueDidEnd:^{
//手势结束,但 alert 没有消失,此时启动定时器
dispatch_resume(weakSelf.timer);
}];
[bSelf.baseView setGestrueDidEndWithTimerInvalidate:^{
//手势结束,alert 也已经消失,此时销毁定时器
dispatch_cancel(weakSelf.timer);
if (complete) {
complete();
}
}];
}
在上面,我通过block
来监听手势的状态,通过这种状态来控制定时器对象的暂停、开启以及取消。
关于NSTimer
和GCD
定时器,GCD
定时器使用步骤比较复杂,但是相比NSTimer
,计时更加准确,且更方便对定时器对象进行暂停等操作。
顺便说一下,在UIView
动画的block
回调中为什么不需要使用weakSelf
?
在block
本身不被self
持有,而被别的对象持有,同时不产生循环引用的时候,就不需要使用weakself
了.最常见的代码就是UIView
的动画代码,我们在使用UIView
的 animationWithDuration:animation
方法做动画的时候,并不需要使用weakself
,因为引用持有关系是:
-
UIView
的某个负责动画的对象持有了block
,block
本身持有了self
- 但
self
并不持有block
,所以就没有循环引用产生,因此就不需要使用weakSelf
了
有感兴趣的小伙伴,可以去我的 github
下载这个 demo,你们也可以根据自己的需求来进一步改写。