离上次写iOS集成极光推送之后,我还得这样做总结已过去半年了,半年的时间,在码代码的过程中,慢慢的体会到了一点,看到需求、设计不要急于写代码,先在脑子里想想,该怎么设计,应该考虑哪些方面的问题,怎么编写效率更高,用户体验更好等等?这样才能更快的提升。
有关iOS10推送的介绍和使用,早有很多前辈已写好了博客,这里我就不介绍怎么使用推送了,着重介绍本地推送在实际项目中的使用流程,以及需要注意的地方。苹果提供本地推送这么一个功能,我想一般是用来结合网络、时间针对性的提醒某些用户的。
相比远程推送,1.本地推送没有相关证书方面的要求;2.在没有自己平台推送服务器的情况下,可以在app内部对用户进行推送提醒。而远程推送最大的优点就是可以轻松利用三方平台,无论用户是否有网络,都能随时对app用户进行推送,当然也可以对个别用户进行推送。关键还得看需求~
谈谈最近公司接的一个项目中的一个需求,以下是项目需求:
用户在使用APP的过程中,要能及时(30s)看到他的交易信息,
1. 在APP内以弹窗的形式提醒用户,点击“查看”跳转到对应消息界面;
2. 在后台运行时,以推送通知的形式提醒用户,用户点击消息跳转到对应的消息界面。
由于没有自己的推送服务器,又涉及到网络请求,暂不考虑APP处于退出状态时的情况。
看到这么一个需求,当时就想到用推送,至于远程推还是本地推,当然选择后者,远程推的话必须借助极光推送平台,而且不能满足从公司后台获取用户实时交易的消息,并有针对性的给用户推送。
下面说说实现思路,涉及到iOS10推送API
、读取用户对通知权限的设置
、定时器
、通知中心
、界面跳转
、定时器和通知的移除
,以下关于本地通知都用iOS10的API。
1.给APP申请通知权限,
2.读取用户对通知的设置;
3.创建定时器;
4.添加通知中心观察者;
5.创建 UNUserNotificationCenter对象,配置推送内容;
6.分别处理前台和后台接收通知的情况;
7.用户点击通知后的界面跳转;
8.移除定时器、通知中心对象。
开始上代码:
- 1.给APP申请通知权限
// 1.给APP申请通知权限,关于请求权限(定位等)务必放在APPdelegate中,程序一启动马上提醒用户选择.否则设置-通知中心根本就没有此应用程序的通知设置,自己想去后台设置打开都找不到地方。
// 这是iOS10请求通知权限的API
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error) {
// 用户对通知的设置
[[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
DLog(@"%@", (long)settings.authorizationStatus == 2 ? @"成获取到通知权限" : @"用户关闭了通知");
if (settings.authorizationStatus != 2) {
DLog(@"用户关闭了通知");
}
}];
}];
- 2.检查用户对通知功能的设置状态,我放在首页viewDidLoad中,当用户一进入首页如果发现没有打开则以弹窗的形式提醒用户,引导其打开;否则不进行接下来的操作。注:以下代码都放在首页控制器中。
// 2.检查用户对通知功能的设置状态
- (BOOL)isPermissionedPushNotification {
if ([[UIApplication sharedApplication] currentUserNotificationSettings].types == UIUserNotificationTypeNone) {
DLog(@"用户关闭了通知功能");
// 弹窗
UIAlertController *alertvc = [UIAlertController alertControllerWithTitle:@"温馨提醒" message:@"请打开通知功能,否则无法收到交易提醒." preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"去设置" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
DLog(@"引导用户去设置中心打开通知");
// 点击跳转至设置中心
NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
if([[UIApplication sharedApplication] canOpenURL:url]) {
NSURL *url =[NSURL URLWithString:UIApplicationOpenSettingsURLString];
[[UIApplication sharedApplication] openURL:url];
}
}];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"忽略交易提醒" style:UIAlertActionStyleCancel handler:nil];
[alertvc addAction:cancelAction];
[alertvc addAction:confirmAction];
[self presentViewController:alertvc animated:YES completion:nil];
return false;
} else {
DLog(@"可以进行推送");
return true;
}
}
- 3.创建定时器,获得新消息未读数
- 4.创建通知中心观察者
- (void)viewDidLoad {
[super viewDidLoad];
// ...
if ([self isPermissionedPushNotification])
{
// 3.创建定时器,获得新消息未读数
self.timer = [NSTimer scheduledTimerWithTimeInterval:RequestTimeInterval target:self selector:@selector(setupUnreadTradeMsg) userInfo:nil repeats:YES];
// 设置timer模式
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
// 4.创建通知中心观察者
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(confirmReadNoticeContent:) name:@"ReadNoticeContent" object:nil];
}
// 定时器执行方法
- (void)setupUnreadTradeMsg {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[NetworkEngine signPostWithURL:PARAMQUERY parmas:params success:^(id requestResult) {
// ...
// 省略部分网络请求代码
// 设置app角标数量 = 未读消息数量
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:badge];
// 消息按钮红点
[self.msgBtn setBackgroundImage:[UIImage imageNamed:@"new_message"] forState:UIControlStateNormal];
// 将新的交易信息内容传入推送
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
// ...
// 发起推送
[self pushNotificationOfTradeMsg:params];
} failure:^(NSError *error) {
// [self showToast:@"请求失败,请检查网络连接"];
return ;
}];
});
- 5.配置推送内容
// 5.配置推送内容
- (void)pushNotificationOfTradeMsg:(NSDictionary *)params {
// 1、创建通知对象
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
// 必须设置代理,否则无法收到通知
center.delegate = self;
// 权限
[center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
// 2、创建通知内容
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
// 标题
content.title = params[@"title"];
content.body = params[@"content"];
// 声音
content.sound = [UNNotificationSound soundNamed:@"notification.wav"];
// 图片
NSURL *imageUrl = [[NSBundle mainBundle] URLForResource:@"msgImg" withExtension:@"png"];
UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"imageIndetifier" URL:imageUrl options:nil error:nil];
content.attachments = @[attachment];
// 标识符
content.categoryIdentifier = @"categoryIndentifier";
// 3、创建通知触发时间
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1.0 repeats:NO];
// 4、创建通知请求
UNNotificationRequest *notificationRequest = [UNNotificationRequest requestWithIdentifier:@"KFGroupNotification" content:content trigger:trigger];
// 5、将请求加入通知中心
[center addNotificationRequest:notificationRequest withCompletionHandler:^(NSError * _Nullable error) {
if (!error) {
DLog(@"已成功加入通知请求");
} else {
DLog(@"出错啦%@", [error localizedDescription]);
}
}];
}
}];
}
- 6.分别处理前台和后台接收通知的情况
/** 6.1当用户处于前台时,消息发送前走这个方法 */
-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler{
DLog(@"--------通知即将发出-------");
// 在前台时,通过此方法监听到消息发出,不让其在通知栏显示,以弹窗的形式展示出来;设置声音提示
completionHandler(UNNotificationPresentationOptionSound);
// 获取消息内容
NSMutableDictionary *content = [[NSMutableDictionary alloc] init];
[content setObject:notification.request.content.title forKey:@"content"];
[content setObject:notification.request.content.body forKey:@"body"];
// 弹窗
UIAlertController *alertvc = [UIAlertController alertControllerWithTitle:notification.request.content.title message:notification.request.content.body preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* updateAction = [UIAlertAction actionWithTitle:@"查看" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
// 创建通知对象
NSNotification *notice = [NSNotification notificationWithName:@"ReadNoticeContent" object:nil userInfo:content];
// 发送通知
[[NSNotificationCenter defaultCenter] postNotification:notice];
DLog(@"点击查看消息");
}];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"忽略" style:UIAlertActionStyleCancel handler:nil];
[alertvc addAction:cancelAction];
[alertvc addAction:updateAction];
[self presentViewController:alertvc animated:YES completion:nil];
}
/** 6.2不处于前台时,与通知交互走这个方法 */
-(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler {
completionHandler(UIBackgroundFetchResultNewData);
// 获取消息内容
NSMutableDictionary *content = [[NSMutableDictionary alloc] init];
[content setObject:response.notification.request.content.title forKey:@"content"];
[content setObject:response.notification.request.content.body forKey:@"body"];
// 判断是否为本地通知
if([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
NSLog(@"收到远程通知");
} else {
// 判断为本地通知
//创建通知对象
NSNotification *notice = [NSNotification notificationWithName:@"ReadNoticeContent" object:nil userInfo:content];
//发送通知
[[NSNotificationCenter defaultCenter] postNotification:notice];
}
}
- 7.用户点击通知后的界面跳转
// 点击查看消息,进行界面跳转
- (void)confirmReadNoticeContent:(NSDictionary *)content {
DLog(@"跳转页面 %@", content);
// 点击通知查看内容,角标清零,红点消失
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
[self.msgBtn setBackgroundImage:[UIImage imageNamed:@"nav_message"] forState:UIControlStateNormal];
// 获取当前跟控制器
UIViewController *rootVC = nil;
UIWindow *window = [UIApplication sharedApplication].windows.firstObject;
if ([window.rootViewController isKindOfClass:[GFNavigationController class]])
{
rootVC = [(GFNavigationController *)window.rootViewController visibleViewController];
}
else if ([window.rootViewController isKindOfClass:[GFTabBarController class]])
{
GFTabBarController *tabVC = (GFTabBarController *)window.rootViewController;
rootVC = [(GFNavigationController *)[tabVC selectedViewController] visibleViewController];
}
else
{
rootVC = window.rootViewController;
}
// 跳转到消息列表页面
TradeMessageViewController *tmvc = [[TradeMessageViewController alloc] init];
[rootVC.navigationController pushViewController:tmvc animated:YES];
}
- 8.移除定时器、通知中心对象
- (void)dealloc {
// 移除通知
[[NSNotificationCenter defaultCenter] removeObserver:self];
// 销毁定时器
[self.timer invalidate];
self.timer = nil;
}
至此,消息已经可以成功发出,从前台点击查看或从后台点击都可以成功进行界面跳转,然后返回到之前的页面。
总结:
1、UNUserNotificationCenter对象的创建及内容配置可以根据需要放在控制器中,不一定非得放在APPdelegate中;
2、上面前台和后台点击查看消息内容
是以发通知的形式进行处理的,其实可以直接用[self methodName]的形式调用,原因是接收通知的方法和点击查看消息的方法在同一个控制器中,如果不在同一个控制器中(跨界面)就得用通知进行接收了。
3、这里我的跟控制器是TabBar,所以界面跳转走的是第二个else if,如果是导航控制器或其他,会跟着变的。
最后,由于是客户的项目,就没有单独整理demo。本地通知实际运用并不难,自己稍微花点时间整理下,就全部弄明白了,思路流程都已经列出来了,可以根据需要一步一步来,这个弄清楚,其实远程通知也差不多了。好了,如果发现有不对或者需要优化的地方,请指出,真心感谢!