iOS10的推送框架UserNotifications

iOS10發(fā)布以來(lái)捂掰,相信各位開(kāi)發(fā)者能踩的坑也應(yīng)該踩得差不多了;但也許正是因?yàn)槊看翁O(píng)果都會(huì)更新比較多的東西,才會(huì)覺(jué)得搞iOS很有意思吧袖瞻。(不知道大家會(huì)不會(huì)覺(jué)得樓主這種想法有點(diǎn)坑?)

推送是iOS10系統(tǒng)變動(dòng)比較大的一個(gè)地方,對(duì)于這種大變動(dòng)不瞅瞅一下著實(shí)不符合樓主的性格拆吆,那么樓主就在求知欲的推動(dòng)下(畢竟還得給自己的項(xiàng)目進(jìn)行適配不是..)就花了點(diǎn)時(shí)間對(duì)UserNotifications這個(gè)框架進(jìn)行了一個(gè)整體的了解聋迎,希望在這里記錄一下,希望能夠幫助對(duì)這個(gè)框架有興趣的小伙伴枣耀。

如果大家的項(xiàng)目中推送是使用了極光霉晕、友盟等第三方推送,那么請(qǐng)根據(jù)他們的官方文檔以及最新的SDK對(duì)iOS10進(jìn)行適配即可捞奕,跟著文檔一步一步走也比較簡(jiǎn)單牺堰。但是如果想要真實(shí)的了解原框架的流程,建議還是使用原生推送吧颅围,樓主本文博客下使用的就是原生推送伟葫。

如何配置原生推送,推薦有夢(mèng)想的蝸牛 一步一步教你做ios推送院促,這位博主寫(xiě)的很詳細(xì)筏养,雖然開(kāi)發(fā)者界面已經(jīng)不是那樣子了,但實(shí)質(zhì)的東西還是沒(méi)有變化的常拓。如果按照推薦博文的配置(!一定要確認(rèn)所有的配置都沒(méi)有問(wèn)題!)渐溶,但依舊收不到遠(yuǎn)程推送,那么估計(jì)就和樓主一樣“中獎(jiǎng)”了弄抬,可能是PHP端交互出問(wèn)題了茎辐,這個(gè)時(shí)候推薦大家使用PushMeBody來(lái)完成遠(yuǎn)程推送。(使用PushMesBody中的device token直接復(fù)制打印的device token即可,不需要去掉空格)

為了能夠區(qū)分它與之前用法的異同拖陆,樓主還是盡可能的想通過(guò)比較的方法實(shí)現(xiàn)推送的相關(guān)功能弛槐。
所有coding都在 RITL/RITLPushNoticationDemo(如果有幫助,請(qǐng)Star表示支持一下慕蔚,感謝)丐黄。
如果有什么問(wèn)題,也請(qǐng)及時(shí)指出孔飒,共同進(jìn)步灌闺,感謝。

推送的注冊(cè)

注冊(cè)的位置沒(méi)有任何的變化坏瞄,是在Appdelegate中的-application: didFinishLaunchingWithOptions:方法里面進(jìn)行注冊(cè)桂对,樓主為了簡(jiǎn)化它的代碼,將注冊(cè)功能封裝成了一個(gè)RITLNotificationManager類(lèi)鸠匀,所以該方法下只需一句話即可:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    //初始化個(gè)數(shù)
    [UIApplication sharedApplication].applicationIconBadgeNumber = 0;
    
    //注冊(cè)所有的推送
    [[RITLNotificationManager sharedInstance]registerRemoteNotificationsApplication:self];

    return YES;
}


注冊(cè)具體代碼實(shí)現(xiàn)

RITLNotificationManager的聲明(樓主在聲明文件中使用了預(yù)編譯進(jìn)行方法的聲明蕉斜,實(shí)際的開(kāi)發(fā)中建議不要這么寫(xiě),將方法分開(kāi)便是缀棍,不同的版本使用不同的方法即可)以及實(shí)現(xiàn)方法如下:

//RITLNotificationManager.h(實(shí)際Demo中它在"RITLOriginPushAppDelegate+RITLNotificationManager.h"文件下)

/// 注冊(cè)遠(yuǎn)程推送
#ifdef __IPHONE_10_0
- (void)registerRemoteNotificationsApplication:(id<UNUserNotificationCenterDelegate>)application;
#else
- (void)registerRemoteNotificationsApplication:(id)application;
#endif
//RITLOriginPushAppDelegate+RITLNotificationManager.m

#ifdef __IPHONE_10_0
-(void)registerRemoteNotificationsApplication:(id<UNUserNotificationCenterDelegate>)application
#else
- (void)registerRemoteNotificationsApplication:(id)application
#endif
{
#ifdef __IPHONE_10_0

    //設(shè)置代理對(duì)象
    [UNUserNotificationCenter currentNotificationCenter].delegate = application;
    
    // 請(qǐng)求權(quán)限
    [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:UNAuthorizationOptionBadge|UNAuthorizationOptionSound|UNAuthorizationOptionAlert completionHandler:^(BOOL granted, NSError * _Nullable error) {
        
        if (granted == true)//如果準(zhǔn)許宅此,注冊(cè)推送
        {
            [[UIApplication sharedApplication]registerForRemoteNotifications];
        }  
    }];

#else
    
#ifdef __IPHONE_8_0 //適配iOS 8
    [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert categories:nil]];
    
    [[UIApplication sharedApplication]registerForRemoteNotifications];
    
#else //適配iOS7
    
    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert];
#endif
    
#endif
}


注冊(cè)完畢進(jìn)行的回調(diào)

這一部分iOS10沒(méi)有發(fā)生變化,還是和之前一樣爬范,分為注冊(cè)成功以及注冊(cè)失敗兩個(gè)協(xié)議方法父腕,方法就在類(lèi)別RITLOriginPushAppDelegate+RITLNotificationManager:

// 注冊(cè)推送成功
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    //    NSLog(@"token = %@",deviceToken);
    // 將返回的token發(fā)送給服務(wù)器
}

// 注冊(cè)推送失敗
-(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
    NSLog(@"remoteNotice failture = %@",error.localizedDescription);
}


UserNotifications以前接收推送消息

在Demo中對(duì)AppDelegate新建了一個(gè)類(lèi)別"RITLOriginPushAppDelegate+RITLOldNotification"來(lái)完成對(duì)之前推送消息的接收以及處理:

//通過(guò)點(diǎn)擊遠(yuǎn)程推送進(jìn)入App執(zhí)行的方法,不管是應(yīng)用被殺死還是位于后臺(tái)
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    //收到的信息
    NSLog(@"%@",[[userInfo valueForKey:@"aps"] valueForKey:@"alert"]);
    
    [self performSelector:NSSelectorFromString(@"__showAlert:") withObject:@"通過(guò)點(diǎn)擊推送進(jìn)入App" afterDelay:0];
}
// 在前臺(tái)收到遠(yuǎn)程推送執(zhí)行的方法,如果實(shí)現(xiàn)了iOS10的協(xié)議方法,該方法不執(zhí)行,雖然沒(méi)有標(biāo)明廢棄
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
{
    //這個(gè)時(shí)候不會(huì)彈出推送青瀑,通常璧亮,在此處進(jìn)行一次本地推送,進(jìn)行通知
    //coding..
    
    //回調(diào)
    completionHandler(UIBackgroundFetchResultNewData);
}
// 收到本地推送
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
    //coding to handle local notification
    NSLog(@"本地推送啦!");
    
    //獲得文本與詳細(xì)內(nèi)容
    NSString * content = [NSString stringWithFormat:@"本地通知:title = %@, subTitle = %@",notification.alertBody,notification.alertTitle];
    
    [self performSelector:NSSelectorFromString(@"__showAlert:") withObject:content afterDelay:1];
}


UserNotifications下的接收推送消息

由于UserNotifications不再對(duì)遠(yuǎn)程推送以及本地推送進(jìn)行區(qū)分斥难,所以只需實(shí)現(xiàn)UNUserNotificationCenterDelegate協(xié)議下的兩個(gè)協(xié)議方法即可枝嘶,樓主是將該方法寫(xiě)在了AppDelegate的另一類(lèi)別中RITLOriginPushAppDelegate+RITLUserNotifications

#pragma mark - <UNUserNotificationCenterDelegate>

// 在前臺(tái)收到通知時(shí),將要彈出通知的時(shí)候觸發(fā)
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
{
    //比如如果App實(shí)在前臺(tái)哑诊,就不需要Badge了
    if([UIApplication sharedApplication].applicationState == UIApplicationStateActive)
    {
        completionHandler(UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert);
    }
    
    else//如果是后臺(tái)或者不活躍狀態(tài)群扶,需要badge
    {
        //需要三種彈出形式,如果存在Alert,那么App在前臺(tái)也是可以從上面彈出的
        completionHandler(UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert);
    }
}
// 已經(jīng)收到通知響應(yīng)的處理方法,不管是什么通知镀裤,當(dāng)通過(guò)點(diǎn)擊推送進(jìn)入或者回到App的時(shí)候觸發(fā)
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler
{
    //獲得響應(yīng)對(duì)象
    UNNotification * notification = response.notification;
    
    //獲得響應(yīng)時(shí)間
//    NSDate * responseDate = notification.date;
    
    //獲得響應(yīng)體
    UNNotificationRequest * request = notification.request;
    
    //獲得響應(yīng)體的標(biāo)識(shí)符
//    NSString * identifier = request.identifier;
    
    // 喚起通知的對(duì)象
//    UNNotificationTrigger * trigger = request.trigger;
    
    //獲得通知內(nèi)容
    UNNotificationContent * content = request.content;
    
    //比如獲得我想要的alert
    NSString * alertString = content.body;
    
    //可以彈出Alert提示一下
    [self performSelector:NSSelectorFromString(@"__showAlert:") withObject:alertString afterDelay:1];
    
    //比如這里可以進(jìn)行界面的跳轉(zhuǎn)等操作...
    
    //告知完成
    completionHandler();
}


遠(yuǎn)程推送階段預(yù)覽

以上配置完畢后穷当,使用PushMeBaby進(jìn)行推送預(yù)覽一下,以下是推送的內(nèi)容淹禾。
具體內(nèi)容的格式如下:具體格式摘自簡(jiǎn)書(shū)博主linatan博文-iOS10-UserNotifications

{
"aps":
    {
        "alert":
        {
            "title":"hello",
            "subtitle" : "Session 01",
            "body":"it is a beautiful day"
        },
        "category":"helloIdentifier",//比如我們想要使用自定義的UI,可以通過(guò)該值設(shè)置當(dāng)前request對(duì)象的identifier
        "badge":1,
        "mutable-content":1,//如果想要啟用Service的拓展,需要此數(shù)據(jù)茴扁,這個(gè)后面會(huì)有介紹
        "sound":"default",
        "image":"https://picjumbo.imgix.net/HNCK8461.jpg?q=40&w=200&sharp=30"
    }
}

在進(jìn)行測(cè)試的過(guò)程中铃岔,樓主使用的遠(yuǎn)程推送如下:

//Demo中進(jìn)行推送的內(nèi)容如下:
{
    "aps":
    {
        "alert" : "This is My message Yue.",
        "badge" : "1",
        "mutable-content" : 1
    }
}

//pushMeBody中推送的字符串
self.payload = @"{\"aps\":{\"alert\":\"This is My message Yue.\",\"badge\":\"1\",\"mutable-content\":1}}";

如下:

后臺(tái)接收的推送、響應(yīng)3D Touch的推送以及點(diǎn)擊推送進(jìn)入App后的效果

本地進(jìn)行推送

項(xiàng)目中推送的聲明類(lèi)

對(duì)于Demo中大部分的測(cè)試,樓主都是使用了本地推送毁习,所以樓主將推送功能封裝成了RITLPushMessageManager類(lèi)智嚷,聲明方法如下:

/// iOS10之前的本地推送
- (void)pushLocationNotificationbeforeiOS10;

//由于iOS10推送的可更新性,還新增了一個(gè)枚舉纺且,當(dāng)然盏道,本來(lái)是不需要的,但為了測(cè)試才有的這個(gè)枚舉類(lèi)型
typedef NS_ENUM(NSUInteger, RITLPushMessageType)
{
    RITLPushMessageTypeNew = 0,     /**<默認(rèn)為推送新的推送通知*/
    RITLPushMessageTypeUpdate = 1,  /**<更新當(dāng)前的推送通知*/
};


/// iOS10 之后的本地推送载碌,并根據(jù)類(lèi)型選擇是新的推送還是更新
- (void)pushLicationNotification:(nullable NSArray <UNNotificationAttachment *> *)attachments
                        pushType:(RITLPushMessageType)type NS_AVAILABLE_IOS(10_0);


UserNotifications以前本地推送

這個(gè)推送相信大家都是比較熟的了猜嘱,使用iOS10建議廢棄的UILocalNotification類(lèi),實(shí)現(xiàn)方法如下:

-(void)pushLocationNotificationbeforeiOS10
{
    UILocalNotification * localNotification = [[UILocalNotification alloc]init];
    
    //設(shè)置消息體
    localNotification.alertBody = @"RITL send a location notications (iOS9)";
    
#ifdef __IPHONE_8_2
    //設(shè)置詳細(xì)內(nèi)容,iOS8.2才存在
    localNotification.alertTitle = @"I am SubTitle";
#endif
    
    //設(shè)置彈出時(shí)的圖
    localNotification.alertLaunchImage = @"Stitch.png";
    
#ifdef __IPHONE_8_0
    //拓展id
    localNotification.category = RITLRequestIdentifier;
#endif
    
    //觸發(fā)聲音
    localNotification.soundName = UILocalNotificationDefaultSoundName;
    
    //觸發(fā)標(biāo)志
    localNotification.applicationIconBadgeNumber = 1;
    
    //1秒之后觸發(fā)
    localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:1];
    
    //注冊(cè)通知
    [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
}


UserNotifications下的本地推送

在新的框架下嫁艇,所有的推送都是使用了UNNotificationRequest類(lèi)朗伶,這里有一點(diǎn)需要注意:媒體的url可以存儲(chǔ)在UNNotificationAttachment類(lèi)之中,但是必須是本地路徑步咪,如果是網(wǎng)絡(luò)圖论皆,需要先從網(wǎng)絡(luò)下載下來(lái),存到本地猾漫,然后將本地的路徑存儲(chǔ)進(jìn)行處理点晴;如果UNNotificationAttachment對(duì)象初始化失敗,會(huì)拋出異常悯周,導(dǎo)致程序崩潰粒督。比如Demo中就是通過(guò)選擇相冊(cè)的照片進(jìn)行本地化,再賦值路徑來(lái)進(jìn)行的本地推送队橙,實(shí)現(xiàn)方法如下:

-(void)pushLicationNotification:(NSArray <UNNotificationAttachment *> *)attachments pushType:(RITLPushMessageType)type
{
    NSString * subTitle = (type == RITLPushMessageTypeNew ? @"I am a new SubTitle" : @"I am a update SubTitle");
    
    //初始化信息對(duì)象
    UNMutableNotificationContent * content = [[UNMutableNotificationContent alloc]init];
    
    //設(shè)置內(nèi)容
    content.body = @"RITL send a location notications";
    
    //設(shè)置詳細(xì)內(nèi)容
    content.subtitle = subTitle;
    
    //設(shè)置圖片名稱(chēng)
    content.launchImageName = @"Stitch.png";
    
    //設(shè)置拓展id
    content.categoryIdentifier = RITLRequestIdentifier;
    
    //設(shè)置推送聲音
    content.sound = [UNNotificationSound defaultSound];
    
    //設(shè)置通知
    content.badge = @1;
    
    //設(shè)置附帶信息
    content.userInfo = @{@"RITL":@"I am RITL.",@"network":@"https://www.baidu.com"};
    
    //媒體附帶信息
    content.attachments = attachments;
    
#pragma mark - 延時(shí)發(fā)送
    UNTimeIntervalNotificationTrigger * trigger = [UNNotificationTrigger defaultTimeIntervalNotificationTrigger];                                                        
                                                           
    //初始化通知請(qǐng)求
    UNNotificationRequest * request = [UNNotificationRequest requestWithIdentifier:RITLRequestIdentifier content:content trigger:trigger];
    
    //如果是更新坠陈,先移除
    if (type == RITLPushMessageTypeUpdate)
        [[UNUserNotificationCenter currentNotificationCenter]removeDeliveredNotificationsWithIdentifiers:@[RITLRequestIdentifier]];
    
    
    //獲得推送控制中心
    [[UNUserNotificationCenter currentNotificationCenter]addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
        
        if(error != nil)//出錯(cuò)
        {
            NSLog(@"error = %@",error.localizedDescription);
        }
    }];
}


UNNotificationTrigger 消息觸發(fā)類(lèi)

可能名字翻譯的不是那么正確,但個(gè)人的理解捐康,它就是負(fù)責(zé)觸發(fā)條件的仇矾,比如Demo中的類(lèi)別UNNotificationTrigger+RITLConveniceInitialize中,為Demo提供了三種默認(rèn)的觸發(fā)條件解总,如下:

///默認(rèn)的延時(shí)推送觸發(fā)時(shí)機(jī)
+(UNTimeIntervalNotificationTrigger *)defaultTimeIntervalNotificationTrigger
{
    return [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 repeats:false];
}
/// 默認(rèn)的日歷推送觸發(fā)時(shí)機(jī)
+(UNCalendarNotificationTrigger *)defaultCalendarNotificationTrigger
{
    
    NSDateComponents * dateCompents = [NSDateComponents new];
    dateCompents.hour = 7;//表示每天的7點(diǎn)進(jìn)行推送
    return [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:dateCompents repeats:true];
}
/// 默認(rèn)的地域推送觸發(fā)時(shí)機(jī)
+(UNLocationNotificationTrigger *)defaultLocationNotificationTrigger
{
    // 因?yàn)镃LRegion類(lèi)的初始化方法在iOS7提示廢棄贮匕,改用它的子類(lèi)CLCircularRegion
    //    CLRegion * region = [CLRegion alloc]initCircularRegionWithCenter:cooddinate2D(100,100) radius:200 identifier:locationTriggerIdentifier
    
    //經(jīng)緯度分別都是100
    CLLocationCoordinate2D coordinate2D = cooddinate2D(100,100);

    //初始化范圍類(lèi)
    CLCircularRegion * region = [[CLCircularRegion alloc]initWithCenter:coordinate2D radius:200 identifier:locationTriggerIdentifier];

    return [UNLocationNotificationTrigger triggerWithRegion:region repeats:false];

}


本地推送階段預(yù)覽

樓主選擇使用在本地相冊(cè)中選擇一個(gè)圖片來(lái)完成本地推送,大體思路如下:

//該過(guò)程通過(guò)類(lèi)別UNNotificationAttachment+RITLConceniceInitialize實(shí)現(xiàn)
+(NSArray<UNNotificationAttachment *> *)defaultNotificationAttachmentsWithImage:(UIImage *)image
{
    if (image == nil) return nil;
    
    NSMutableArray <UNNotificationAttachment *> * attachments = [NSMutableArray arrayWithCapacity:1];
    
    //將image存到本地
    [RITLPushFilesManager saveImage:image key:imageTransformPathKey];
    
    NSError * error;
    
    UNNotificationAttachment * attachment = [UNNotificationAttachment attachmentWithIdentifier:attachmentIdentifier URL:[RITLPushFilesManager imageUrlPathWithKey:imageTransformPathKey] options:nil error:&error];
    
    NSAssert(error == nil, error.localizedDescription);
    
    [attachments addObject:attachment];
    
    return [attachments mutableCopy];
}

預(yù)覽圖如下:

本地推送預(yù)覽


推送消息的策略

推送策略是什么呢花枫,可以理解為推送的一個(gè)"類(lèi)別"刻盐,當(dāng)我們使用3D Touch得以響應(yīng)推送信息之后,會(huì)出現(xiàn)類(lèi)似的一排按鈕劳翰,不過(guò)目前最大的限制為4個(gè)敦锌,并且最早出于iOS 8.0(那個(gè)時(shí)候沒(méi)有3D Touch感應(yīng),怎么能喚出拓展呢? 如果能告知的小伙伴佳簸,也請(qǐng)告知一下樓主乙墙,十分感謝)

同樣為了方便管理添加拓展,將添加拓展的方法封裝成RITLPushCategoryManager類(lèi)

UserNotifications之前推送策略

// 為Demo添加默認(rèn)的策略--before iOS10
+(UIUserNotificationCategory *)addDefaultCategorysBeforeiOS10
{
    //設(shè)置普通響應(yīng) -- 表示沒(méi)有便利初始化方法很坑
    UIMutableUserNotificationAction * foregroundAction = [UIMutableUserNotificationAction new];
    //設(shè)置屬性
    foregroundAction.identifier = foregroundActionIdentifier;
    foregroundAction.title = @"收到了";
    foregroundAction.activationMode = UIUserNotificationActivationModeForeground;
    
    
    //設(shè)置文本響應(yīng)
    UIMutableUserNotificationAction * destructiveTextAction = [UIMutableUserNotificationAction new];
    //設(shè)置屬性
    destructiveTextAction.identifier = destructiveTextActionIdentifier;
    destructiveTextAction.title = @"我想說(shuō)兩句";
    destructiveTextAction.activationMode = UIUserNotificationActivationModeForeground;
    destructiveTextAction.behavior = UIUserNotificationActionBehaviorTextInput;
    destructiveTextAction.authenticationRequired = false;
    destructiveTextAction.destructive = true;
    
    //初始化Category
    UIMutableUserNotificationCategory * category = [UIMutableUserNotificationCategory new];
    //設(shè)置屬性
    category.identifier = RITLRequestIdentifier;
    [category setActions:@[foregroundAction,destructiveTextAction] forContext:UIUserNotificationActionContextDefault];
    
    //返回
    return [category copy];
}

//并在外部使用注冊(cè)推送的時(shí)候添加即可,代碼有點(diǎn)長(zhǎng),Demo中有演示
/*
[[UIApplication sharedApplication] registerUserNotificationSettings:categories:中的第二個(gè)參數(shù)听想,以NSSet的形式傳入即可
*/

UserNotifications下的推送策略

UserNotifications框架為我們提供了很多的便利初始化方法腥刹,從代碼也可以看出它的簡(jiǎn)潔性,由于是單行汉买,所以會(huì)顯得比較長(zhǎng) (相信我衔峰,在這里換行格式也是很難看的,所以索性用了一行 0.0)

+(void)addDefaultCategorys
{
    // 設(shè)置響應(yīng)
    UNNotificationAction * foregroundAction = [UNNotificationAction actionWithIdentifier:foregroundActionIdentifier title:@"收到了" options:UNNotificationActionOptionForeground];
    
    // 設(shè)置文本響應(yīng)
    UNTextInputNotificationAction * destructiveTextAction = [UNTextInputNotificationAction actionWithIdentifier:destructiveTextActionIdentifier title:@"我想說(shuō)兩句" options:UNNotificationActionOptionDestructive|UNNotificationActionOptionForeground textInputButtonTitle:@"發(fā)送" textInputPlaceholder:@"想說(shuō)什么?"];
    
    // 初始化策略對(duì)象,這里的categoryWithIdentifier一定要與需要使用Category的UNNotificationRequest的identifier匹配(相同)才可觸發(fā)
    UNNotificationCategory * category = [UNNotificationCategory categoryWithIdentifier:RITLRequestIdentifier actions:@[foregroundAction,destructiveTextAction] intentIdentifiers:@[foregroundActionIdentifier,destructiveTextActionIdentifier] options:UNNotificationCategoryOptionCustomDismissAction];
    
    //直接通過(guò)UNUserNotificationCenter設(shè)置策略即可
    [[UNUserNotificationCenter currentNotificationCenter]setNotificationCategories:[NSSet setWithObjects:category, nil]];
    
}

響應(yīng)策略的方式

如何響應(yīng)我們添加的策略呢蛙粘,這里就只介紹UserNotifications下的響應(yīng)方法垫卤,找到負(fù)責(zé)響應(yīng)最新推送協(xié)議方法的類(lèi)別RITLOriginPushAppDelegate+RITLUserNotifications:

- (void)userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:方法體最前面進(jìn)行一下策略判定即可,如下:

if ([response.actionIdentifier isEqualToString:foregroundActionIdentifier])
{
    //可以彈出Alert提示一下
    [self performSelector:NSSelectorFromString(@"__showAlert:") withObject:@"我是第一個(gè)策略動(dòng)作,收到了" afterDelay:0];
    
    completionHandler();return;
}

else if([response.actionIdentifier isEqualToString:destructiveTextActionIdentifier])
{
    [self performSelector:NSSelectorFromString(@"__showAlert:") withObject:[NSString stringWithFormat:@"我是第二個(gè)文本動(dòng)作组题,我輸入的文字是:%@",((UNTextInputNotificationResponse *)response).userText] afterDelay:0];
    
    completionHandler();return;
}

推送策略階段預(yù)覽

通過(guò)Touch喚起推送策略葫男、響應(yīng)text策略以及響應(yīng)策略的預(yù)覽圖


推送拓展插件-NotificationContentExtension

創(chuàng)建方式

與Widget插件化開(kāi)發(fā)的創(chuàng)建步驟是一樣的呢,F(xiàn)ile->New->Target->NotificationContentExtension

</img>

創(chuàng)建成功之后崔列,默認(rèn)的控制器名稱(chēng)為NotificationViewController梢褐,Demo中僅僅改了一下名字,文件夾分布如下:

</img>

如果牽扯到UI的繪制的方法赵讯,數(shù)據(jù)與代碼的共享的方法盈咳,作為插件化開(kāi)發(fā)其實(shí)是一致的,可回顧一下博主之前的博文iOS開(kāi)發(fā)------Widget(Today Extension)插件化開(kāi)發(fā)感謝边翼。

這里需要注意一下鱼响,如果我們創(chuàng)建了拓展,但是本地推送的UI如果還是默認(rèn)的UI形式组底,這個(gè)時(shí)候就需要看一下Info.plist文件下 NSExtension->NSExtensionAttributes->UNNotificationExtensionCategory (默認(rèn)是一個(gè)String類(lèi)型)丈积,里面需要與UNNotificationRequest對(duì)象的identifier相匹配才可觸發(fā);如果支持多個(gè)通知债鸡,可以將UNNotificationExtensionCategory更改為數(shù)組類(lèi)型江滨。下面是一個(gè)栗子0.0

下面是Demo中進(jìn)行本地推送中初始化UNNotificationRequest對(duì)象的具體代碼形式:

//作為一個(gè)全局的固定字符串
NSString * const RITLRequestIdentifier = @"com.yue.originPush.myNotificationCategory";

//初始化通知請(qǐng)求
UNNotificationRequest * request = [UNNotificationRequest requestWithIdentifier:RITLRequestIdentifier content:content trigger:trigger];

同時(shí)為了保證能夠觸發(fā)自定義的推送UI,Demo中的plist文件設(shè)置如下:

</img>

在Demo中厌均,樓主依舊使用了storyboard進(jìn)行UI布局唬滑,也只因?yàn)槭茄芯靠蚣埽圆季质欠浅5暮?jiǎn)單棺弊,如下:

</img>

UNNotificationContentExtension協(xié)議

這個(gè)協(xié)議也是比較簡(jiǎn)單的晶密,只有兩個(gè)協(xié)議方法,有一個(gè)必須實(shí)現(xiàn)的方法模她,也有一個(gè)可選的稻艰。

// 收到推送消息后進(jìn)行的回調(diào),必須實(shí)現(xiàn)的方法
- (void)didReceiveNotification:(UNNotification *)notification
{
    //獲得內(nèi)容對(duì)象
    UNNotificationContent * content = notification.request.content;
    
    //獲得需要展示的文本
    NSString * customTitle = content.body;
    
    //需要展示的圖片
    UIImage * image = [UIImage imageNamed:content.launchImageName];
    
    //設(shè)置
    self.customlabel.text = customTitle;
    
    //這是拓展里自定義的圖片
    self.customimageView.image = image;
    
    //這是通知里帶的照片
    self.attachmentImageView.image = nil;//直接使用attachment對(duì)象的路徑進(jìn)行加載即可
    
    //如果是遠(yuǎn)程推送的網(wǎng)絡(luò)圖沒(méi)有加載怎么辦侈净,這里不用擔(dān)心
    //下面的NotificationServiceExtension就是解決這個(gè)問(wèn)題的
}

下面這個(gè)協(xié)議方法是可選的连锯。
它的作用就是归苍,當(dāng)我們使用自定義的UI進(jìn)行通知顯示的時(shí)候,通過(guò)點(diǎn)擊或者響應(yīng)策略Action的時(shí)候运怖,會(huì)優(yōu)先執(zhí)行該協(xié)議方法,并通過(guò)它的回調(diào)來(lái)決定下一步的操作夏伊。

//回調(diào)類(lèi)型的個(gè)人理解如下:
typedef NS_ENUM(NSUInteger, UNNotificationContentExtensionResponseOption)
{
    UNNotificationContentExtensionResponseOptionDoNotDismiss, //默認(rèn)表示不消失
    UNNotificationContentExtensionResponseOptionDismiss,//消失
    UNNotificationContentExtensionResponseOptionDismissAndForwardAction,//消失并讓App對(duì)它進(jìn)行處理摇展,這個(gè)時(shí)候才會(huì)走原應(yīng)用的回調(diào)
};

// 展開(kāi)后的推送消息得到點(diǎn)擊響應(yīng)
- (void)didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(UNNotificationContentExtensionResponseOption option))completion
{
    //進(jìn)行回調(diào),這里是使其消失并回到主App進(jìn)行處理溺忧,處理完畢之后會(huì)走UNUserNotificationCenter的協(xié)議方法
    completion(UNNotificationContentExtensionResponseOptionDismissAndForwardAction);
}


NotificationContentExtension階段預(yù)覽

使用自定義的通知UI咏连、使用策略輸入文本以及最后響應(yīng)文本的操作


推送拓展插件-NotificationServiceExtension

如果眼睛比較厲害的小伙伴估計(jì)上面的截圖也看到了,在NotificationContentExtension旁邊鲁森,如下圖:

</img>

這個(gè)插件的作用是什么呢祟滴,當(dāng)然這個(gè)插件主要是用于遠(yuǎn)程推送的數(shù)據(jù)處理,它能夠給我們最長(zhǎng)30秒的時(shí)間讓我們對(duì)遠(yuǎn)程推送的內(nèi)容進(jìn)行修改歌溉,或者對(duì)推送內(nèi)容中的圖片垄懂、視頻、音頻鏈接進(jìn)行下載的過(guò)程痛垛。

當(dāng)然大部分的代碼系統(tǒng)已經(jīng)幫我們寫(xiě)好了草慧,這里之貼上Demo中的一個(gè)小實(shí)現(xiàn),作用就是在收到遠(yuǎn)程推送的時(shí)候匙头,修改推送的內(nèi)容漫谷,如下:

//大約會(huì)給30秒的時(shí)間限制
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    
    //獲取request以及block對(duì)象
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    
    
    //對(duì)內(nèi)容可以進(jìn)行修改
    self.bestAttemptContent.body = @"我是在Service里面修改后的title";
    
    //如果如果含有圖片,視頻音頻的url蹂析,可以利用這里進(jìn)行下載coding...
    
    //然后初始化UNNotificationAttachment對(duì)象舔示,賦值數(shù)組即可
    
    //回調(diào)處理完畢的content內(nèi)容
    self.contentHandler(self.bestAttemptContent);
}

下面的協(xié)議方法雖然能大體翻譯出來(lái),但卻不知道到底有什么用电抚,如果有知道的小伙伴惕稻,也請(qǐng)告知一下,十分感謝

//提供最后一個(gè)機(jī)會(huì)喻频,當(dāng)該拓展將被系統(tǒng)殺死的時(shí)候執(zhí)行的方法
- (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.
    
    //就比如上面英文介紹所說(shuō)的:可以用這個(gè)機(jī)會(huì)來(lái)表達(dá)一下你最想在content中表達(dá)的缩宜,不然的話最初推送的payload將被用到
    self.contentHandler(self.bestAttemptContent);
}

NotificationServiceExtension階段預(yù)覽

使用pushMeBody進(jìn)行一次遠(yuǎn)程推送,Touch響應(yīng)以及點(diǎn)擊推送該內(nèi)容結(jié)果

更多的請(qǐng)?jiān)贕itHub

出了文中提到的博文甥温,最后還要感謝下面博文對(duì)該博文的幫助:
iOS 10 新特性之通知推送--干貨一篇
活久見(jiàn)的重構(gòu) - iOS 10 UserNotifications 框架解析

最后祝大家工作開(kāi)心锻煌,明天就要為祖國(guó)慶生了,也祝大家節(jié)日快樂(lè)姻蚓。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末宋梧,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子狰挡,更是在濱河造成了極大的恐慌捂龄,老刑警劉巖释涛,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異倦沧,居然都是意外死亡唇撬,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)展融,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)窖认,“玉大人,你說(shuō)我怎么就攤上這事告希∑私” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵燕偶,是天一觀的道長(zhǎng)喝噪。 經(jīng)常有香客問(wèn)我,道長(zhǎng)指么,這世上最難降的妖魔是什么酝惧? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮涧尿,結(jié)果婚禮上系奉,老公的妹妹穿的比我還像新娘。我一直安慰自己姑廉,他們只是感情好缺亮,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著桥言,像睡著了一般萌踱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上号阿,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天并鸵,我揣著相機(jī)與錄音,去河邊找鬼扔涧。 笑死园担,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的枯夜。 我是一名探鬼主播弯汰,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼湖雹!你這毒婦竟也來(lái)了咏闪?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤摔吏,失蹤者是張志新(化名)和其女友劉穎鸽嫂,沒(méi)想到半個(gè)月后纵装,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡据某,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年橡娄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哗脖。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瀑踢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出才避,到底是詐尸還是另有隱情,我是刑警寧澤氨距,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布霎桅,位于F島的核電站讹俊,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜上沐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望叶组。 院中可真熱鬧阶祭,春花似錦、人聲如沸勒奇。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)赊颠。三九已至格二,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間竣蹦,已是汗流浹背顶猜。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留痘括,地道東北人长窄。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像纲菌,于是被迫代替她去往敵國(guó)和親挠日。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容