iOS推送,看这篇足够了
关于远程推送的相关配置网上已经有足够多的教程,这里就不复述了。这里讲述当客户端收到推送消息后,该怎样对其进行相应的逻辑处理。
工程的AppDelegate.m文件里提供了如下方法:
//当应用程序启动后,可能需要进行其他逻辑处理时调用的方法 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions; //成功注册registerUserNotificationSettings:后,回调的方法 - (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings; /// 注册失败调用 - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error; /// 用户同意接收通知后,会调用此程序 - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken; //收到推送后调用的方法(iOS 10 及以上) - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler; //收到推送后调用的方法(iOS 10 以下) - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler; //应用挂起前调用的方法(当有电话进来或者锁屏,应用会挂起) - (void)applicationWillResignActive:(UIApplication *)application; //当程序复原或启动时调用的方法 - (void)applicationDidBecomeActive:(UIApplication *)application; //应用将要进入后台调用的方法 - (void)applicationWillEnterForeground:(UIApplication *)application; //应用进入后台调用的方法 -(void)applicationDidEnterBackground:(UIApplication *)application; //应用将要退出 - (void)applicationWillTerminate:(UIApplication *)application;
首先为应用程序注册通知:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSDictionary *remoteNotification = [launchOptions objectForKey:@"UIApplicationLaunchOptionsRemoteNotificationKey"]; if (remoteNotification != nil) { self.isLaunchedByNotification = YES; }else{ self.isLaunchedByNotification = NO; } self.window=[[UIWindow alloc]initWithFrame:[[UIScreen mainScreen]bounds]]; if ([[UIDevice currentDevice].systemVersion floatValue] >= 10.0) { UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; center.delegate = self; [center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) { if (!error) { NSLog(@"succeeded!"); } }]; } else if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0){//iOS8-iOS9 UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIUserNotificationTypeSound) categories:nil]; [application registerUserNotificationSettings:settings]; [application registerForRemoteNotifications]; } else {//iOS8以下 [application registerForRemoteNotificationTypes: UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound]; } return YES; }
用户同意后,会调用此程序,获取系统的deviceToken,应把deviceToken传给服务器保存,此函数会在程序每次启动时调用(前提是用户允许通知):
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { NSLog(@"deviceToken = %@",deviceToken); NSString *token=[NSString stringWithFormat:@"%@",deviceToken]; token=[token stringByReplacingOccurrencesOfString:@"<" withString:@""]; token=[token stringByReplacingOccurrencesOfString:@">" withString:@""]; token=[token stringByReplacingOccurrencesOfString:@" " withString:@""]; //TODO //保存到本地并上传到服务器 }
收到推送消息后,进行相应的逻辑处理:
//iOS 10 及以上 - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler { completionHandler(UIBackgroundFetchResultNewData); NSDictionary *userInfo = response.notification.request.content.userInfo; //程序关闭状态点击推送消息打开 if (self.isLaunchedByNotification) { //TODO } else{ //前台运行 if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) { //TODO } //后台挂起时 else{ //TODO } //收到推送消息手机震动,播放音效 AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); AudioServicesPlaySystemSound(1007); } //设置应用程序角标数为0 [UIApplication sharedApplication].applicationIconBadgeNumber = 9999; [UIApplication sharedApplication].applicationIconBadgeNumber = 0; }
//iOS 10 以下 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { completionHandler(UIBackgroundFetchResultNewData); //程序关闭状态点击推送消息打开 if (self.isLaunchedByNotification) { //TODO } else{ //前台运行 if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) { //TODO } //后台挂起时 else{ //TODO } //收到推送消息手机震动,播放音效 AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); AudioServicesPlaySystemSound(1007); } //设置应用程序角标数为0 [UIApplication sharedApplication].applicationIconBadgeNumber = 9999; [UIApplication sharedApplication].applicationIconBadgeNumber = 0; }
应用进入后台时,设置角标数(应用图标右上角小红点,未读消息数):
-(void)applicationDidEnterBackground:(UIApplication *)application{ [[UIApplication sharedApplication] setApplicationIconBadgeNumber:123]; }
实际应用:
一、注册推送:
- (void)registerNotification { if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 10.0) { if (@available(iOS 10.0, *)) { UNUserNotificationCenter * center =[UNUserNotificationCenter currentNotificationCenter]; center.delegate=self; UNAuthorizationOptions options = UNAuthorizationOptionAlert|UNAuthorizationOptionSound|UNAuthorizationOptionBadge|UNAuthorizationOptionCarPlay; [center requestAuthorizationWithOptions:options completionHandler:^(BOOL granted, NSError * _Nullable error) { if (granted) { }else{ NSLog(@"授权失败"); } }]; } else { // Fallback on earlier versions } } else if ([[[UIDevice currentDevice] systemVersion] floatValue]>=8.0) { UIUserNotificationSettings *sets= [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge|UIUserNotificationTypeAlert|UIUserNotificationTypeSound) categories:nil]; [[UIApplication sharedApplication] registerUserNotificationSettings:sets]; } else { UIRemoteNotificationType type = UIRemoteNotificationTypeAlert|UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeSound; [[UIApplication sharedApplication] registerForRemoteNotificationTypes:type]; } [[UIApplication sharedApplication] registerForRemoteNotifications]; }
假如用户第一次安装拒绝推送需要提醒的,按以下方法:(例子:iOS10 )
UNUserNotificationCenter *userNotificationCenter = [UNUserNotificationCenter currentNotificationCenter]; userNotificationCenter.delegate = self; //获取通知设置信息 [userNotificationCenter getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) { //用户还未做出选择 if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) { //弹出授权框 [userNotificationCenter requestAuthorizationWithOptions:UNAuthorizationOptionAlert + UNAuthorizationOptionBadge + UNAuthorizationOptionSound completionHandler:^(BOOL granted, NSError * _Nullable error) { if (granted) {
}else{ //首次授权为未授权状态,之后为已授权状态 DLog(@"没有开启通知"); } }]; }
else if (settings.authorizationStatus == UNAuthorizationStatusDenied){ //用户不同意授权时,弹出提示(最好只弹一次) UIAlertController *noticeNotificationAlertController = [UIAlertController alertControllerWithTitle:@"友情提示" message:@"建议您开启通知功能,以便及时获取相关信息" preferredStyle:UIAlertControllerStyleAlert]; [noticeNotificationAlertController addAction:[UIAlertAction actionWithTitle:@"忽略" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { }]]; [noticeNotificationAlertController addAction:[UIAlertAction actionWithTitle:@"去开启" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action){ NSURL *appSettingsUrl = [NSURL URLWithString:UIApplicationOpenSettingsURLString]; if ([[UIApplication sharedApplication] canOpenURL:appSettingsUrl]) { [[UIApplication sharedApplication] openURL:appSettingsUrl]; } }]]; [self.window.rootViewController presentViewController:noticeNotificationAlertController animated:YES completion:NULL]; }else{ //已授权 DLog(@"已授权通知"); }
}];
二、本地推送:
iOS10之后:
//使用 UNNotification 本地通知 +(void)registerNotification:(NSInteger )alerTime{ // 使用 UNUserNotificationCenter 来管理通知 UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; //需创建一个包含待通知内容的 UNMutableNotificationContent 对象,注意不是 UNNotificationContent ,此对象为不可变对象。 UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init]; content.title = [NSString localizedUserNotificationStringForKey:@"Hello!" arguments:nil]; content.body = [NSString localizedUserNotificationStringForKey:@"Hello_message_body" arguments:nil]; content.sound = [UNNotificationSound defaultSound]; // 在 alertTime 后推送本地推送 UNTimeIntervalNotificationTrigger* trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:alerTime repeats:NO]; UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"FiveSecond" content:content trigger:trigger]; //添加推送成功后的处理! [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"本地通知" message:@"成功添加推送" preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil]; [alert addAction:cancelAction]; [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alert animated:YES completion:nil]; }]; }
iOS10以前:
+ (void)registerLocalNotificationInOldWay:(NSInteger)alertTime { UILocalNotification *notification = [[UILocalNotification alloc] init]; // 设置触发通知的时间 NSDate *fireDate = [NSDate dateWithTimeIntervalSinceNow:alertTime]; NSLog(@"fireDate=%@",fireDate); notification.fireDate = fireDate; // 时区 notification.timeZone = [NSTimeZone defaultTimeZone]; // 设置重复的间隔 notification.repeatInterval = kCFCalendarUnitSecond; // 通知内容 notification.alertBody = @"该起床了..."; notification.applicationIconBadgeNumber = 1; // 通知被触发时播放的声音 notification.soundName = UILocalNotificationDefaultSoundName; // 通知参数 NSDictionary *userDict = [NSDictionary dictionaryWithObject:@"开始学习iOS开发了" forKey:@"key"]; notification.userInfo = userDict; // 通知重复提示的单位,可以是天、周、月 notification.repeatInterval = NSDayCalendarUnit; // 将本地通知添加到调度池,定时发送 [[UIApplication sharedApplication] scheduleLocalNotification:notification]; // 立即发送 // [[UIApplication sharedApplication] presentLocalNotificationNow:localNotification]; }
本地通知相关代理方法:
// 当App在前台状态下,如果有通知会调用该方法
// 当应用程序在后台状态下,点击推送通知,程序从后台进入前台后,会调用该方法(从锁屏界面点击推送通知从后台进入前台也会执行)
// 当应用程序完全退出时不调用该方法
- (void)application:(UIApplication *)application didReceiveLocalNotification:(nonnull UILocalNotification *)notification{ NSLog(@"%@", notification); // 处理点击通知后对应的业务 UIApplicationState applicationState = [[UIApplication sharedApplication] applicationState]; if (applicationState == UIApplicationStateActive) { // 前台 // 例如QQ会增加tabBar上的badgeValue未读数量 } else if (applicationState == UIApplicationStateInactive) { // 从前台进入后台 // 例如QQ会打开对应的聊天窗口 NSInteger applicationIconBadgeNumber = application.applicationIconBadgeNumber - 1; application.applicationIconBadgeNumber = applicationIconBadgeNumber >= 0 ? applicationIconBadgeNumber : 0; } [application cancelLocalNotification:notification]; }
// 监听附加操作按钮
- (void)application:(UIApplication *)application handleActionWithIdentifier:(nullable NSString *)identifier forLocalNotification:(nonnull UILocalNotification *)notification completionHandler:(nonnull void (^)())completionHandler { NSLog(@"identifier:%@", identifier); completionHandler(); }
// 该方法在iOS9.0后调用,iOS9.0之前调用上面那个方法
- (void)application:(UIApplication *)app handleActionWithIdentifier:(nullable NSString *)identifier forLocalNotification:(nonnull UILocalNotification *)notification withResponseInfo:(nonnull NSDictionary *)responseInfo completionHandler:(nonnull void (^)())completionHandler { // ====identifier:no, content:{UIUserNotificationActionResponseTypedTextKey = "not agree";} NSLog(@"====identifier:%@, content:%@", identifier, responseInfo); completionHandler(); }
三、远程通知:
1、什么是远程推送
在联网的情况下,由远程服务器推送给客户端的通知,又称APNs(Apple Push Notification Services)不管应用是打开还是关闭的情况下,都能接收到服务器推送的远程通知在联网状态下,所有苹果设备都会与苹果服务器建立长连接
2、远程推送的实现原理:
1.打开App时: 发送
UDID
和BundleID
给APNs
加密后返回deviceToken
2.获取
Token
后,App调用接口,将用户身份信息和deviceToken
发给服务器,服务器记录3.当推送消息时, 服务器按照用户身份信息找到存储的
deviceToken
,将消息和deviToken
发送给APNs
4.苹果的APNs通过
deviceToken
, 找到指定设备的指定程序, 并将消息推送给用户
3、实现远程推送功能的前提
1.真机
2.调试阶段的证书
iOS_development.cer
用于真机调试的证书
aps_development.cer
用于真机推送调试能的证书
xxx.mobileprovision
描述文件,记录了能够调试的手机、电脑和程序3.发布阶段的证书
iOS_distribution.cer
用于发布app的证书
aps.cer
用于发布时,让app有推送功能的证书
xxx.mobileprovision
描述文件,记录了能够发布app的电脑
如何配置证书, 不在本教程内, 请读者自行处理, 或者参考视频教程
一、 注册远程推送并获取DeviceToken
1.创建iOS的项目,并输入项目名字
2.在AppDelegate
中导入头文件:
#import <UserNotifications/UserNotifications.h>
3.在application:didFinishLaunchingWithOptions
方法中, 注册远程通知
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //请求通知权限, 本地和远程共用 UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; [center requestAuthorizationWithOptions:UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert completionHandler:^(BOOLgranted, NSError * _Nullable error) { if(granted) { NSLog(@"请求成功"); } else{ NSLog(@"请求失败"); } }]; //注册远程通知 [[UIApplication sharedApplication] registerForRemoteNotifications]; //设置通知的代理 center.delegate = self; returnYES; }
4.在接收远程推送的DeviceToken
方法中, 获取Token
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { //将来需要将此Token上传给后台服务器 NSLog(@"token:%@", deviceToken); }
二、 iOS10之前通知的处理方法
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification { //此方法已被下面的方法代替 } -(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { //iOS10之前,接收远程通知时,不管在前台、后台还是程序杀死都会调用此方法 if (application.applicationState == UIApplicationStateActive) { // 前台收到通知处理 }else{ //后台或退出时点击通知处理 } completionHandler(); }
三、 iOS10远程推送通知的处理方法
当点击了推送后, 如果你希望进行处理. 那么在iOS10中, 还需要设置UNUserNotificationCenter
的delegate
, 并遵守UNUserNotificationCenterDelegate
协议.
以及实现下面实现3个方法, 用于处理点击通知时的不同情况的处理
willPresentNotification:withCompletionHandler
用于前台运行
didReceiveNotificationResponse:withCompletionHandler
用于后台及程序退出
didReceiveRemoteNotification:fetchCompletionHandler
用于静默推送
//设置通知的代理
center.delegate = self;
1.前台运行 会调用的方法
前台运行: 指的是程序正在运行中, 用户能看见程序的界面.
iOS10会出现通知横幅, 而在以前的框架中, 前台运行时, 不会出现通知的横幅.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void(^)(UNNotificationPresentationOptions))completionHandler { NSDictionary *userInfo = notification.request.content.userInfo; //前台运行推送 显示红色Label [self showLabelWithUserInfo:userInfo color:[UIColor redColor]]; //可以设置当收到通知后, 有哪些效果呈现(声音/提醒/数字角标) completionHandler(UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert); }
2.后台运行及程序退出 会调用的方法
后台运行: 指的是程序已经打开, 用户看不见程序的界面, 如锁屏和按Home键.
程序退出: 指的是程序没有运行, 或者通过双击Home键,关闭了程序.
1
2
3
4
5
6
7
8
9
|
- ( void )userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:( void (^)())completionHandler { NSDictionary *userInfo = response.notification.request.content.userInfo; //后台及退出推送 显示绿色Label [self showLabelWithUserInfo:userInfo color:[UIColor greenColor]]; completionHandler(); } |
3.静默推送通知 会调用的方法
静默推送: iOS7以后出现, 不会出现提醒及声音.
要求:
推送的payload
中不能包含alert
及sound
字段
需要添加content-available
字段, 并设置值为1
例如: {"aps":{"content-available":"1"},"PageKey”":"2"}
1
2
3
4
5
6
7
8
|
//如果是以前的旧框架, 此方法 前台/后台/退出/静默推送都可以处理 - ( void )application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:( void (^)(UIBackgroundFetchResult))completionHandler { //静默推送 显示蓝色Label [self showLabelWithUserInfo:userInfo color:[UIColor blueColor]]; completionHandler(UIBackgroundFetchResultNewData); } |
4.处理通知的公用方法
开发中, 点击通知的逻辑应当看自己程序的需求.
这里为了方便演示, 简单的将通知的值, 通过UILabel
显示在主界面上.
1
2
3
4
5
6
7
8
9
|
- ( void )showLabelWithUserInfo:(NSDictionary *)userInfo color:(UIColor *)color { UILabel *label = [UILabel new ]; label.backgroundColor = color; label.frame = CGRectMake(0, 250, [UIScreen mainScreen].bounds.size.width, 300); label.text = userInfo.description; label.numberOfLines = 0; [[UIApplication sharedApplication].keyWindow addSubview:label]; } |
iOS10本地通知:http://www.jianshu.com/p/5713fa2bfece