前言
相信很多人都會(huì)選擇自定義alertView,網(wǎng)上也有太多大神封裝了類似的三方庫,但用來用去,感覺最靠譜的,還是系統(tǒng)的,這也是之前花功夫針對(duì)系統(tǒng)的alert進(jìn)行適配封裝的原因之一(iOS (封裝)一句話調(diào)用系統(tǒng)的alertView和alertController)。
但是系統(tǒng)的效果畢竟是局限的,很多時(shí)候,我們僅僅是需要顯示一個(gè)遮罩層的提示語,又或者是比較麻煩的,需要顯示一個(gè)可實(shí)現(xiàn)多種交互的提示窗,這時(shí),還是得自定義……
下面的封裝思路,相對(duì)來說簡(jiǎn)單一些,但肯定不是最好的,甚至因?yàn)椴坏貌坏脑颍昧藛卫@個(gè)東西,如果你有更好的改善方法,還望多多指教。
代碼詳見GitHub:Demo_JXTAlertView
16.3.8更新Demo,添加了模態(tài)跳轉(zhuǎn)視圖控制器實(shí)現(xiàn)alertView彈出
代碼封裝度不高,只是提供一個(gè)簡(jiǎn)單的實(shí)現(xiàn)思路。
下面是演示效果(定義的樣式很簡(jiǎn)單,因?yàn)間if幀數(shù)限制,動(dòng)畫效果被削弱了):

1.彈性動(dòng)畫
iOS的動(dòng)畫效果是很強(qiáng)悍的,彈窗展示時(shí),需要一個(gè)彈性動(dòng)畫去過渡,彈性動(dòng)畫的實(shí)現(xiàn)方式有很多,也比較簡(jiǎn)單,但難的是自然平滑的效果,下面的兩種實(shí)現(xiàn),是參考了網(wǎng)上的例子,兩種方法大同小異,代碼還是很好理解的,具體參數(shù)可自行調(diào)整:
- 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];
}
我采用了第二種方式,相對(duì)來說,過渡更為順滑些,如果想要實(shí)現(xiàn)和系統(tǒng)的alert動(dòng)畫相似的效果,可以去掉values數(shù)組中的倒數(shù)第二項(xiàng),當(dāng)然keyTimes和timingFunctions也要去掉對(duì)應(yīng)的項(xiàng)。
2.全屏遮罩
一般這類提示窗是顯示在一個(gè)半透明黑的遮罩層上的,遮罩層的實(shí)現(xiàn)也有多種方式,我采用的是在keyWindow層上添加一個(gè)和屏幕尺寸相當(dāng)?shù)陌胪该骱诘膙iew:
_alertBackgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, ScreenWidth, ScreenHeight)];
_alertBackgroundView.backgroundColor = UIColorFromHEX(0x000000, 0.7);
[[UIApplication sharedApplication].keyWindow addSubview:_alertBackgroundView];
有些人習(xí)慣設(shè)置view的alpha值,但這樣做,會(huì)導(dǎo)致其子視圖也會(huì)半透明化,最簡(jiǎn)單的還是設(shè)置背景色的透明度,這里采用的是16進(jìn)制色值,系統(tǒng)沒有提供關(guān)于16進(jìn)制色值設(shè)置的方法,大都是自己封裝,封裝方法也大同小異,只是完善度的問題,我在Demo中使用的是最簡(jiǎn)單的一個(gè)沒有任何容錯(cuò)機(jī)制的宏定義方法:
#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進(jìn)制色值的轉(zhuǎn)換機(jī)制的朋友,也可以從上述代碼直觀的去理解。
3.鍵盤的彈出
鍵盤的彈出是需要考慮的問題,好的progressHUD指示器,也會(huì)考慮鍵盤的彈出,從而自動(dòng)移動(dòng)指示器的位置。系統(tǒng)的alert自然不會(huì)例外,當(dāng)有鍵盤彈出時(shí),系統(tǒng)的alert視圖會(huì)自動(dòng)上移,防止鍵盤的遮蓋。在這里說句題外話,就是alert動(dòng)畫中斷系統(tǒng)的鍵盤收起動(dòng)畫的問題,這是尤其要注意避免的,一旦在收鍵盤的同時(shí)展示alert,收鍵盤的動(dòng)畫就會(huì)被強(qiáng)行中斷,當(dāng)alert消失時(shí),鍵盤又會(huì)詭異的閃現(xiàn)一下……一種解決辦法是,監(jiān)聽鍵盤的收起動(dòng)畫,didhide之后再去展示alert。
這里防止鍵盤遮蓋alert的解決辦法也是監(jiān)聽鍵盤的高度去實(shí)現(xiàn)。
吐槽一句,有哪位朋友知道如何在鍵盤視圖層添加view嗎?有時(shí)候輸入提示想添加在鍵盤上面,但是一直沒有成功過,鍵盤視圖是在window上,但是卻總也定位不到……
監(jiān)聽鍵盤彈起就很簡(jiǎn)單了:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
只是要注意在合適的時(shí)機(jī)移除就好。
鍵盤的高度在監(jiān)聽到的info字典中,系統(tǒng)的鍵盤信息是比較完善的,但是三方鍵盤就要差很多,甚至有些三方鍵盤的frame是監(jiān)聽不到的……針對(duì)這類鍵盤,除了平時(shí)做到放棄使用,還可以根據(jù)經(jīng)驗(yàn)去估計(jì)一個(gè)值……至于更好的解決辦法就不知道了,可以查閱一些參考資料。
// 鍵盤的frame
CGRect keyboardRect = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGFloat keyboardHeight = keyboardRect.size.height;
有了鍵盤的高度值,就很好處理了,只需要?jiǎng)討B(tài)的去設(shè)置alertView的frame就好了,為了效果自然,也可以添加動(dòng)畫。具體代碼實(shí)現(xiàn)參考Demo,效果如上圖。
4.Demo中的封裝方法的使用解釋
Demo中的使用情景是填寫圖片驗(yàn)證碼,需要?jiǎng)討B(tài)設(shè)置中間的驗(yàn)證碼獲取按鈕的圖片,還有動(dòng)態(tài)獲取輸入框的輸入內(nèi)容,這里用的是block,很方便。
[[JXTAlertView sharedAlertView] showAlertViewWithConfirmAction:^(NSString *inputText) {
NSLog(@"輸入內(nèi)容:%@", inputText);
} andReloadAction:^{
[[JXTAlertView sharedAlertView] refreshVerifyImage:[VerifyNumberView verifyNumberImage]];
}];
方法中的第一個(gè)block是點(diǎn)擊確認(rèn)鍵的響應(yīng),可以獲取到textField的輸入值,第二個(gè)blcok是中間的圖片按鈕的點(diǎn)擊響應(yīng),用來設(shè)置按鈕的背景圖,即從網(wǎng)絡(luò)請(qǐng)求到的驗(yàn)證碼圖片,VerifyNumberView類即相關(guān)方法,只是為了模擬效果而搞笑的……可以自行忽略。
前面提到封裝時(shí)用到了單例,這是因?yàn)閎lock交互響應(yīng)和設(shè)置圖片時(shí)的需要,可能還有更好的方式吧,請(qǐng)指教。
5.用模態(tài)跳轉(zhuǎn)視圖控制器方式實(shí)現(xiàn)(此方式僅支持iOS8及以后版本)
iOS8之后的API中,系統(tǒng)的alert增加了UIAlertController方法,需要使用模態(tài)方式調(diào)用。從這點(diǎn)受啟發(fā),在自定義時(shí),也嘗試下模態(tài)跳轉(zhuǎn)視圖控制器的方式。
先看看初始化方法:
- (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;是設(shè)置跳轉(zhuǎn)的動(dòng)畫方式。選擇比較自然的淡入淡出。
self.modalPresentationStyle = UIModalPresentationOverFullScreen;這一句是核心,這個(gè)樣式可以使得模態(tài)推出的頁面透明化,當(dāng)然還需要在推出的視圖中添加這個(gè)這個(gè):
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = UIColorFromHEX(0x000000, 0.5);
}
先看一個(gè)UIModalPresentationOverFullScreen這個(gè)枚舉值在API中的說明:
UIModalPresentationOverFullScreen NS_ENUM_AVAILABLE_IOS(8_0),
可以看到,這個(gè)枚舉樣式,實(shí)在iOS8之后才支持的,系統(tǒng)的UIAlertController也是iOS8之后才有的,從這一點(diǎn)可以簡(jiǎn)單猜測(cè)系統(tǒng)的alert的實(shí)現(xiàn)機(jī)制。
其他的自定義的方法和上面的大同小異,只是這里不再使用單例,感覺心安了許多……
還要注意一點(diǎn):
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
//必須在這里,否則動(dòng)畫無效
[self showAlertView];
}
[self showAlertView]是創(chuàng)建alert視圖的方法,這一方法,最好是在viewWillAppear中實(shí)現(xiàn),如果直接寫在viewDidLoad,模態(tài)跳轉(zhuǎn)的動(dòng)畫會(huì)將我們?cè)趧?chuàng)建alert時(shí)實(shí)現(xiàn)的彈窗動(dòng)畫中斷掉,也就是彈窗沒有動(dòng)畫效果,這不是我們想要的。viewWillAppear的執(zhí)行是相對(duì)延后的,實(shí)驗(yàn)發(fā)現(xiàn)沒有影響。
根據(jù)這個(gè)思路,也很容易自定義出自己想要的alertView效果了。
最后還要提一點(diǎn),就是statusBar的樣式,如果是UIStatusBarStyleLightContent,也就是白色文字,全屏遮罩時(shí)的半透明黑背景上的白色文字會(huì)顯得很是突兀,iOS7之后,系統(tǒng)支持在每個(gè)視圖控制器中控制statusBar的樣式(注意navigationBar的影響),這樣,用視圖控制器方式實(shí)現(xiàn)alert的全屏遮罩,相信可以解決這一問題。
參考文章:
1.視圖彈出后放大又縮小的動(dòng)畫實(shí)現(xiàn)、類似于alertView效果
2.談?wù)刬OS中粘性動(dòng)畫以及果凍效果的實(shí)現(xiàn)
3.iOS動(dòng)畫實(shí)現(xiàn):彈簧效果
4.UITextField 文本字段控件 -- IOS (解決鍵盤遮住View及密文設(shè)定的問題)(實(shí)例)
5.iOS開發(fā)之監(jiān)聽鍵盤高度的變化
6.模態(tài)(modal)畫面的顯示方法