iOS10本地推送,上线项目中的实际运用

离上次写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;
}
处于前台时收到消息.gif
处于后台时收到消息.gif

至此,消息已经可以成功发出,从前台点击查看或从后台点击都可以成功进行界面跳转,然后返回到之前的页面。
总结:

1、UNUserNotificationCenter对象的创建及内容配置可以根据需要放在控制器中,不一定非得放在APPdelegate中;
2、上面前台和后台点击查看消息内容是以发通知的形式进行处理的,其实可以直接用[self methodName]的形式调用,原因是接收通知的方法和点击查看消息的方法在同一个控制器中,如果不在同一个控制器中(跨界面)就得用通知进行接收了。
3、这里我的跟控制器是TabBar,所以界面跳转走的是第二个else if,如果是导航控制器或其他,会跟着变的。

最后,由于是客户的项目,就没有单独整理demo。本地通知实际运用并不难,自己稍微花点时间整理下,就全部弄明白了,思路流程都已经列出来了,可以根据需要一步一步来,这个弄清楚,其实远程通知也差不多了。好了,如果发现有不对或者需要优化的地方,请指出,真心感谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容