當(dāng)應(yīng)用不在前臺運行時,可以通過 user notifications 推送信息給用戶。user notifications 包括 local notifications 和 remote notifications 兩種類型。操作系統(tǒng)展示本地和遠(yuǎn)程通知的方式是一樣的,包括展示提醒信息,應(yīng)用圖標(biāo)標(biāo)記和聲音。當(dāng)用戶收到通知時,可打開應(yīng)用去查看詳細(xì)信息。
本地通知和遠(yuǎn)程通知最基本的區(qū)別是:
- 本地通知由應(yīng)用本身安排和推送到本機。
- 遠(yuǎn)程通知由服務(wù)器發(fā)送到APNs(Apple Push Notification service),再由其推送到設(shè)備。
使用 User Notification 第一步,注冊通知類型
從iOS8開始,不管是本地還是遠(yuǎn)程通知,如果應(yīng)用想使用該功能,必須先注冊希望接收的通知類型。
UIUserNotificationType userNotificationTypes = UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound;
UIUserNotificationSettings *userNotificationSettings = [UIUserNotificationSettings settingsForTypes:userNotificationTypes categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:userNotificationSettings];
如果不注冊,系統(tǒng)將不推送任何類型的通知,即使應(yīng)用在前臺運行也不會觸發(fā) AppDelegate 的application:didReceiveLocalNotification:
方法,在系統(tǒng)設(shè)置中也沒有設(shè)置該應(yīng)用的通知選項。
當(dāng)?shù)谝淮螆?zhí)行注冊代碼時,系統(tǒng)會彈出一個警告窗口,告訴用戶該應(yīng)用希望推送通知,用戶可以選擇是否允許。使用不同的類型組合再次注冊,則系統(tǒng)設(shè)置中也會相應(yīng)改變,未注冊的類型在系統(tǒng)設(shè)置中也就沒有相應(yīng)選項了。如果展示通知的類型設(shè)為UIUserNotificationTypeNone,那么之后在系統(tǒng)設(shè)置中設(shè)置該應(yīng)用的通知選項將消失,并且即使改變展示通知的類型再去注冊也沒有了效果。
注冊之后系統(tǒng)會調(diào)用 AppDelegate 的application:didRegisterUserNotificationSettings:
方法,傳來的 UIUserNotificationSettings 參數(shù)指明了當(dāng)前用戶允許的通知展示類型。可以通過[UIApplication sharedApplication].currentUserNotificationSettings
隨時查看當(dāng)前用戶允許的通知展示類型。
所以,如果沒有特殊情況,應(yīng)該直接注冊所有的通知展示類型。
Local Notification
本地通知是實現(xiàn)基于時間的行為的理想方式,例如日歷事件,提醒事項等。每一個應(yīng)用最多只能同時安排64條本地通知,若此時安排新的本地通知會被系統(tǒng)丟棄,重復(fù)安排的通知按同一條計算。
當(dāng)應(yīng)用收到本地通知時在前臺,系統(tǒng)會調(diào)用 AppDelegate 中的application:didReceiveLocalNotification:
方法,當(dāng)應(yīng)用在后臺掛起或關(guān)閉時則不觸發(fā)該方法。但是當(dāng)應(yīng)用在后臺掛起時,用戶通過點擊或滑動提醒信息進入應(yīng)用時,會調(diào)用application:didReceiveLocalNotification:
方法,若用戶通過點擊應(yīng)用圖標(biāo)進入應(yīng)用則不會調(diào)用。若應(yīng)用已關(guān)閉,則都不會調(diào)用application:didReceiveLocalNotification:
方法,用戶在應(yīng)用關(guān)閉狀態(tài)下通過本地通知打開應(yīng)用,可以在application:didFinishLaunchingWithOptions:
方法中獲得該通知,如果通過點擊應(yīng)用圖標(biāo)打開則無法獲得
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UILocalNotification *localNotification = launchOptions[UIApplicationLaunchOptionsLocalNotificationKey];
}
構(gòu)建一個UILocalNotification:
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
// fireDate設(shè)置推送通知的日期和時間,受timeZone屬性的影響,同時設(shè)置region會發(fā)生異常
localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:5];
// timeZone設(shè)置時區(qū),對fireDate有影響。默認(rèn)為nil,此時fireDate將根據(jù)GMT時間
localNotification.timeZone = [NSTimeZone defaultTimeZone];
// repeatIntervar設(shè)置重復(fù)推送的間隔,默認(rèn)為0,不重復(fù)推送。
localNotification.repeatInterval = kCFCalendarUnitMonth;
// repeatCalendar設(shè)置重復(fù)推送時使用的歷法,默認(rèn)為nil,則使用[NSCalendar currentCalendar]
localNotification.repeatCalendar = [NSCalendar currentCalendar];
// region設(shè)置推送通知的地區(qū),同時設(shè)置fireDate會發(fā)生異常
localNotification.region = nil;
// regionTriggersOnce設(shè)置是否只在第一次到達地區(qū)邊界時推送消息
localNotification.regionTriggersOnce = NO;
// alertBody設(shè)置通知要提醒的信息
localNotification.alertBody = @"alertBody";
// alertAction設(shè)置滑動動作的標(biāo)題
localNotification.alertAction = @"alertAction";
// alertTitle設(shè)置通知提醒的標(biāo)題
localNotification.alertTitle = @"alertTitle";
// hasAction設(shè)置是否展示動作按鈕
localNotification.hasAction = YES;
// alertLaunchImage設(shè)置動作觸發(fā)時應(yīng)用啟動圖片
localNotification.alertLaunchImage = @"default";
// applicationIconBadgeNumber設(shè)置顯示在應(yīng)用圖標(biāo)上的未讀信息數(shù)量
localNotification.applicationIconBadgeNumber = 1;
// soundName設(shè)置通知的提示聲音的文件
localNotification.soundName = UILocalNotificationDefaultSoundName;
// userInfo設(shè)置通知的自定義信息
localNotification.userInfo = @{};
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
除了將通知加入列表外,應(yīng)用還可以通過 UIApplication 對象的presentLocalNotificationNow:
方法立即推送通知,也可以通過cancelLocalNotification:
方法和cancelAllLocalNotifications
方法來取消已安排的通知,同時這些方法可以使正在展示的通知從屏幕上消失。
注意:
在使用關(guān)于位置的通知時,一定要指定CLRegion的identifier,CLRegion靠identifier來唯一區(qū)別,當(dāng)通知對象的區(qū)域?qū)ο蟮膇dentifier為空時,該通知對象最終無法被安排到通知隊列中。擁有含相同identifier的區(qū)域?qū)ο蟮耐ㄖ獙ο笾挥幸粋€會被安排到隊列中。
CLLocationCoordinate2D locationCoordinate = CLLocationCoordinate2DMake(0, 0);
NSString *regionIdentifier = [NSString stringWithFormat:@"%g%g", locationCoordinate.latitude, locationCoordinate.longitude];
CLRegion *region = [[CLCircularRegion alloc] initWithCenter:coordinate radius:100 identifier:regionIdentifier];
注冊遠(yuǎn)程通知
如果應(yīng)用希望接收服務(wù)器發(fā)送的遠(yuǎn)程通知,需要通過調(diào)用 UIApplication 對象的registerForRemoteNotifications
方法向APNs(Apple Push Notification service)注冊。當(dāng)注冊成功時,應(yīng)用會調(diào)用代理對象的application:didRegisterForRemoteNotificationsWithDeviceToken:
方法并傳入一個 device token(二進制編碼),該 device token 需要發(fā)送給發(fā)送遠(yuǎn)程通知的服務(wù)器。如果注冊失敗,則會調(diào)用代理對象的application:didFailToRegisterForRemoteNotificationsWithError:
方法。注意,device token 是可變的,所以每次應(yīng)用啟動都需要重新注冊。若設(shè)備處于未聯(lián)網(wǎng)狀態(tài),則以上兩個方法都不會被調(diào)用。
處理本地和遠(yuǎn)程通知
當(dāng)收到通知時,如果應(yīng)用并沒有在前臺運行,那么根據(jù)用戶的設(shè)置,系統(tǒng)可以通過顯示提醒,應(yīng)用圖標(biāo)加角標(biāo),聲音以及其他的動作按鈕來展示應(yīng)用。當(dāng)用戶點擊了自定義的動作按鈕,應(yīng)用代理對象會調(diào)用application:handleActionWithIdentifier:forRemoteNotification:completionHandler:
方法或者application:handleActionWithIdentifier:forLocalNotification:completionHandler:
方法。當(dāng)用戶點擊默認(rèn)的提醒欄時,如果應(yīng)用未啟動,那么會調(diào)用代理對象的application:didFinishLaunchingWithOptions:
方法,在 options 字典中,可以通過鍵值UIApplicationLaunchOptionsLocalNotificationKey
獲取觸發(fā)應(yīng)用啟動的 UILocalNotification 對象,或者通過鍵值UIApplicationLaunchOptionsRemoteNotificationKey
獲取遠(yuǎn)程通知的userInfo(NSDictionary對象),如果觸發(fā)應(yīng)用啟動的是遠(yuǎn)程通知,那么系統(tǒng)還會調(diào)用代理對象的application:didReceiveRemoteNotification:fetchCompletionHandler:
方法并且執(zhí)行順序先與application:didFinishLaunchingWithOptions:
方法。當(dāng)用戶通過點擊應(yīng)用圖標(biāo)啟動應(yīng)用時,則無法獲得任何有關(guān)通知的信息。當(dāng)應(yīng)用在前臺運行時收到通知,則會調(diào)用代理對象的application:didReceiveLocalNotification:
方法或application:didReceiveRemoteNotification:fetchCompletionHandler:
方法,還有一個方法是application:didReceiveRemoteNotification:
,該方法只有應(yīng)用在前臺時才會被調(diào)用,但若application:didReceiveRemoteNotification:fetchCompletionHandler:
方法被實現(xiàn)則會替代該方法,其不會被調(diào)用。當(dāng)應(yīng)用在后臺掛起時收到遠(yuǎn)程通知,如果此時 Background Mode 設(shè)置了遠(yuǎn)程通知,那么應(yīng)用會被喚醒并在后臺調(diào)用application:didReceiveRemoteNotification:fetchCompletionHandler:
方法,但是有30秒的時間限制,另外如果設(shè)置中的后臺刷新選項被關(guān)閉同時通知選項被設(shè)為不允許通知,則也不會調(diào)用該方法。
有關(guān)處理方法中的最后一個參數(shù) completionHandler,當(dāng)執(zhí)行完處理通知的代碼后,一定要調(diào)用傳入的 block 參數(shù) completionHandler,否則會使應(yīng)用結(jié)束運行。在異步獲取數(shù)據(jù)結(jié)束后,執(zhí)行application:didReceiveRemoteNotification:fetchCompletionHandler:
方法中的 completionHandler 時還要傳入一個描述獲取數(shù)據(jù)結(jié)果的參數(shù)(UIBackgroundFetchResult類型)。
使用通知處理(Notification Actions)
從 iOS8 開始,用戶對于通知的處理除了默認(rèn)的方式(點擊橫幅或提醒中的默認(rèn)處理,鎖屏?xí)r滑動通知)外,還可以自定義其他處理方式供用戶選擇。在橫幅,鎖屏狀態(tài)或者通知中心中最多可添加兩種自定義處理,在提醒框的選項中最多可添加4種自定義處理。使用自定義通知處理的第一步就是注冊處理。
UIMutableUserNotificationAction *acceptAction = [[UIMutableUserNotificationAction alloc] init];
// 當(dāng)用戶收到通知并選擇該處理,系統(tǒng)會將 identifier 傳給應(yīng)用代理對象的相應(yīng)方法,以便判斷用戶選擇了哪一項處理。
acceptAction.identifier = @"ACTION_ACCEPT";
// 處理按鈕的標(biāo)題。
acceptAction.title = @"Accept";
// 設(shè)置當(dāng)用戶點擊處理按鈕后,應(yīng)用在前臺運行還是后臺運行,如果為后臺模式,應(yīng)用將獲得一定的時間運行,若此時應(yīng)用在前臺(鎖屏情況下),應(yīng)用將繼續(xù)保持在前臺。若該處理不需要用戶與界面交互則可用后臺模式。
acceptAction.activationMode = UIUserNotificationActivationModeBackground;
// 設(shè)為YES則按鈕背景色為紅色,起強調(diào)作用。
acceptAction.destructive = NO;
// 該設(shè)置針對鎖屏情況下,用戶選擇處理后是否需要輸入密碼,若 activationMode 為后臺模式則輸入密碼不會解鎖,只是執(zhí)行處理,若為前臺模式,則必須驗證密碼,無論該屬性的值是什么。
acceptAction.authenticationRequired = YES;
UIMutableUserNotificationAction *maybeAction = [[UIMutableUserNotificationAction alloc] init];
maybeAction.identifier = @"ACTION_MAYBE";
maybeAction.title = @"Maybe";
maybeAction.activationMode = UIUserNotificationActivationModeBackground;
maybeAction.destructive = NO;
maybeAction.authenticationRequired = NO;
UIMutableUserNotificationAction *declineAction = [[UIMutableUserNotificationAction alloc] init];
declineAction.identifier = @"ACTION_DECLINE";
declineAction.title = @"Decline";
declineAction.activationMode = UIUserNotificationActivationModeBackground;
declineAction.destructive = YES;
declineAction.authenticationRequired = NO;
// 需要將定義的一組 Action 放入到一個category中,在推送通知設(shè)置該通知對應(yīng)的 category,當(dāng)系統(tǒng)收到通知展示時通過 category 的 identifier 匹配到已注冊的 category,并將其中的 action 展示出來。
UIMutableUserNotificationCategory *inviteCategory = [[UIMutableUserNotificationCategory alloc] init];
inviteCategory.identifier = @"CATEGORY_INVITE";
// 設(shè)置 category 的 action 時對應(yīng)兩種 context,default 和 minimal。default 應(yīng)用于展示4個 action 的地方,minimal 應(yīng)用于展示2個 action 的地方。若未指定 minimal,則只能展示2個 action 的地方將展示 default 中的前兩個。
[inviteCategory setActions:@[acceptAction, maybeAction, declineAction] forContext:UIUserNotificationActionContextDefault];
[inviteCategory setActions:@[acceptAction, declineAction] forContext:UIUserNotificationActionContextMinimal];
// 可以注冊多個 category,所以注冊前要將所有 category 放入一個 set 中用于注冊。
NSSet *categories = [NSSet setWithObjects:inviteCategory, nil];
UIUserNotificationType userNotificationTypes = UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound;
UIUserNotificationSettings *userNotificationSettings = [UIUserNotificationSettings settingsForTypes:userNotificationTypes categories:categories];
[[UIApplication sharedApplication] registerUserNotificationSettings:userNotificationSettings];
當(dāng)推送通知時,只要設(shè)置了遠(yuǎn)程通知的 key 值 category 或者本地通知的 category 屬性為已注冊的 category 的 identifier,那么當(dāng)系統(tǒng)展示通知時,就會將相應(yīng)處理按鈕展示出來。最后,當(dāng)用戶點擊了自定義的處理按鈕,系統(tǒng)會調(diào)用應(yīng)用代理對象的application:handleActionWithIdentifier:forRemoteNotification:completionHandler:
方法或application:handleActionWithIdentifier:forLocalNotification:completionHandler:
方法。在該方法中可以通過傳入的 action 的 identifier 來判斷用戶點擊了哪一個 action 按鈕,另還傳入了通知對象。
判斷用戶是否允許通知
可通過[UIApplication sharedApplication].currentUserNotificationSettings
獲取用戶的設(shè)置并進行判斷,但要注意該屬性只適用于 iOS8 及以后,需要處理較早版本的情況。
BOOL enable = NO;
if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0f) {
UIUserNotificationSettings *settings = [[UIApplication sharedApplication] currentUserNotificationSettings];
enable = UIUserNotificationTypeNone == settings.types ? NO : YES;
} else {
UIRemoteNotificationType type = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
enable = UIRemoteNotificationTypeNone == type ? NO : YES;
}
若用戶未開啟推送功能,可通過以下代碼跳轉(zhuǎn)至設(shè)置中有關(guān)該應(yīng)用的界面
UIApplication *application = [UIApplication sharedApplication];
NSURL *openSettingsURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
if ([application canOpenURL:openSettingsURL]) {
if ([application respondsToSelector:@selector(openURL:options:completionHandler:)]) {
[application openURL:openSettingsURL options:@{} completionHandler:nil];
} else {
[application openURL:openSettingsURL];
}
} else {
// Do something.
}