推送-iOS本地通知

前言

首先我们要明白通知和推送是不一样的。

通知是iOS操作系统层面上的功能,说白了就是iPhone上的通知条,通知中心等,App来了一条通知,系统来了升级通知,待办事项来了一条通知,这里的通知指的是iOS操作系统内的一个功能,更多体现在UI、交互、触发逻辑、通知方式上。 推送指的是由APNs服务器、ProviderService、iOS系统、App构成的通讯系统,是移动互联网与传统的Web最明显的区别的地方。正因为有了推送,实现了服务端能够反向与用户建立联系,而不是等待用户访问Web服务器。

本文主要讲的是通知

说明

从 iOS 10 新增的 UserNotifications Framework 可以发现,Apple 整合了原有散乱的 API,并且增加了许多强大的功能。以 Apple 官方的角度来看,也必然是相当重视推送服务对 App 的影响、以及对 Apple iOS 生态圈长远发展的影响。

iOS10的通知新功能,用户体验的提升和开发者能够发挥的地方非常多,使得iOS更具有竞争力。

  • iOS 10通知系统支持Images, GIFs, Audio and Video类型
  • iOS 10推出Notification Service Extension与Notification Content Extension,可以实现推送数据在展示前进行下载更新、定制通知UI
  • iOS 10统一了通知类型,具有时间间隔通知、地理位置通知和日历通知

iOS里的通知扩展

User Notifications Framework 介绍: 关系图:

User Notifications Framework类关系图

重点介绍:

UNUserNotificationCenter通知中心,用以管理通知的注册、权限获取和管理、通知的删除与更新,通过代理分发事件等。

UNNotification 通知实体,在UNUserNotificationCenter的代理回调事件中,告知App接收到一条通知,包含一个发起通知的请求UNNotificationRequest

UNNotificationRequest包含通知内容UNNotificationContent和触发器UNNotificationTrigger

UNNotificationContent 通知内容,通知的title,sound,badge以及相关的图像、声音、视频附件UNNotificationAttachment,触发打开App时候指定的LacnchImage等

UNNotificationResponse,用户在触发了按钮或者文本提交的UNNotificationAction的时候,会形成一个response,通过通知中心的代理方法回调给App进行处理或者是交给扩展处理。

UNNotificationServiceExtension,是一个在接收到APNs服务器推送过来的数据进行处理的服务扩展,如果App提供了服务扩展,那么APNs下发推送后在通知显示触发之前,会在UNNotificationServiceExtension内接收到,此处有大约30秒的处理时间,开发者可以进行一些数据下载、数据解密、更新等操作,然后交由而后的内容扩展(UNNotificationContentExtension)或者是App进行触发显示

UNNotificationCategory,用以定义一组样式类型,该分类包含了某一个通知包含的交互动作的组合,比如说UNNotificationRequest内包含了一个Category标示,那该通知就会以预定义好的交互按钮或者文本框添加到通知实体上。

UNNotificationAttachment,通知内容UNNotificationContent包含的附件,一般为图片、视频和音频,虽然iOS10的通知数据容量为4k,但依旧很少,在添加了UNNotificationServiceExtension扩展的情况下,可以在服务里下载图片,生成图片、视频等的本地缓存,UNNotificationAttachment根据缓存数据生成并添加到UNNotificationContent中,交由UI显示

UNNotificationAction,是通知中添加的action,展示在通知栏的下方。默认以的button样式展示。有一个文本输入的子类UNTextInputNotificationAction。可以在点击button之后弹出一个键盘,输入信息。用户点击信息和输入的信息可以在UNNotificationResponse中获取

系统级别的代理方法

// The method will be called on the delegate only if the application is in the foreground. 
 If the method is not implemented or the handler is not called in a timely manner 
 then the notification will not be presented.
  #翻译
[UNUserNotificationCenterDelegate willPresentNotification:withCompletionHandler:]
[UNUserNotificationCenterDelegate didReceiveNotificationResponse:withCompletionHandler:]

说了这么多,现在源码展示不同系统下的本地通知的实现,亲测有效。

源码实现iOS8-iOS10的本地通知

这里必须说一下iOS8-iOS10的本地通知跟 iOS10以后的不一样,在应用在前台时是不会有横幅或者弹框提示的,只会触发代理方法,想要展示本地通知,需要把应用切换到后台,当时就很纠结为什么不出现提示,浪费了很多时间。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
     
    if ([[UIApplication sharedApplication]  respondsToSelector:@selector(registerForRemoteNotifications)]) {
        // 这里 types 可以自定义,如果 types 为 0,那么所有的用户通知均会静默的接收,系统不会给用户任何提示(当然,App 可以自己处理并给出提示)
        UIUserNotificationType types = (UIUserNotificationType) (UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert);
        // 这里 categories 可暂不深入,本文后面会详细讲解。
        UIUserNotificationSettings *mySettings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
        // 当应用安装后第一次调用该方法时,系统会弹窗提示用户是否允许接收通知
        [[UIApplication sharedApplication] registerUserNotificationSettings:mySettings];
    }
    [self registerLocalNotificationInOldWay:10];
    
    return YES;
}

-(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;
    
    // ios8后,需要添加这个注册,才能得到授权
    if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerUserNotificationSettings:)]) {
        UIUserNotificationType type =  UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound;
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:type
                                                                                 categories:nil];
        [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
        // 通知重复提示的单位,可以是天、周、月
        notification.repeatInterval = NSCalendarUnitDay;
    } else {
        // 通知重复提示的单位,可以是天、周、月
        notification.repeatInterval = NSDayCalendarUnit;
    }
    
    // 执行通知注册
    [[UIApplication sharedApplication] scheduleLocalNotification:notification];
}

  //在后台情况下点击本地推送  或者   在前台收到本地通知都会触发这个方法
  - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
      NSLog(@"didReceiveLocalNotification notification");
      application.applicationIconBadgeNumber -= 1;

}

本地通知效果图

源码实现iOS10以后的本地通知

iOS10以后不论应用在前台或者在后台,都可以展示本地通知。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    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 (granted) {
                //点击允许
                NSLog(@"注册通知成功");
                [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
                    [self registerNotification:5];
                }];
            } else {
                //点击不允许
                NSLog(@"注册通知失败");
            }
        }];
        //注册推送(同iOS8)
        [[UIApplication sharedApplication] registerForRemoteNotifications];
    }
}

/** * 描述 使用 UNNotification 本地通知(iOS 10) * @param alerTime 多长时间后进行推送 **/
-(void)registerNotification:(NSInteger)alerTime
{
    // 1、创建通知内容,注:这里得用可变类型的UNMutableNotificationContent,否则内容的属性是只读的
    UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
    // 标题
    content.title = @"这是通知";
    // 次标题
    content.subtitle = @"这是通知subtitle";
    // 内容
    content.body = @"这是通知body这是通知body这是通知body这是通知body这是通知body这是通知body";
    
    // app显示通知数量的角标
    content.badge = [NSNumber numberWithInteger:4];
    
    // 通知的提示声音,这里用的默认的声音
    content.sound = [UNNotificationSound defaultSound];
    NSURL *imageUrl = [[NSBundle mainBundle] URLForResource:@"cell2@2x" withExtension:@"png"];
    UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"imageIndetifier" URL:imageUrl options:nil error:nil];
    // 附件 可以是音频、图片、视频 这里是一张图片
    content.attachments = @[attachment];
    // 标识符
    content.categoryIdentifier = @"categoryIndentifier";
    // 2、创建通知触发
    /* 触发器分三种: UNTimeIntervalNotificationTrigger : 在一定时间后触发,如果设置重复的话,timeInterval不能小于60 UNCalendarNotificationTrigger : 在某天某时触发,可重复 UNLocationNotificationTrigger : 进入或离开某个地理区域时触发 */
    
    UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:10 repeats:NO];
    //    NSDateComponents *components = [NSDateComponents new];
    //    components.second = 2.0f;
    //    UNCalendarNotificationTrigger *trigger = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:components repeats:NO];
    
    // 3、创建通知请求
    UNNotificationRequest *notificationRequest = [UNNotificationRequest requestWithIdentifier:@"KFGroupNotification" content:content trigger:trigger];
    // 4、将请求加入通知中心
    [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:notificationRequest withCompletionHandler:^(NSError * _Nullable error) {
        if (error == nil) {
            NSLog(@"已成功加推送%@",notificationRequest.identifier);
        }
    }];
}

#pragma mark - iOS10 推送代理

//不实现,通知不会有提示
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
    //应用在前台收到通知
    NSLog(@"========%@", notification);
    
    UNNotificationRequest *request = notification.request; // 原始请求
    NSDictionary * userInfo = notification.request.content.userInfo;//userInfo数据
    UNNotificationContent *content = request.content; // 原始内容
    NSString *title = content.title;  // 标题
    NSString *subtitle = content.subtitle;  // 副标题
    NSNumber *badge = content.badge;  // 角标
    NSString *body = content.body;    // 推送消息体
    UNNotificationSound *sound = content.sound;  // 指定的声音
    //建议将根据Notification进行处理的逻辑统一封装,后期可在Extension中复用~
     //如果需要在应用在前台也展示通知
    completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionSound|UNNotificationPresentationOptionAlert); // 回调block,将设置传入

}

// 对通知进行响应
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler {
    
    // 根据类别标识符处理目标反应
    if ([response.notification.request.content.categoryIdentifier isEqualToString:@"categoryIndentifier"]) {
        [self handleResponse:response];
        
        UNNotificationRequest *request = response.notification.request; // 原始请求
        UNNotificationContent *content = request.content; // 原始内容
        NSString *title = content.title;  // 标题
        NSString *subtitle = content.subtitle;  // 副标题
        NSNumber *badge = content.badge;  // 角标
        NSString *body = content.body;    // 推送消息体
        UNNotificationSound *sound = content.sound;
        //在此,可判断response的种类和request的触发器是什么,可根据远程通知和本地通知分别处理,再根据action进行后续回调
    }
    completionHandler();
}

- (void)handleResponse:(UNNotificationResponse *)response {
    
    NSString *actionIndentifier = response.actionIdentifier;
    [UIApplication sharedApplication].applicationIconBadgeNumber = 0;
    NSLog(@"%@",@"处理通知");

}

触发器 UNNotificationTrigger有四个子类:

  • UNPushNotificationTrigger,远程推送触发器,一般是远程推送推过来的通知带有这类触发器
  • UNTimeIntervalNotificationTrigger,时间间隔触发器,定时或者是重复,在本地推送设置中有用
  • UNCalendarNotificationTrigger,日历触发器,指定日期进行通知
  • UNLocationNotificationTrigger,地理位置触发器,指定触发通知的条件是地理位置CLRegion这个类型。

触发器和内容最后形成UNNotificationRequest,一个通知请求,本地通知的请求,直接交给通知中心进行发送,发送成功后,该通知会按照触发器的触发条件进行触发,并且会显示到通知中心上,用户可与指定的category交互方式与通知进行交互

效果图

小结

本地通知其实很有用处,可以做为一种提示使用,做类似布卡漫画这种也可以推送正在追的漫画的新章节并在后台静默下载,待到下载好再给用户发送一个本地推送,用户点开即看无需再联网。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券