先上效果圖
消息中心(NSNotificationCenter)
實(shí)現(xiàn)視圖對鍵盤彈出的響應(yīng)主要利用的是通知機(jī)制。這里我們來了解下通知機(jī)制。
不同于代理用于一對一的傳遞消息,通知機(jī)制用于多對多的傳遞消息。這個(gè)信息的傳遞是通過消息中心(NSNotificationCenter),每一個(gè)應(yīng)用程序都有一個(gè)通知中心(NSNotificationCenter)實(shí)例,專門負(fù)責(zé)協(xié)助不同對象之間的消息通信。任何一個(gè)對象都可以向通知中心發(fā)布通知(NSNotification),描述自己在做什么。其他感興趣的對象(Observer)可以申請?jiān)谀硞€(gè)特定通知發(fā)布時(shí)(或在某個(gè)特定的對象發(fā)布通知時(shí))收到這個(gè)通知。這樣便能實(shí)現(xiàn)消息的傳遞。
消息中心(NSNotificationCenter)是唯一的(即單例),在創(chuàng)建時(shí)使用類方法 [NSNotificationCenter defaultCenter] 即可。
通知(NSNotification)
而發(fā)送的消息叫做通知(NSNotification),一個(gè)完整的通知一般包含3個(gè)屬性:
(NSString *)name; // 通知的名稱
(id)object; // 通知發(fā)布者(是誰要發(fā)布通知)
(NSDictionary *)userInfo; // 一些額外的信息(通知發(fā)布者傳遞給通知接收者的信息內(nèi)容)
初始化一個(gè)通知(NSNotification)對象有這些方法
+ (instancetype)notificationWithName:(NSString *)aName object:(id)anObject;
+ (instancetype)notificationWithName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)UserInfo;
- (instancetype)initWithName:(NSString *)name object:(id)object userInfo:(NSDictionary *)userInfo;
發(fā)布通知
通知中心提供了相應(yīng)的方法來幫助發(fā)布通知
- (void)postNotification:(NSNotification *)notification;
//發(fā)布一個(gè)notification通知,可在notification對象中設(shè)置通知的名稱、通知發(fā)布者、額外信息等
- (void)postNotificationName:(NSString *)aName object:(id)anObject;
//發(fā)布一個(gè)名稱為aName的通知,anObject為這個(gè)通知的發(fā)布者
- (void)postNotificationName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo;
//發(fā)布一個(gè)名稱為aName的通知,anObject為這個(gè)通知的發(fā)布者,aUserInfo為額外信息
監(jiān)聽通知
通知中心(NSNotificationCenter)提供了方法來注冊一個(gè)監(jiān)聽通知的監(jiān)聽器(Observer)
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject;
其中:
observer:監(jiān)聽器,即誰要接收這個(gè)通知
aSelector:收到通知后,回調(diào)監(jiān)聽器的這個(gè)方法,并且把通知對象當(dāng)做參數(shù)傳入
aName:通知的名稱。如果為nil,那么無論通知的名稱是什么,監(jiān)聽器都能收到這個(gè)通知
anObject:通知發(fā)布者。如果為anObject和aName都為nil,監(jiān)聽器都收到所有的通知
值得注意的是,aSelector 指示的方法傳入的參數(shù)即是通知對象本身,我們可以通過通知對象的 userInfo; // 一些額外的信息(通知發(fā)布者傳遞給通知接收者的信息內(nèi)容)
初始化一個(gè)通知(NSNotification)對象有這些方法
+ (instancetype)notificationWithName:(NSString *)aName object:(id)anObject;
+ (instancetype)notificationWithName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)UserInfo;
- (instancetype)initWithName:(NSString *)name object:(id)object userInfo:(NSDictionary *)userInfo;
取消注冊通知監(jiān)聽器
由于通知中心不會保留(retain)監(jiān)聽器對象,在通知中心注冊過的對象,必須在該對象釋放前取消注冊。否則,當(dāng)相應(yīng)的通知再次出現(xiàn)時(shí),通知中心仍然會向該監(jiān)聽器發(fā)送消息。因?yàn)橄鄳?yīng)的監(jiān)聽器對象已經(jīng)被釋放了,所以可能會導(dǎo)致應(yīng)用崩潰!!
通知中心提供了相應(yīng)的方法來取消注冊監(jiān)聽器
- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(NSString *)aName object:(id)anObject;
一般我們用第一種方法就可以了,可以(也需要)移除所有監(jiān)聽器對象注冊的監(jiān)聽。
一般在監(jiān)聽器銷毀之前取消注冊(如在監(jiān)聽器中加入下列代碼):
- (void)dealloc {
//[super dealloc]; 非ARC中需要調(diào)用此句
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
注意我們現(xiàn)在一般都是使用的 ARC 模式,所以不需要調(diào)用 [super dealloc] 以及 release 了。 delloc 方法在 ARC 依然保留的原因就是為了方便我們做取消監(jiān)聽注冊等情況。
常見的通知名稱
UIDevice 通知
UIDevice類提供了一個(gè)單例對象,它代表著設(shè)備,通過它可以獲得一些設(shè)備相關(guān)的信息,比如電池電量值(batteryLevel)、電池狀態(tài)(batteryState)、設(shè)備的類型(model,比如iPod、iPhone等)、設(shè)備的系統(tǒng)(systemVersion)
,通過[UIDevice currentDevice]可以獲取這個(gè)單例對象。
UIDevice對象會不間斷地發(fā)布一些通知,下列是UIDevice對象所發(fā)布通知的名稱常量:
UIDeviceOrientationDidChangeNotification // 設(shè)備旋轉(zhuǎn)
UIDeviceBatteryStateDidChangeNotification // 電池狀態(tài)改變
UIDeviceBatteryLevelDidChangeNotification // 電池電量改變
UIDeviceProximityStateDidChangeNotification // 近距離傳感器(比如設(shè)備貼近了使用者的臉部)
鍵盤通知
鍵盤狀態(tài)改變的時(shí)候,系統(tǒng)會發(fā)出一些特定的通知
UIKeyboardWillShowNotification // 鍵盤即將顯示
UIKeyboardDidShowNotification // 鍵盤顯示完畢
UIKeyboardWillHideNotification // 鍵盤即將隱藏
UIKeyboardDidHideNotification // 鍵盤隱藏完畢
UIKeyboardWillChangeFrameNotification // 鍵盤的位置尺寸即將發(fā)生改變
UIKeyboardDidChangeFrameNotification // 鍵盤的位置尺寸改變完畢
實(shí)現(xiàn)鍵盤彈出界面上移
首先需要注冊鍵盤監(jiān)聽的通知,在 viewDidLoad 方法中加入:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
這樣表示任何對象發(fā)布的名為 UIKeyboardWillChangeFrameNotification 的通知都會被監(jiān)聽并且觸發(fā) self(即 ViewController)執(zhí)行 keyboardWillChangeFrame: 方法,這名字是自己定義的。
keyboardWillChangeFrame: 方法實(shí)現(xiàn)
下面來自定義
- (void)keyboardWillChangeFrame:(NSNotification *)notification;
的實(shí)現(xiàn)。注意發(fā)布通知會把通知對象本身當(dāng)作參數(shù)傳入,因此可以獲取 notification 的內(nèi)容。在方法中加入
NSLog(@"%@",notification);
運(yùn)行輸出 notification 的內(nèi)容。我這里用的是 iphone 8,iOS 11.1。
輸出內(nèi)容如下:
{name = UIKeyboardWillChangeFrameNotification;
userInfo = {
UIKeyboardAnimationCurveUserInfoKey = 7;//鍵盤彈出的節(jié)奏、
UIKeyboardAnimationDurationUserInfoKey = "0.25";//鍵盤彈出動畫執(zhí)行的時(shí)間
UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {375, 258}}";
UIKeyboardCenterBeginUserInfoKey = "NSPoint: {187.5, 796}";
UIKeyboardCenterEndUserInfoKey = "NSPoint: {187.5, 538}";
//彈出鍵盤:
UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 667}, {375, 258}}";//鍵盤彈出前的frame
UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 409}, {375, 258}}";//鍵盤彈出后的frame,說明鍵盤的上移后y為409,高度為258.
UIKeyboardIsLocalUserInfoKey = 1;
}}
這其中就以字典的形式存放了 鍵盤彈出的節(jié)奏、時(shí)間、鍵盤彈出前后的 frame 等等內(nèi)容。以此我們可以通過 notification 獲取鍵盤的 frame 的 y 值已確定視圖需要上移的高度。前面給出的 UIKeyboard…Key 就是字典需要的關(guān)鍵字了。
//1. 獲取鍵盤的 Y 值
CGRect keyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
//或者 keyboardFrame = [[notification.userInfo objectForKey:@"UIKeyboardFrameEndUserInfoKey"] CGRectValue];
//注意從字典取出來的是對象,而 CGRect CGFloat 都是基本數(shù)據(jù)類型,一次需要轉(zhuǎn)換
CGFloat keyboardY = keyboardFrame.origin.y;
移動主視圖可以通過修改 self.view 的 frame、center、或者是 transform 屬性來進(jìn)行變形。這里以 transform 為例。主視圖上移或者下移時(shí)移動到的位置應(yīng)該是 keyboardY – self.view.frame.size.height ,即 主視圖原位置為 0 ,彈出時(shí)上移移動到 “-鍵盤的高度”。隱藏時(shí)下移移動到 0。代碼為:
self.view.transform = CGAffineTransformMakeTranslation(0, keyboardY - self.view.frame.size.height);
實(shí)現(xiàn)鍵盤隱藏界面下移
首先需要給 tableView 添加代理,設(shè)置代理對象為 ViewController,然后自定義方法:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
[self.view endEditing:YES];//結(jié)束編輯狀態(tài),即可以關(guān)閉鍵盤
}
界面上移的動畫效果
可以由此前的 notification 的 userInfo 內(nèi)容可以知道,鍵盤上移的動畫時(shí)間是 0.25s 。而 self.view 移動理論上是瞬時(shí)的(在我的 iOS 11.1 的動畫是和鍵盤同步的,但是在之前看的教程中 iOS 8 里系統(tǒng)中是瞬時(shí)的,我上網(wǎng)查了也沒找到原因,肯能是 apple 對此作了優(yōu)化,但是為了保險(xiǎn)起見還是手動設(shè)置界面上移的動畫效果),另外動畫移動的節(jié)奏也要與鍵盤一致。將代碼修改為:
double timeInterval = [[notification.userInfo objectForKey:@"UIKeyboardAnimationDurationUserInfoKey"] doubleValue];
NSInteger animationOption = [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
[UIView animateWithDuration:timeInterval delay:0.0 options:animationOption << 16 animations:^{
self.view.transform = CGAffineTransformMakeTranslation(0, keyboardY - self.view.frame.size.height);
} completion:nil];
至此鍵盤處理完成~