iOS 推送通知及推送扩展

栏目: IOS · 发布时间: 6年前

内容简介:iOS中的通知包括本地推送通知是由本地应用触发的,是基于时间的通知形式,一般用于闹钟定时、待办事项等提醒功能。发送本地推送通知的大体步骤如下:(1)注册本地通知;

iOS中的通知包括 本地推送通知远程推送通知 ,两者在iOS系统中都可以通过弹出横幅的形式来提醒用户,点击横幅会打开应用。在iOS 10及之后版本的系统中,还支持通知扩展功能( UNNotificationServiceExtension、UNNotificationContentExtension ),下面就来详细介绍iOS推送通知的相关功能及操作。

一、本地推送通知

本地推送通知是由本地应用触发的,是基于时间的通知形式,一般用于闹钟定时、待办事项等提醒功能。发送本地推送通知的大体步骤如下:

(1)注册本地通知;

(2)创建本地通知相关变量,并初始化;

(3)设置处理通知的时间 fireDate

(4)设置通知的内容:通知标题、通知声音、图标数字等;

(5)设置通知传递的参数 userInfo ,该字典内容可自定义(可选);

(6)添加这个本地通知到 UNUserNotificationCenter

1. 注册本地推送通知

- (void)sendLocalNotification {
    
    NSString *title = @"通知-title";
    NSString *sutitle = @"通知-subtitle";
    NSString *body = @"通知-body";
    NSInteger badge = 1;
    NSInteger timeInteval = 5;
    NSDictionary *userInfo = @{@"id": @"LOCAL_NOTIFY_SCHEDULE_ID"};
    
    if (@available(iOS 10.0, *)) {
        // 1.创建通知内容
        UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
        [content setValue:@(YES) forKeyPath:@"shouldAlwaysAlertWhileAppIsForeground"];
        content.sound = [UNNotificationSound defaultSound];
        content.title = title;
        content.subtitle = subtitle;
        content.body = body;
        content.badge = @(badge);

        content.userInfo = userInfo;

        // 2.设置通知附件内容
        NSError *error = nil;
        NSString *path = [[NSBundle mainBundle] pathForResource:@"logo_img_02@2x" ofType:@"png"];
        UNNotificationAttachment *att = [UNNotificationAttachment attachmentWithIdentifier:@"att1" URL:[NSURL fileURLWithPath:path] options:nil error:&error];
        if (error) {
            NSLog(@"attachment error %@", error);
        }
        content.attachments = @[att];
        content.launchImageName = @"icon_certification_status1@2x";

        // 3.设置声音
        UNNotificationSound *sound = [UNNotificationSound soundNamed:@"sound01.wav"];// [UNNotificationSound defaultSound];
        content.sound = sound;

        // 4.触发模式
        UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:timeInteval repeats:NO];

        // 5.设置UNNotificationRequest
        UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:LocalNotiReqIdentifer content:content trigger:trigger];

        // 6.把通知加到UNUserNotificationCenter, 到指定触发点会被触发
        [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
        }];

    } else {
    
        UILocalNotification *localNotification = [[UILocalNotification alloc] init];
        
        // 1.设置触发时间(如果要立即触发,无需设置)
        localNotification.timeZone = [NSTimeZone defaultTimeZone];
        localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:5];
        
        // 2.设置通知标题
        localNotification.alertBody = title;
        
        // 3.设置通知动作按钮的标题
        localNotification.alertAction = @"查看";
        
        // 4.设置提醒的声音
        localNotification.soundName = @"sound01.wav";// UILocalNotificationDefaultSoundName;
        
        // 5.设置通知的 传递的userInfo
        localNotification.userInfo = userInfo;
        
        // 6.在规定的日期触发通知
        [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];

        // 7.立即触发一个通知
        //[[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];
    }
}
复制代码

2. 取消本地推送通知

- (void)cancelLocalNotificaitons {
    
    // 取消一个特定的通知
    NSArray *notificaitons = [[UIApplication sharedApplication] scheduledLocalNotifications];
    // 获取当前所有的本地通知
    if (!notificaitons || notificaitons.count <= 0) { return; }
    for (UILocalNotification *notify in notificaitons) {
        if ([[notify.userInfo objectForKey:@"id"] isEqualToString:@"LOCAL_NOTIFY_SCHEDULE_ID"]) {
            if (@available(iOS 10.0, *)) {
                [[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers:@[LocalNotiReqIdentifer]];
            } else {
                [[UIApplication sharedApplication] cancelLocalNotification:notify];
            }
            break;
        }
    }
    // 取消所有的本地通知
    //[[UIApplication sharedApplication] cancelAllLocalNotifications];
}
复制代码

3. AppDelegate中的回调方法

在上面的代码中我们设置了 userInfo ,在iOS中收到并点击通知,则会自动打开应用。但是在不同版本的iOS系统中回调方式有所差异,如下:

  • 系统版本 < iOS 10
// 如果App已经完全退出:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;

// 当App已经完全退出时,获取userInfo参数过程如下:
// NSDictionary *userInfoLocal = (NSDictionary *)[launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
// NSDictionary *userInfoRemote = (NSDictionary *)[launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];

// 如果App还在运行(前台or后台)
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification;
复制代码
  • 系统版本 >= iOS 10
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
#pragma mark - UNUserNotificationCenterDelegate
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler __IOS_AVAILABLE(10.0) __TVOS_AVAILABLE(10.0) __WATCHOS_AVAILABLE(3.0);

- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler __IOS_AVAILABLE(10.0) __WATCHOS_AVAILABLE(3.0) __TVOS_PROHIBITED;
#endif
复制代码

4. 实现效果

  • app向用户请求推送通知权限的提示弹窗:

    iOS 推送通知及推送扩展
  • app处于不同状态(前台、后台、锁屏)时弹出通知的效果:

    iOS 推送通知及推送扩展
    iOS 推送通知及推送扩展
    iOS 推送通知及推送扩展

PS:

  • 当用户拒绝授权推送通知时, app 无法接收通知;(用户可以到设置->通知->相应 app ,手动设置通知选项)
  • 通知的声音在代码中指定,由系统播放,时长必须在 30s 内,否则将被默认声音替换,并且自定义声音文件必须放到 main bundle 中。
  • 本地通知有数量限制,超过一定数量(64个)将被系统忽略(数据来源于网络,具体时间间隔待验证)。

二、远程推送通知

远程推送通知是通过苹果的 APNsApple Push Notification service )发送到 app ,而 APNs 必须先知道用户设备的令牌( device token )。在启动时, appAPNs 通信并接收 device token ,然后将其转发到 App ServerApp Server 将该令牌和要发送的通知消息发送至 APNs PS:苹果官网APNs概述

远程推送通知的传递过程涉及几个关键组件:

  • App Server
  • Apple推送通知服务(APNs)
  • 用户的设备(iPhone、iPad、iTouch、Mac等)
  • 相应的app

苹果官方提供的远程推送通知的传递示意图如下:

iOS 推送通知及推送扩展

各关键组件之间的交互细节:

iOS 推送通知及推送扩展
  • 开发远程推送功能首先要设置正确的推送证书和权限,步骤如下:

    1)根据工程的 Bundle Identifier ,在苹果开发者平台中创建同名 App ID ,并勾选 Push Notifications 服务;

    2)在工程的“Capabilities”中设置 Push NotificationsON

    3)远程推送必须使用真机调试,因为模拟器无法获取得到 device token

  • 在设置好证书和权限后,按照以下步骤开发远程推送功能:

1. 注册远程通知

// iOS 8及以上版本的远程推送通知注册
- (void)registerRemoteNotifications
{
    if (@available(iOS 10.0, *)) {
        UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
        center.delegate = self;
        [center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionCarPlay) completionHandler:^(BOOL granted, NSError *_Nullable error) {
            if (!error) {
                NSLog(@"request authorization succeeded!");
                [[UIApplication sharedApplication] registerForRemoteNotifications];
            } else {
                NSLog(@"request authorization failed!");
            }
        }];
    } else {
        UIUserNotificationType types = (UIUserNotificationTypeAlert | UIUserNotificationTypeSound | UIUserNotificationTypeBadge);
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
        [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
        [[UIApplication sharedApplication] registerForRemoteNotifications];
    }
}
复制代码

2. App获取device token

  • 在注册远程通知后,获取 device token 的回调方法:
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
复制代码
  • 获取 device token 失败的回调方法:
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error;
复制代码

3. app将device token发送给App Server

只有苹果公司知道 device token 的生成算法,保证唯一, device token 在app卸载后重装等情况时会变化,因此为确保 device token 变化后app仍然能够正常接收服务器端发送的通知,建议每次启动应用都将获取到的 device token 传给 App Server

4. App Server将device token和要推送的消息发送给APNs

将指定的 device token 和消息内容发送给 APNs 时,必须按照苹果官方的消息格式组织消息内容。 PS:远程通知消息的字段、 创建远程通知消息

消息格式: {"aps":{"alert":{"title":"通知的title","subtitle":"通知的subtitle","body":"通知的body","title-loc-key":"TITLE_LOC_KEY","title-loc-args":["t_01","t_02"],"loc-key":"LOC_KEY","loc-args":["l_01","l_02"]},"sound":"sound01.wav","badge":1,"mutable-content":1,"category": "realtime"},"msgid":"123"}

5. APNs根据device token查找相应设备,并推送消息

一般情况 APNs 可以根据 deviceToken 将消息成功推送到相应设备中,但也存在用户卸载程序等导致推送消息失败的情况,这时 App Server 会收到 APNs 返回的错误信息)。

6. AppDelegate中的回调方法

// iOS<10时,且app被完全杀死
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;

// 注:iOS10以上如果不使用UNUserNotificationCenter时,也将走此回调方法
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo;

// 支持iOS7及以上系统
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler;

//  iOS>=10: app在前台获取到通知
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler;

//  iOS>=10: 点击通知进入app时触发(杀死/切到后台唤起)
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler;
复制代码

在AppDelegate中注册远程推送通知并解析通知数据的完整代码如下:

#import "AppDelegate.h"
#import "ViewController.h"

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
#import <UserNotifications/UserNotifications.h>
#endif

@interface AppDelegate () <UNUserNotificationCenterDelegate>

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
   
    ViewController *controller = [[ViewController alloc] init];
    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:controller];
    _window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    [_window setRootViewController:nav];
    [_window makeKeyAndVisible];
    
    ////注册本地推送通知(具体操作在ViewController中)
    //[self registerLocalNotification];
    // 注册远程推送通知
    [self registerRemoteNotifications];
    
    return YES;
}

- (void)registerLocalNotification {

    if (@available(iOS 10.0, *)) {
        UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
        center.delegate = self;
        [center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) {
            if (!error) {
                NSLog(@"request authorization succeeded!");
            }
        }];
    } else {
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil];
        [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
    }
}


- (void)registerRemoteNotifications
{
    if (@available(iOS 10.0, *)) {
        UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
        center.delegate = self;
        [center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionCarPlay) completionHandler:^(BOOL granted, NSError *_Nullable error) {
            if (!error) {
                NSLog(@"request authorization succeeded!");
                [[UIApplication sharedApplication] registerForRemoteNotifications];
            } else {
                NSLog(@"request authorization failed!");
            }
        }];
    } else {
        UIUserNotificationType types = (UIUserNotificationTypeAlert | UIUserNotificationTypeSound | UIUserNotificationTypeBadge);
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
        [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
        [[UIApplication sharedApplication] registerForRemoteNotifications];
    }
}


- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
    
    NSLog(@"didRegisterUserNotificationSettings");
}

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
    
    NSLog(@"app收到本地推送(didReceiveLocalNotification:):%@", notification.userInfo);
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    
    // 获取并处理deviceToken
    NSString *token = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];
    token = [token stringByReplacingOccurrencesOfString:@" " withString:@""];
    NSLog(@"DeviceToken:%@\n", token);
}

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
    
    NSLog(@"didFailToRegisterForRemoteNotificationsWithError: %@", error.description);
}

// 注:iOS10以上如果不使用UNUserNotificationCenter时,也将走此回调方法
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
    // iOS6及以下系统
    if (userInfo) {
        if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {// app位于前台通知
            NSLog(@"app位于前台通知(didReceiveRemoteNotification:):%@", userInfo);
        } else {// 切到后台唤起
            NSLog(@"app位于后台通知(didReceiveRemoteNotification:):%@", userInfo);
        }
    }
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler NS_AVAILABLE_IOS(7_0) {
    // iOS7及以上系统
    if (userInfo) {
        if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
            NSLog(@"app位于前台通知(didReceiveRemoteNotification:fetchCompletionHandler:):%@", userInfo);
        } else {
            NSLog(@"app位于后台通知(didReceiveRemoteNotification:fetchCompletionHandler:):%@", userInfo);
        }
    }
    completionHandler(UIBackgroundFetchResultNewData);
}


#pragma mark - iOS>=10 中收到推送消息

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0

- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
API_AVAILABLE(ios(10.0)){
    NSDictionary * userInfo = notification.request.content.userInfo;
    if (userInfo) {
        NSLog(@"app位于前台通知(willPresentNotification:):%@", userInfo);
    }
    completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionSound|UNNotificationPresentationOptionAlert);
}

- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
API_AVAILABLE(ios(10.0)){
    NSDictionary * userInfo = response.notification.request.content.userInfo;
    if (userInfo) {
        NSLog(@"点击通知进入App时触发(didReceiveNotificationResponse:):%@", userInfo);
    }
    completionHandler();
}

#endif


@end
复制代码

7. 使用Pusher工具模拟App Server推送通知

Pusher SmartPush工具 一样,是优秀的远程推送测试工具,工具界面如下:

iOS 推送通知及推送扩展
  • Pusher的使用步骤说明:

    1)选择 p12 格式的推送证书;

    2)设置是否为测试环境(默认勾选为测试环境,由于推送证书分为测试证书和生产证书,并且苹果的 APNs 也分为测试和生产两套环境,因此 Pusher 需要手动勾选推送环境);

    3)输入 device token

    4)输入符合苹果要求格式的 aps 字符串;

    5)执行推送。

效果如下:

iOS 推送通知及推送扩展
点击横幅打开app,在回调方法中获取到的 json

串如下:

iOS 推送通知及推送扩展

PS:

  • 要使用远程推送通知功能,需要至少启动app一次;
  • 设备不连网,是无法注册远程推送通知的;
  • 推送过程中aps串可在适当位置 添加自定义字段 ,消息上限为 4 KB

三、iOS 通知扩展

iOS 10及之后的推送通知具有扩展功能,包括两个方面:

  • 通知服务扩展(UNNotificationServiceExtension),是在收到通知后且展示通知前允许开发者做一些事情,比如添加附件、加载网络请求等。点击查看官网文档
  • 通知内容扩展(UNNotificationContentExtension),是在展示通知时展示一个自定义的用户界面。点击查看官网文档

1. 创建UNNotificationServiceExtension和UNNotificationContentExtension:

iOS 推送通知及推送扩展
iOS 推送通知及推送扩展

注意:

  • target支持的iOS版本为10.0及以上,且当前系统支持target版本。

2. 通知服务扩展UNNotificationServiceExtension

在NotificationService.m文件中,有两个回调方法:

// 系统接到通知后,有最多30秒在这里重写通知内容(如下载附件并更新通知)
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent *contentToDeliver))contentHandler;
// 处理过程超时,则收到的通知直接展示出来
- (void)serviceExtensionTimeWillExpire;
复制代码

在通知服务扩展中加载网络请求,代码如下:

#import "NotificationService.h"
#import <AVFoundation/AVFoundation.h>

@interface NotificationService ()

@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;

@end

@implementation NotificationService

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    
    //// Modify the notification content here...
    self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [ServiceExtension modified]", self.bestAttemptContent.title];
    
    // 设置UNNotificationAction
    UNNotificationAction * actionA  =[UNNotificationAction actionWithIdentifier:@"ActionA" title:@"A_Required" options:UNNotificationActionOptionAuthenticationRequired];
    UNNotificationAction * actionB = [UNNotificationAction actionWithIdentifier:@"ActionB" title:@"B_Destructive" options:UNNotificationActionOptionDestructive];
    UNNotificationAction * actionC = [UNNotificationAction actionWithIdentifier:@"ActionC" title:@"C_Foreground" options:UNNotificationActionOptionForeground];
    UNTextInputNotificationAction * actionD = [UNTextInputNotificationAction actionWithIdentifier:@"ActionD"
                                                                                            title:@"D_InputDestructive"
                                                                                          options:UNNotificationActionOptionDestructive
                                                                             textInputButtonTitle:@"Send"
                                                                             textInputPlaceholder:@"input some words here ..."];
    NSArray *actionArr = [[NSArray alloc] initWithObjects:actionA, actionB, actionC, actionD, nil];
    NSArray *identifierArr = [[NSArray alloc] initWithObjects:@"ActionA", @"ActionB", @"ActionC", @"ActionD", nil];
    UNNotificationCategory * notficationCategory = [UNNotificationCategory categoryWithIdentifier:@"QiShareCategoryIdentifier"
                                                                                          actions:actionArr
                                                                                intentIdentifiers:identifierArr
                                                                                          options:UNNotificationCategoryOptionCustomDismissAction];
    [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:[NSSet setWithObject:notficationCategory]];
    
    // 设置categoryIdentifier
    self.bestAttemptContent.categoryIdentifier = @"QiShareCategoryIdentifier";
    
    // 加载网络请求
    NSDictionary *userInfo =  self.bestAttemptContent.userInfo;
    NSString *mediaUrl = userInfo[@"media"][@"url"];
    NSString *mediaType = userInfo[@"media"][@"type"];
    if (!mediaUrl.length) {
        self.contentHandler(self.bestAttemptContent);
    } else {
        [self loadAttachmentForUrlString:mediaUrl withType:mediaType completionHandle:^(UNNotificationAttachment *attach) {
            
            if (attach) {
                self.bestAttemptContent.attachments = [NSArray arrayWithObject:attach];
            }
            self.contentHandler(self.bestAttemptContent);
        }];
    }
}

- (void)loadAttachmentForUrlString:(NSString *)urlStr withType:(NSString *)type completionHandle:(void(^)(UNNotificationAttachment *attach))completionHandler
{
    __block UNNotificationAttachment *attachment = nil;
    NSURL *attachmentURL = [NSURL URLWithString:urlStr];
    NSString *fileExt = [self getfileExtWithMediaType:type];
    
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    [[session downloadTaskWithURL:attachmentURL completionHandler:^(NSURL *temporaryFileLocation, NSURLResponse *response, NSError *error) {
        if (error) {
            NSLog(@"加载多媒体失败 %@", error.localizedDescription);
        } else {
            NSFileManager *fileManager = [NSFileManager defaultManager];
            NSURL *localURL = [NSURL fileURLWithPath:[temporaryFileLocation.path stringByAppendingString:fileExt]];
            [fileManager moveItemAtURL:temporaryFileLocation toURL:localURL error:&error];
            
            // 自定义推送UI需要
            NSMutableDictionary * dict = [self.bestAttemptContent.userInfo mutableCopy];
            [dict setObject:[NSData dataWithContentsOfURL:localURL] forKey:@"image"];
            self.bestAttemptContent.userInfo = dict;
            
            NSError *attachmentError = nil;
            attachment = [UNNotificationAttachment attachmentWithIdentifier:@"QiShareCategoryIdentifier" URL:localURL options:nil error:&attachmentError];
            if (attachmentError) {
                NSLog(@"%@", attachmentError.localizedDescription);
            }
        }
        completionHandler(attachment);
    }] resume];
}

- (NSString *)getfileExtWithMediaType:(NSString *)mediaType {
    NSString *fileExt = mediaType;
    if ([mediaType isEqualToString:@"image"]) {
        fileExt = @"jpg";
    }
    if ([mediaType isEqualToString:@"video"]) {
        fileExt = @"mp4";
    }
    if ([mediaType isEqualToString:@"audio"]) {
        fileExt = @"mp3";
    }
    return [@"." stringByAppendingString:fileExt];
}

- (void)serviceExtensionTimeWillExpire {
    // Called just before the extension will be terminated by the system.
    // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
    self.contentHandler(self.bestAttemptContent);
}

@end
复制代码

消息内容格式: {"aps":{"alert":{"title":"Title...","subtitle":"Subtitle...","body":"Body..."},"sound":"default","badge": 1,"mutable-content": 1,"category": "QiShareCategoryIdentifier",},"msgid":"123","media":{"type":"image","url":" www.fotor.com/images2/fea… "}}

PS:

  • 加载并处理附件时间上限为30秒,否则,通知按系统默认形式弹出;
  • UNNotificationAttachment的url接收的是本地文件的url;
  • 服务端在处理推送内容时,最好加上媒体类型字段;
  • aps字符串中的mutable-content字段需要设置为1;
  • 在对NotificationService进行debug时,需要在Xcode顶栏选择编译运行的target为NotificationService,否则无法进行实时debug。

3. 通知内容扩展UNNotificationContentExtension

通知内容扩展界面NotificationViewController的结构如下:

iOS 推送通知及推送扩展
  • 设置actions: 从NotificationViewController直接继承于ViewController,因此可以在这个类中重写相关方法,来修改界面的相关布局及样式。在这个界面展开之前,用户可以通过UNNotificationAction与相应推送通知交互,但是用户和这个通知内容扩展界面无法直接交互。
  • 设置category: 推送通知内容中的category字段,与UNNotificationContentExtension的info.plist中UNNotificationExtensionCategory字段的值要匹配,系统才能找到自定义的UI。

在aps串中直接设置category字段,例如: { "aps":{ "alert":"Testing...(0)","badge":1,"sound":"default","category":"QiShareCategoryIdentifier"}}

在NotificationService.m中设置category的值如下:

self.bestAttemptContent.categoryIdentifier = @"QiShareCategoryIdentifier";
复制代码

info.plist中关于category的配置如下:

iOS 推送通知及推送扩展
  • UNNotificationContentExtension协议:NotificationViewController 中生成时默认实现了。

简单的英文注释很明了:

// This will be called to send the notification to be displayed by
// the extension. If the extension is being displayed and more related
// notifications arrive (eg. more messages for the same conversation)
// the same method will be called for each new notification.
- (void)didReceiveNotification:(UNNotification *)notification;

// If implemented, the method will be called when the user taps on one
// of the notification actions. The completion handler can be called
// after handling the action to dismiss the notification and forward the
// action to the app if necessary.
- (void)didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(UNNotificationContentExtensionResponseOption))completion

// Called when the user taps the play or pause button.
- (void)mediaPlay;
- (void)mediaPause;
复制代码
  • UNNotificationAttachment:attachment支持

    1)音频5M(kUTTypeWaveformAudio/kUTTypeMP3/kUTTypeMPEG4Audio/kUTTypeAudioInterchangeFileFormat)

    2)图片10M(kUTTypeJPEG/kUTTypeGIF/kUTTypePNG)

    3)视频50M(kUTTypeMPEG/kUTTypeMPEG2Video/kUTTypeMPEG4/kUTTypeAVIMovie)

4. 自定义内容扩展界面与内容扩展功能联合使用时,代码如下:

#import "NotificationViewController.h"
#import <UserNotifications/UserNotifications.h>
#import <UserNotificationsUI/UserNotificationsUI.h>

#define Margin      15

@interface NotificationViewController () <UNNotificationContentExtension>

@property (nonatomic, strong) UILabel *label;
@property (nonatomic, strong) UILabel *subLabel;
@property (nonatomic, strong) UIImageView *imageView;

@property (nonatomic, strong) UILabel *hintLabel;

@end

@implementation NotificationViewController

- (void)viewDidLoad {

    [super viewDidLoad];
    
    CGPoint origin = self.view.frame.origin;
    CGSize size = self.view.frame.size;
    
    self.label = [[UILabel alloc] initWithFrame:CGRectMake(Margin, Margin, size.width-Margin*2, 30)];
    self.label.autoresizingMask = UIViewAutoresizingFlexibleWidth;
    [self.view addSubview:self.label];
    
    self.subLabel = [[UILabel alloc] initWithFrame:CGRectMake(Margin, CGRectGetMaxY(self.label.frame)+10, size.width-Margin*2, 30)];
    self.subLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
    [self.view addSubview:self.subLabel];
    
    self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(Margin, CGRectGetMaxY(self.subLabel.frame)+10, 100, 100)];
    [self.view addSubview:self.imageView];
    
    self.hintLabel = [[UILabel alloc] initWithFrame:CGRectMake(Margin, CGRectGetMaxY(self.imageView.frame)+10, size.width-Margin*2, 20)];
    [self.hintLabel setText:@"我是hintLabel"];
    [self.hintLabel setFont:[UIFont systemFontOfSize:14]];
    [self.hintLabel setTextAlignment:NSTextAlignmentLeft];
    [self.view addSubview:self.hintLabel];
    self.view.frame = CGRectMake(origin.x, origin.y, size.width, CGRectGetMaxY(self.imageView.frame)+Margin);

    // 设置控件边框颜色
    [self.label.layer setBorderColor:[UIColor redColor].CGColor];
    [self.label.layer setBorderWidth:1.0];
    [self.subLabel.layer setBorderColor:[UIColor greenColor].CGColor];
    [self.subLabel.layer setBorderWidth:1.0];
    [self.imageView.layer setBorderWidth:2.0];
    [self.imageView.layer setBorderColor:[UIColor blueColor].CGColor];
    [self.view.layer setBorderWidth:2.0];
    [self.view.layer setBorderColor:[UIColor cyanColor].CGColor];
}

- (void)didReceiveNotification:(UNNotification *)notification {
    
    self.label.text = notification.request.content.title;
    self.subLabel.text = [NSString stringWithFormat:@"%@ [ContentExtension modified]", notification.request.content.subtitle];
    
    NSData *data = notification.request.content.userInfo[@"image"];
    UIImage *image = [UIImage imageWithData:data];
    [self.imageView setImage:image];
}

- (void)didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(UNNotificationContentExtensionResponseOption))completion {
    
    [self.hintLabel setText:[NSString stringWithFormat:@"触发了%@", response.actionIdentifier]];
    if ([response.actionIdentifier isEqualToString:@"ActionA"]) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            completion(UNNotificationContentExtensionResponseOptionDismiss);
        });
    } else if ([response.actionIdentifier isEqualToString:@"ActionB"]) {

    } else if ([response.actionIdentifier isEqualToString:@"ActionC"]) {

    }  else if ([response.actionIdentifier isEqualToString:@"ActionD"]) {

    } else {
        completion(UNNotificationContentExtensionResponseOptionDismiss);
    }
    completion(UNNotificationContentExtensionResponseOptionDoNotDismiss);
}

@end
复制代码

手机收到通知时的展示(aps串以上面第2点中提到的“消息内容格式”为例)

iOS 推送通知及推送扩展

说明:

  • 服务扩展target和内容扩展target在配置中所支持的系统版本要在iOS10及以上;
  • 自定义视图的大小可以通过设置NotificationViewController的preferredContentSize大小来控制,但是用户体验稍显突兀,可以通过设置info.plist中的UNNotificationExtensionInitialContentSizeRatio属性的值来优化;
  • contentExtension中的info.plist中NSExtension下的NSExtensionAttributes字段下可以配置以下属性的值,UNNotificationExtensionCategory:表示自定义内容假面可以识别的category,可以为数组,也即可以为这个content绑定多个通知;UNNotificationExtensionInitialContentSizeRatio:默认的UI界面的宽高比;UNNotificationExtensionDefaultContentHidden:是否显示系统默认的标题栏和内容,可选参数;UNNotificationExtensionOverridesDefaultTitle:是否让系统采用消息的标题作为通知的标题,可选参数。
  • 处理通知内容扩展的过程中关于identifier的设置共有五处(UNNotificationAction、UNNotificationCategory、bestAttemptContent、contentExtension中的info.plist中,aps字符串中),请区别不同identifier的作用。
  • 两个扩展联合使用,在XCode中选择当前target,才能打断点看到相应log信息。

工程源码: github.com/QiShare/QiN…


以上所述就是小编给大家介绍的《iOS 推送通知及推送扩展》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Netty实战

Netty实战

诺曼·毛瑞尔(Norman Maurer)、马文·艾伦·沃尔夫泰尔(Marvin Allen Wolfthal) / 何品 / 人民邮电出版社 / 2017-5-1 / 69.00

编辑推荐 - Netty之父”Trustin Lee作序推荐 - 阿里巴巴中间件高级技术专家为本书中文版作序推荐 - 系统而详细地介绍了Netty的各个方面并附带了即用型的优质示例 - 附带行业一线公司的案例研究 - 极实用的Netty技术书 无论是构建高性能的Web、游戏服务器、推送系统、RPC框架、消息中间件还是分布式大数据处理引擎,都离不开Nett......一起来看看 《Netty实战》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器