前言
相信很多人都会选择自定义alertView,网上也有太多大神封装了类似的三方库,但用来用去,感觉最靠谱的,还是系统的,这也是之前花功夫针对系统的alert进行适配封装的原因之一(iOS (封装)一句话调用系统的alertView和alertController)。
但是系统的效果毕竟是局限的,很多时候,我们仅仅是需要显示一个遮罩层的提示语,又或者是比较麻烦的,需要显示一个可实现多种交互的提示窗,这时,还是得自定义……
下面的封装思路,相对来说简单一些,但肯定不是最好的,甚至因为不得不的原因,用了单例这个东西,如果你有更好的改善方法,还望多多指教。
代码详见GitHub:Demo_JXTAlertView
16.3.8更新Demo,添加了模态跳转视图控制器实现alertView弹出
代码封装度不高,只是提供一个简单的实现思路。
下面是演示效果(定义的样式很简单,因为gif帧数限制,动画效果被削弱了):
1.弹性动画
iOS的动画效果是很强悍的,弹窗展示时,需要一个弹性动画去过渡,弹性动画的实现方式有很多,也比较简单,但难的是自然平滑的效果,下面的两种实现,是参考了网上的例子,两种方法大同小异,代码还是很好理解的,具体参数可自行调整:
- 1.方式一:
- (void)shakeToShow:(UIView *)aView
{
CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
animation.duration = 0.2;
NSMutableArray * values = [NSMutableArray array];
[values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.1, 0.1, 1.0)]];
[values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.2, 1.2, 1.0)]];
[values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.9, 0.9, 1.0)]];
[values addObject:[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 1.0)]];
animation.values = values;
[aView.layer addAnimation:animation forKey:nil];
}
- 2.方式二:
- (void)shakeToShow:(UIView *)aView
{
CAKeyframeAnimation *popAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
popAnimation.duration = 0.35;
popAnimation.values = @[[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.01f, 0.01f, 1.0f)],
[NSValue valueWithCATransform3D:CATransform3DMakeScale(1.1f, 1.1f, 1.0f)],
[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.9f, 0.9f, 1.0f)],
[NSValue valueWithCATransform3D:CATransform3DIdentity]];
popAnimation.keyTimes = @[@0.0f, @0.5f, @0.75f, @1.0f];
popAnimation.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[aView.layer addAnimation:popAnimation forKey:nil];
}
我采用了第二种方式,相对来说,过渡更为顺滑些,如果想要实现和系统的alert动画相似的效果,可以去掉values
数组中的倒数第二项,当然keyTimes
和timingFunctions
也要去掉对应的项。
2.全屏遮罩
一般这类提示窗是显示在一个半透明黑的遮罩层上的,遮罩层的实现也有多种方式,我采用的是在keyWindow层上添加一个和屏幕尺寸相当的半透明黑的view:
_alertBackgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, ScreenWidth, ScreenHeight)];
_alertBackgroundView.backgroundColor = UIColorFromHEX(0x000000, 0.7);
[[UIApplication sharedApplication].keyWindow addSubview:_alertBackgroundView];
有些人习惯设置view的alpha值,但这样做,会导致其子视图也会半透明化,最简单的还是设置背景色的透明度,这里采用的是16进制色值,系统没有提供关于16进制色值设置的方法,大都是自己封装,封装方法也大同小异,只是完善度的问题,我在Demo中使用的是最简单的一个没有任何容错机制的宏定义方法:
#define UIColorFromHEX(hexValue, alphaValue) \
[UIColor colorWithRed:((float)((hexValue & 0xFF0000) >> 16))/255.0 \
green:((float)((hexValue & 0x00FF00) >> 8))/255.0 \
blue:((float)(hexValue & 0x0000FF))/255.0 \
alpha:alphaValue]
这种写法相信十分直观了,很多不太了解16进制色值的转换机制的朋友,也可以从上述代码直观的去理解。
3.键盘的弹出
键盘的弹出是需要考虑的问题,好的progressHUD指示器,也会考虑键盘的弹出,从而自动移动指示器的位置。系统的alert自然不会例外,当有键盘弹出时,系统的alert视图会自动上移,防止键盘的遮盖。在这里说句题外话,就是alert动画中断系统的键盘收起动画的问题,这是尤其要注意避免的,一旦在收键盘的同时展示alert,收键盘的动画就会被强行中断,当alert消失时,键盘又会诡异的闪现一下……一种解决办法是,监听键盘的收起动画,didhide之后再去展示alert。
这里防止键盘遮盖alert的解决办法也是监听键盘的高度去实现。
吐槽一句,有哪位朋友知道如何在键盘视图层添加view吗?有时候输入提示想添加在键盘上面,但是一直没有成功过,键盘视图是在window上,但是却总也定位不到……
监听键盘弹起就很简单了:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
只是要注意在合适的时机移除就好。
键盘的高度在监听到的info字典中,系统的键盘信息是比较完善的,但是三方键盘就要差很多,甚至有些三方键盘的frame是监听不到的……针对这类键盘,除了平时做到放弃使用,还可以根据经验去估计一个值……至于更好的解决办法就不知道了,可以查阅一些参考资料。
// 键盘的frame
CGRect keyboardRect = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGFloat keyboardHeight = keyboardRect.size.height;
有了键盘的高度值,就很好处理了,只需要动态的去设置alertView的frame就好了,为了效果自然,也可以添加动画。具体代码实现参考Demo,效果如上图。
4.Demo中的封装方法的使用解释
Demo中的使用情景是填写图片验证码,需要动态设置中间的验证码获取按钮的图片,还有动态获取输入框的输入内容,这里用的是block,很方便。
[[JXTAlertView sharedAlertView] showAlertViewWithConfirmAction:^(NSString *inputText) {
NSLog(@"输入内容:%@", inputText);
} andReloadAction:^{
[[JXTAlertView sharedAlertView] refreshVerifyImage:[VerifyNumberView verifyNumberImage]];
}];
方法中的第一个block是点击确认键的响应,可以获取到textField的输入值,第二个blcok是中间的图片按钮的点击响应,用来设置按钮的背景图,即从网络请求到的验证码图片,VerifyNumberView
类即相关方法,只是为了模拟效果而搞笑的……可以自行忽略。
前面提到封装时用到了单例,这是因为block交互响应和设置图片时的需要,可能还有更好的方式吧,请指教。
5.用模态跳转视图控制器方式实现(此方式仅支持iOS8及以后版本)
iOS8之后的API中,系统的alert增加了UIAlertController
方法,需要使用模态方式调用。从这点受启发,在自定义时,也尝试下模态跳转视图控制器的方式。
先看看初始化方法:
- (instancetype)initWithConfirmAction:(ClickBlock)confirmBlock andCancelAction:(CancelBlock)cancelBlcok
{
if (self = [super init]) {
self.confirmBlock = confirmBlock;
self.cancelBlock = cancelBlcok;
self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
self.modalPresentationStyle = UIModalPresentationOverFullScreen;
}
return self;
}
其中self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
是设置跳转的动画方式。选择比较自然的淡入淡出。
self.modalPresentationStyle = UIModalPresentationOverFullScreen;
这一句是核心,这个样式可以使得模态推出的页面透明化,当然还需要在推出的视图中添加这个这个:
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColorFromHEX(0x000000, 0.5);
}
先看一个UIModalPresentationOverFullScreen
这个枚举值在API中的说明:
UIModalPresentationOverFullScreen NS_ENUM_AVAILABLE_IOS(8_0),
可以看到,这个枚举样式,实在iOS8之后才支持的,系统的UIAlertController
也是iOS8之后才有的,从这一点可以简单猜测系统的alert的实现机制。
其他的自定义的方法和上面的大同小异,只是这里不再使用单例,感觉心安了许多……
还要注意一点:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
//必须在这里,否则动画无效
[self showAlertView];
}
[self showAlertView]
是创建alert视图的方法,这一方法,最好是在viewWillAppear
中实现,如果直接写在viewDidLoad
,模态跳转的动画会将我们在创建alert时实现的弹窗动画中断掉,也就是弹窗没有动画效果,这不是我们想要的。viewWillAppear
的执行是相对延后的,实验发现没有影响。
根据这个思路,也很容易自定义出自己想要的alertView效果了。
最后还要提一点,就是statusBar的样式,如果是UIStatusBarStyleLightContent
,也就是白色文字,全屏遮罩时的半透明黑背景上的白色文字会显得很是突兀,iOS7之后,系统支持在每个视图控制器中控制statusBar的样式(注意navigationBar的影响),这样,用视图控制器方式实现alert的全屏遮罩,相信可以解决这一问题。
参考文章:
1.视图弹出后放大又缩小的动画实现、类似于alertView效果
2.谈谈iOS中粘性动画以及果冻效果的实现
3.iOS动画实现:弹簧效果
4.UITextField 文本字段控件 -- IOS (解决键盘遮住View及密文設定的问题)(实例)
5.iOS开发之监听键盘高度的变化
6.模态(modal)画面的显示方法