基于手勢密碼的安全性與交互性比較好,一直以來都是手機APP的標配。在接手公司項目的時候,項目已經實現了手勢密碼。但是集成在項目中的手勢密碼功能一直沒有達到老大要求和手Q一致的需求。在研究了手Q的彈出機制之后,我發現主要的差距是在app進入后臺再次點擊圖標進入程序:手勢密碼界面會滯后?(即手勢密碼彈出是在主界面出現之后再彈出)
我查看了代碼實現是寫一個基礎ViewController,在viewDidAppear監聽手勢密碼監聽事件,代碼示例如下:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(verifyGesturePwd) name:@"VerifyGesturePwd" object:nil];
我們姑且不論代碼的書寫,此代碼在不繼承這個基礎view controller的情況下,手勢密碼是彈不出的。并且實現不了直接彈出手勢密碼的要求。
中間我嘗試在這個基礎上做修改,即把監聽代碼放到viewWillAppear中,結果你肯定也猜到了,沒有用,因為這還是在界面生成之后彈出的。那怎么解決這個需求呢?我陷入了這個死胡同。難道這個問題就解決不了了嗎?答案肯定是可以解決,因為人家已經實現了這個功能。
最近剛好看到訂閱專欄里面一篇關于“萬能鑰匙”的文章,文章的主題就是不要盯著鎖頭找鑰匙,要到其他地方找鑰匙。很顯然我就是陷入了盯著問題找答案的死胡同,此時我豁然開朗。找到解題的關鍵:答案在別處。我應該在程序入口的地方來完成項目需求。下面給出我的實現。
1、在每次程序啟動時,在didFinishLaunchingWithOptions方法中判斷是否設置手勢密碼,如果有手勢密碼功能的話,直接把手勢密碼界面設置為rootViewController,沒有的話就判斷是否登錄賬號,登錄就設置MainViewController為rootViewController,否則就設置登錄界面為rootViewController。并注冊一個登錄的監聽。
if ([DBUtil objectForKey:kDB_GestureValid]) {
VertifyGesViewController *controller = [VertifyGesViewController new];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:controller];
self.window.rootViewController = nav;
}
else{
[self loginStateChange:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(loginStateChange:)
name:KNOTIFICATION_LOGINCHANGE
object:nil];
其中loginStateChange的處理邏輯如下:
- (void)loginStateChange:(NSNotification *)notification{
if (notification == nil) {
[self userLogined];
}else{
BOOL loginSuccess = [notification.object boolValue];
if (loginSuccess) {
[self userLogined];
}
else{
[self userLoginOut];
}
}
}
- (void)userLogined{
self.window.rootViewController = [MainViewController new];
}
- (void)userLoginOut{
self.window.rootViewController = [[UINavigationController alloc]initWithRootViewController:[LoginViewController new]];
}
2、 為了處理方便,我在手勢密碼驗證后把邏輯放到登錄狀態監聽里面。即在驗證后判斷如果當前手勢界面是設置的rootViewController時,發出通知。監聽處需要重新設置rootViewController。
if (self.navigationController) {
[[NSNotificationCenter defaultCenter] postNotificationName:KNOTIFICATION_LOGINCHANGE object:@YES];
}else{
[self dismissViewControllerAnimated:YES completion:^{}];
}
3、在程序進入后臺時,如果手勢密碼已設置,彈出手勢密碼即可。
if ([DBUtil boolForKey:kDB_GestureValid]) { //手勢密碼有效
[self.window.rootViewController presentViewController:[VertifyGesViewController new] animated:NO completion:nil];
}
4、在測試的時候發現有個小bug,即在殺死程序再次進入應用時,直接進入后臺發現手勢密碼界面有2個。此處只需要添加如下代碼邏輯來解決重復彈出的問題。
UIViewController *rootViewController = [[[UIApplication sharedApplication] delegate] window].rootViewController;
if ([rootViewController isKindOfClass:[UINavigationController class]]) {
return;
}
行文至此,這個功能需求被實現了出來。說一下自己的一點小感悟:1、很多你覺得不可能完成的任務,其實是你還沒有找到解決方法。2、不要盯著鎖頭找鑰匙,鑰匙一定在其他地方。
今天抽時間把手勢密碼抽離出來,https://github.com/hohua88/Gesture,歡迎交流。
#未完再續
上述實現在當前頁面有Alert或者鍵盤時,彈出的驗證手勢密碼界面會出現遮擋(既Alert或者鍵盤)。這是由于Alert/鍵盤是展示在UIWindow上的,會顯示在手勢密碼界面的上面。
有興趣的可以去Window官方文檔,這里就不展開描述了。這里為了解決遮擋問題,可以通過設置UIWindowLevel為UIWindowLevelAlert來做到手勢密碼顯示在最上層。
- (void)show {
????????self.windowLevel = UIWindowLevelAlert;
????????[self makeKeyWindow];
????????self.hidden = NO;
}
這樣做的話,想要再彈出Alert提示框就需要自己自定義添加到window上。想嘗試的可以自己實現一下。
參考鏈接:UIWindow 詳解及使用場景