IOS基礎(chǔ)使用:推送服務(wù)

原創(chuàng):知識點總結(jié)性文章
創(chuàng)作不易夯到,請珍惜峦萎,之后會持續(xù)更新,不斷完善
個人比較喜歡做筆記和寫總結(jié)蝶桶,畢竟好記性不如爛筆頭哈哈慨绳,這些文章記錄了我的IOS成長歷程,希望能與大家一起進步
溫馨提示:由于簡書不支持目錄跳轉(zhuǎn)真竖,大家可通過command + F 輸入目錄標題后迅速尋找到你所需要的內(nèi)容

目錄

  • 一脐雪、簡介
  • 一、Local Notifications(本地推送)
    • 1恢共、本地推送流程
    • 2战秋、通知的觸發(fā)條件
    • 3、通知的內(nèi)容
    • 4讨韭、對推送進行查脂信、改、刪
    • 5拐袜、UNUserNotificationCenterDelegate中的回調(diào)方法
  • 二吉嚣、Remote Notifications(遠程推送)
    • 1梢薪、遠程推送流程
    • 2蹬铺、準備工作
    • 3、AppDelegate中的方法
    • 4秉撇、App Server
  • 三甜攀、iOS 通知擴展
    • 1、準備工作
    • 2琐馆、通知服務(wù)擴展(UNNotificationServiceExtension)
    • 3规阀、通知內(nèi)容擴展(UNNotificationContentExtension)
  • 四、極光推送
    • 1瘦麸、簡介
    • 2谁撼、項目中極光SDK的配置
    • 3、JPUSHRegisterDelegate
    • 4滋饲、封裝的便利方法
  • Demo
  • 參考文獻

一厉碟、簡介

日常生活中會有很多種情形需要通知喊巍,比如:新聞提醒、定時吃藥箍鼓、定期體檢崭参、到達某個地方提醒用戶等等,這些功能在 UserNotifications 中都提供了相應的接口款咖。

iOS推送分為Local Notifications(本地推送) 和 Remote Notifications(遠程推送)何暮。本地推送是App本地創(chuàng)建通知,加入到系統(tǒng)的Schedule里铐殃,如果觸發(fā)器條件達成時會推送相應的消息內(nèi)容海洼。

在遠程推送中,Provider是指某個APP的Push服務(wù)器富腊。APNSApple Push Notification ServiceApple Push服務(wù)器)的縮寫贰军,是蘋果的服務(wù)器。APNS Pusher應用程序把要發(fā)送的消息蟹肘、目標iPhone的標識(deviceToken)打包词疼,發(fā)給APNSAPNS在自身的已注冊Push服務(wù)的iPhone列表中帘腹,查找有相應標識的iPhone贰盗,并把消息發(fā)到iPhoneiPhone把發(fā)來的消息傳遞給相應的應用程序阳欲, 并且按照設(shè)定彈出Push通知舵盈。

如果你的App有遠端推送的話,那你需要用開發(fā)者賬號新建一個push證書球化。再在Capabilities中打開Push Notifications 開關(guān)秽晚,打開后會自動在項目里生成entitlements文件。


二筒愚、Local Notifications(本地推送)

1赴蝇、本地推送流程

本地推送通知是由本地應用觸發(fā)的,是基于時間的通知形式巢掺,一般用于鬧鐘定時句伶、待辦事項等提醒功能。

a陆淀、發(fā)送本地推送通知的步驟
  1. 創(chuàng)建一個觸發(fā)器(trigger
  2. 創(chuàng)建推送的內(nèi)容(UNMutableNotificationContent
  3. 創(chuàng)建推送請求(UNNotificationRequest
  4. 推送請求添加到推送管理中心(UNUserNotificationCenter)中
b考余、步驟的代碼實現(xiàn)
- (void)simpleLocalNotificationDescribe
{
    [self buildNotificationDescribe:@"最簡單的本地通知(創(chuàng)建5秒后觸發(fā),建議回到桌面察看效果)"];
}
- (void)simpleLocalPushService
{
    // 1.定時推送
    UNTimeIntervalNotificationTrigger *trigger = [self getTimeTrigger];
    
    // 2.推送的內(nèi)容
    UNMutableNotificationContent *content = [self getSimpleContent];
    
    // 3.創(chuàng)建通知請求 UNNotificationRequest 將觸發(fā)條件和通知內(nèi)容添加到請求中
    NSString *requestIdentifer = @"Simple Local Notification";
    UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:requestIdentifer content:content trigger:trigger];
    
    // 4.將通知請求 add 到 UNUserNotificationCenter
    [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
        
        if (!error)
        {
            NSLog(@"簡單的本地通知已添加成功!");
            
            // 此處省略一萬行需求.......
        }
    }];
}

2轧苫、通知的觸發(fā)條件

蘋果把本地通知跟遠程通知合二為一楚堤。區(qū)分本地通知跟遠程通知的類是UNNotificationTrigger,通過它,我們可以得到一些通知的觸發(fā)條件身冬。

UNPushNotificationTrigger// 遠程推送的通知類型
UNTimeIntervalNotificationTrigger// (本地通知) 一定時間之后鳄袍,重復或者不重復推送通知。我們可以設(shè)置timeInterval(時間間隔)和repeats(是否重復)
UNCalendarNotificationTrigger//(本地通知) 一定日期之后吏恭,重復或者不重復推送通知 例如拗小,你每天8點推送一個通知,只要dateComponents為8樱哼,如果你想每天8點都推送這個通知哀九,只要repeats為YES就可以了
UNLocationNotificationTrigger// (本地通知)地理位置的一種通知,當用戶進入或離開一個地理區(qū)域來通知
定時推送
// 觸發(fā)推送的時機搅幅。timeInterval:單位為秒(s)  repeats:是否循環(huán)提醒
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:5 repeats:NO];
定期推送
// components代表日期阅束,這里指在每周一的14點3分提醒
NSDateComponents *components = [[NSDateComponents alloc] init];
components.weekday = 2;
components.hour = 14;
components.minute = 3;
UNCalendarNotificationTrigger *calendarTrigger = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:components repeats:YES];
定點推送
// 使用CLRegion的子類CLCircularRegion創(chuàng)建位置信息
CLLocationCoordinate2D center1 = CLLocationCoordinate2DMake(39.788857, 116.5559392);
CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:center1 radius:500 identifier:@"海峽國際社區(qū)"];

// 進入地區(qū)、從地區(qū)出來或者兩者都要的時候進行通知
region.notifyOnEntry = YES;
region.notifyOnExit = YES;

// region 位置信息 repeats 是否重復
UNLocationNotificationTrigger *locationTrigger = [UNLocationNotificationTrigger triggerWithRegion:region repeats:YES];

3茄唐、通知的內(nèi)容

a息裸、文字、圖像沪编、聲音
簡單的本地通知

下拉放大圖像
- (UNMutableNotificationContent *)getSimpleContent
{
    // 推送的文本內(nèi)容
    // UNNotificationContent的屬性readOnly呼盆,而UNMutableNotificationContent的屬性可以更改
    UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
    
    // 限制在一行,多出部分省略號
    content.title = @"時間提醒";
    content.subtitle = [NSString stringWithFormat:@"《請回答1988》第二季放映的時間提醒"];
    
    // body中printf風格的轉(zhuǎn)義字符蚁廓,比如說要包含%访圃,需要寫成%% 才會顯示,\同樣
    content.body = @"口若懸河相嵌、妙語迭出的精彩表演腿时,從作者津津樂道的口吻可以看出,王爾德無疑是在顧影自憐饭宾,因為他自己正是這樣的作秀高手批糟,而他在社交圈中越練越“酷”的口才,在他的社會喜劇中得到了淋漓盡致的發(fā)揮看铆,令觀眾如醉如癡!";
    
    content.badge = @5;
    // UNNotificationSound *customSound = [UNNotificationSound soundNamed:@""];// 自定義聲音
    content.sound = [UNNotificationSound defaultSound];
    content.userInfo = @{@"useName":@"XieJiapei",@"age":@"22"};
    
    // 輔助圖像徽鼎,下拉通知會放大圖像
    NSString *imageFilePath = [[NSBundle mainBundle] pathForResource:@"luckcoffee" ofType:@"JPG"];
    if (imageFilePath)
    {
        NSError* error = nil;
        UNNotificationAttachment *imageAttachment = [UNNotificationAttachment attachmentWithIdentifier:@"imageAttachment" URL:[NSURL fileURLWithPath:imageFilePath] options:nil error:&error];
        if (imageAttachment)
        {
            // 這里設(shè)置的是Array,但是只會取lastObject
            content.attachments = @[imageAttachment];
        }
    }
    
    return content;
}

b性湿、視頻
下拉播放視頻
- (UNMutableNotificationContent *)getVideoContent
{
    UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
    content.title = @"WWDC";
    content.subtitle = @"蘋果開發(fā)者技術(shù)大會";
    content.body = @"下拉通知可直接播放";
    
    // 導入視頻的時候纬傲,默認不是添加到bundle中满败,必須手動勾選Add to targets
    NSString *videoFilePath = [[NSBundle mainBundle] pathForResource:@"notification_video" ofType:@"m4v"];
    if (videoFilePath)
    {
        UNNotificationAttachment* videoAttachment = [UNNotificationAttachment attachmentWithIdentifier:@"videoAttachment" URL:[NSURL fileURLWithPath:videoFilePath] options:nil error:nil];
        
        if (videoAttachment)
        {
            // 這里設(shè)置的是Array肤频,但是只會取lastObject
            content.attachments = @[videoAttachment];
        }
    }
    
    return content;
}

c、操作
用戶操作
輸入文本
- (UNMutableNotificationContent *)getActionContent
{
    UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
    content.title = @"Apple";
    content.subtitle = @"Apple Developer";
    content.body = @"下拉放大圖片";
    
    NSMutableArray *actionMutableArray = [[NSMutableArray alloc] initWithCapacity:3];
    
    // UNNotificationActionOptionAuthenticationRequired 需要解鎖顯示算墨,點擊不會進app
    UNNotificationAction *unUnlockAction = [UNNotificationAction actionWithIdentifier:@"IdentifierNeedUnUnlock" title:@"需要解鎖" options: UNNotificationActionOptionAuthenticationRequired];
    
    // UNNotificationActionOptionDestructive 紅色文字宵荒,點擊不會進app
    UNNotificationAction *destructiveAction = [UNNotificationAction actionWithIdentifier:@"IdentifierRed" title:@"紅色顯示" options: UNNotificationActionOptionDestructive];
    
    // UNNotificationActionOptionForeground 黑色文字,點擊會進app
    // UNTextInputNotificationAction是輸入框Action,buttonTitle是輸入框右邊的按鈕標題报咳,placeholder是輸入框占位符
    UNTextInputNotificationAction *inputTextAction = [UNTextInputNotificationAction actionWithIdentifier:@"IdentifierInputText" title:@"輸入文本" options:UNNotificationActionOptionForeground textInputButtonTitle:@"發(fā)送" textInputPlaceholder:@"說說今天發(fā)生了啥......"];
    
    [actionMutableArray addObjectsFromArray:@[unUnlockAction, destructiveAction, inputTextAction]];
    
    if (actionMutableArray.count > 1)
    {
        /**categoryWithIdentifier方法
         * identifier:是這個category的唯一標識侠讯,用來區(qū)分多個category,這個id不管是Local Notification暑刃,還是remote Notification厢漩,一定要有并且要保持一致
         * actions:創(chuàng)建action的操作數(shù)組
         * intentIdentifiers:意圖標識符 可在 <Intents/INIntentIdentifiers.h> 中查看,主要是針對電話岩臣、carplay 等開放的 API
         * options:通知選項 枚舉類型 也是為了支持 carplay
         */
        UNNotificationCategory *categoryNotification = [UNNotificationCategory categoryWithIdentifier:@"categoryOperationAction" actions:actionMutableArray intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction];
        
        // 將創(chuàng)建的 category 添加到通知中心
        [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:[NSSet setWithObject:categoryNotification]];
        
        // category的唯一標識溜嗜,Local Notification保持一致
        content.categoryIdentifier = @"categoryOperationAction";
    }
    
    return content;
}

4、對推送進行查架谎、改炸宵、刪

a、更新通知

Local Notification重新創(chuàng)建具有相同requestIdentifierlocal Notification request添加到推送center就可以了谷扣。Remote Notification 更新需要通過新的字段apps-collapse-id來作為唯一標示土全,APNS pusher暫不支持這個字段,不過github上有這樣的工具:Knuff会涎。

b裹匙、查找和刪除通知
//獲取未送達的所有消息列表
- (void)getPendingNotificationRequestsWithCompletionHandler:(void(^)(NSArray<UNNotificationRequest *> *requests))completionHandler;
//刪除所有未送達的特定id的消息
- (void)removePendingNotificationRequestsWithIdentifiers:(NSArray<NSString *> *)identifiers;
//刪除所有未送達的消息
- (void)removeAllPendingNotificationRequests;

//獲取已送達的所有消息列表
- (void)getDeliveredNotificationsWithCompletionHandler:(void(^)(NSArray<UNNotification *> *notifications))completionHandler;
//刪除所有已送達的特定id的消息
- (void)removeDeliveredNotificationsWithIdentifiers:(NSArray<NSString *> *)identifiers;
//刪除所有已送達的消息
- (void)removeAllDeliveredNotifications;

調(diào)用方式如下:

- (void)removeNotificaiton
{
    NSString *requestIdentifier = @"XieJiaPei";
    UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
    
    // 刪除設(shè)備已收到特定id的所有消息推送
    [center removeDeliveredNotificationsWithIdentifiers:@[requestIdentifier]];
    
    // 刪除設(shè)備已收到的所有消息推送
    [center removeAllDeliveredNotifications];
    
    // 獲取設(shè)備已收到的消息推送
    [center getDeliveredNotificationsWithCompletionHandler:^(NSArray<UNNotification *> * _Nonnull notifications) {
        NSLog(@"獲取設(shè)備已收到的消息推送");
    }];
}

5、UNUserNotificationCenterDelegate中的回調(diào)方法

a末秃、即將展示推送的通知時觸發(fā)(app在前臺獲取到通知)
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
{
    // 收到推送的請求
    UNNotificationRequest *request = notification.request;
    
    // 收到的內(nèi)容
    UNNotificationContent *content = request.content;
    
    // 收到用戶的基本信息
    NSDictionary *userInfo = content.userInfo;
    
    // 收到消息的角標
    NSNumber *badge = content.badge;
    
    // 收到消息的body
    NSString *body = content.body;
    
    // 收到消息的聲音
    UNNotificationSound *sound = content.sound;
    
    // 推送消息的副標題
    NSString *subtitle = content.subtitle;
    
    // 推送消息的標題
    NSString *title = content.title;
    
    if ([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]])// 遠程推送的通知
    {
        NSLog(@"遠程推送的通知幻件,收到用戶的基本信息為: %@\n",userInfo);
    }
    else // 本地通知
    {
        NSLog(@"本地推送的通知:{\nbody:%@,\ntitle:%@,\nsubtitle:%@,\nbadge:%@蛔溃,\nsound:%@绰沥,\nuserInfo:%@}",body,title,subtitle,badge,sound,userInfo);
    }
    
    // 不管前臺后臺狀態(tài)下。推送消息的橫幅都可以展示出來
    // 需要執(zhí)行這個方法贺待,選擇是否提醒用戶徽曲,有Badge、Sound麸塞、Banner三種類型可以設(shè)置
    completionHandler(UNNotificationPresentationOptionBadge|
                      UNNotificationPresentationOptionSound|
                      UNNotificationPresentationOptionBanner);
}
本地通知的輸出結(jié)果
2020-10-26 16:08:53.383283+0800 PushServiceDemo[71205:2358029] 簡單的本地通知已添加成功!
2020-10-26 16:08:58.393934+0800 PushServiceDemo[71205:2357722] 本地推送的通知:{
body:口若懸河秃臣、妙語迭出的精彩表演,從作者津津樂道的口吻可以看出哪工,王爾德無疑是在顧影自憐奥此,因為他自己正是這樣的作秀高手,而他在社交圈中越練越“酷”的口才雁比,在他的社會喜劇中得到了淋漓盡致的發(fā)揮稚虎,令觀眾如醉如癡!,
title:時間提醒,
subtitle:《請回答1988》第二季放映的時間提醒,
badge:5偎捎,
sound:<UNNotificationSound: 0x6000038f1a40>蠢终,
userInfo:{
    age = 22;
    useName = XieJiapei;
}}

b序攘、用戶點擊推送消息時觸發(fā) (點擊通知進入app時觸發(fā))
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
{
    //  UNNotificationResponse 是普通按鈕的Response
    NSString *actionIdentifierString = response.actionIdentifier;
    if (actionIdentifierString)
    {
        // 點擊后展示文本2秒后隱藏
        UIView *windowView = [[[UIApplication sharedApplication] keyWindow] rootViewController].view;
        MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:windowView animated:YES];
        hud.label.text = [NSString stringWithFormat:@"用戶點擊了消息,id為:%@",actionIdentifierString];
        hud.mode = MBProgressHUDModeText;
        [hud hideAnimated:YES afterDelay:2];
        
        if ([actionIdentifierString isEqualToString:@"IdentifierNeedUnUnlock"])
        {
            NSLog(@"需要解鎖");
        }
        else if ([actionIdentifierString isEqualToString:@"IdentifierRed"])
        {
            NSLog(@"紅色顯示寻拂,并且設(shè)置APP的Badge通知數(shù)字為0");
            
            // 設(shè)置APP的Badge通知數(shù)字
            [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
        }
    }
    
    //  UNTextInputNotificationResponse 是帶文本輸入框按鈕的Response
    if ([response isKindOfClass:[UNTextInputNotificationResponse class]])
    {
        NSString *userSayString = [(UNTextInputNotificationResponse *)response userText];
        if (userSayString)
        {
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                
                UIView *windowView = [[[UIApplication sharedApplication] keyWindow] rootViewController].view;
                MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:windowView animated:YES];
                hud.label.text = userSayString;
                hud.mode = MBProgressHUDModeText;
                [hud hideAnimated:YES afterDelay:2];
            });
        }
    }

    // 系統(tǒng)要求執(zhí)行這個方法
    completionHandler();
}

點擊紅色按鈕的輸出結(jié)果為:

2020-10-27 16:20:02.797425+0800 PushServiceDemo[80030:2808431] 紅色顯示程奠,并且設(shè)置APP的Badge通知數(shù)字為0

二、Remote Notifications(遠程推送)

1祭钉、遠程推送流程

遠程推送通知是通過蘋果的APNsApple Push Notification service)發(fā)送到app瞄沙,而APNs必須先知道用戶設(shè)備的令牌(device token)。在啟動時慌核,app與APNs通信并接收device token帕识,然后將其轉(zhuǎn)發(fā)到App ServerApp Server將該令牌和要發(fā)送的通知消息發(fā)送至APNs遂铡。

蘋果官方提供的遠程推送通知的傳遞示意圖如下:


遠程推送通知的傳遞過程

各關(guān)鍵組件之間的交互細節(jié):


各關(guān)鍵組件之間的交互細節(jié)

2肮疗、準備工作

  1. 根據(jù)工程的Bundle Identifier,在蘋果開發(fā)者平臺中創(chuàng)建同名App ID扒接,并勾選Push Notifications服務(wù)
  2. 在工程的Capabilities中啟動Push Notifications
  3. 遠程推送必須使用真機調(diào)試伪货,因為模擬器無法獲取得到device token

想要為蘋果開發(fā)軟件并上架蘋果商店,就需要參加蘋果開發(fā)者計劃钾怔,需要交納年費(99和699兩檔)碱呼,只要求自己編寫的代碼在蘋果真機上跑起來,只需要注冊成蘋果開發(fā)者賬戶就可以了宗侦,不需要交錢愚臀,但是如果想調(diào)試推送惨奕、iCloud蚤氏、IAP之類的功能靶衍,或者上架蘋果商店鸟辅,就需要交錢了。買不起~~~~無法調(diào)試呀??以后有開發(fā)者賬戶了再看看吧~生成APNs后端推送證書


3北戏、AppDelegate中的方法

a尿赚、注冊遠程通知
- (void)registerPushService
{
    // 遠程通知授權(quán)
    UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
    [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionBadge | UNAuthorizationOptionSound) completionHandler:^(BOOL granted, NSError * _Nullable error) {
        if (granted)
        {
            NSLog(@"遠程通知中心成功打開");
            
            // 必須在主線程注冊通知
            dispatch_async(dispatch_get_main_queue(), ^{
                // 注冊遠程通知
                [[UIApplication sharedApplication] registerForRemoteNotifications];
                
                // 注冊delegate
                [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self];
            });
        }
        else
        {
            NSLog(@"遠程通知中心打開失敗");
        }
    }];
    
    // 獲取注冊之后的權(quán)限設(shè)置
    // 注意UNNotificationSettings是只讀對象督惰,不能直接修改
    [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
        NSLog(@"通知的配置信息:\n%@",settings);
    }];
}

輸出結(jié)果為:

2020-10-26 16:00:00.694842+0800 PushServiceDemo[71205:2357947] 通知的配置信息:
<UNNotificationSettings: 0x6000038f0d20; authorizationStatus: Authorized, notificationCenterSetting: Enabled, soundSetting: Enabled, badgeSetting: Enabled, lockScreenSetting: Enabled, carPlaySetting: NotSupported, announcementSetting: NotSupported, criticalAlertSetting: NotSupported, alertSetting: Enabled, alertStyle: Banner, groupingSetting: Default providesAppNotificationSettings: No>
2020-10-26 16:00:00.696243+0800 PushServiceDemo[71205:2357946] 遠程通知中心成功打開

b察皇、App獲取device token

app將獲取到的device token發(fā)送給App Server茴厉。只有蘋果公司知道device token的生成算法,保證唯一什荣。device token在app卸載后重裝等情況時會變化矾缓,因此為確保device token變化后app仍然能夠正常接收服務(wù)器端發(fā)送的通知,建議每次啟動應用都將獲取到的device token傳給App Server稻爬。

// 遠端推送需要獲取設(shè)備的Device Token
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    // 解析NSData獲取字符串
    NSString *deviceString = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];// 移除<>
    deviceString = [deviceString stringByReplacingOccurrencesOfString:@" " withString:@""];// 移除空格
    
    NSLog(@"設(shè)備的Device Token為:%@",deviceString);
}

// 獲取設(shè)備的DeviceToken失敗
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
    NSLog(@"獲取設(shè)備的DeviceToken失斒任拧:%@\n",error.description);
}

4、App Server

Pusher的下載地址

NWPusher可以當做framework使用因篇,也可以直接下載APP使用泞辐。

Pusher的使用步驟

使用Pusher工具模擬App Server將指定的device token和消息內(nèi)容發(fā)送給APNs笔横。

Pusher
  1. 選擇p12格式的推送證書
  2. 設(shè)置是否為測試環(huán)境竞滓。默認勾選為測試環(huán)境咐吼,由于推送證書分為測試證書和生產(chǎn)證書,并且蘋果的APNs也分為測試和生產(chǎn)兩套環(huán)境商佑,因此Pusher需要手動勾選推送環(huán)境
  3. 輸入device token
  4. 輸入符合蘋果要求格式的aps字符串
  5. 執(zhí)行推送
內(nèi)容格式

Payload中輸入的內(nèi)容就是我們需要傳送的數(shù)據(jù)了锯茄,這個數(shù)據(jù)傳輸以JSON的格式存儲。

{"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"}
  • aps:我們需要傳送的內(nèi)容
  • alert:彈出框需要展示的內(nèi)容
  • badge:展示的信息個數(shù)
  • sound:表示當有Push消息的時候茶没,是否需要聲音提示
稍縱即逝你就收到了遠端消息了

可惜沒開發(fā)者賬戶無法測試肌幽,連device token都拿不到。

2020-10-27 15:06:34.967968+0800 PushServiceDemo[1272:220269] 獲取設(shè)備的DeviceToken失斪グ搿:Error Domain=NSCocoaErrorDomain Code=3000 "未找到應用程序的“aps-environment”的授權(quán)字符串" UserInfo={NSLocalizedDescription=未找到應用程序的“aps-environment”的授權(quán)字符串}

三喂急、iOS 通知擴展

1、準備工作

a笛求、添加新的Target--> Notification Service/Content
添加新的Target

b廊移、擴展工程的目錄

系統(tǒng)會自動創(chuàng)建一個 UNNotificationServiceExtension 的子類 NotificationService。通過完善這個子類探入,來實現(xiàn)你的需求狡孔。NotificationViewController直接繼承于ViewController,因此可以在這個類中重寫相關(guān)方法蜂嗽,來修改界面的相關(guān)布局及樣式苗膝。

工程目錄

c、擴展提供的方法
Notification Service

讓你可以在后臺處理接收到的推送植旧,傳遞最終的內(nèi)容給 contentHandler辱揭。系統(tǒng)接到通知后,有最多30秒在這里重寫通知內(nèi)容(如下載附件并更新通知)病附。

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler
{
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    
    self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
    
    self.contentHandler(self.bestAttemptContent);
}

在你獲得的一小段運行通知代碼的時間即將結(jié)束的時候界阁,如果仍然沒有成功的傳入內(nèi)容,會走到這個方法胖喳,可以在這里傳肯定不會出錯的內(nèi)容泡躯,或者默認傳遞原始的推送內(nèi)容。處理過程超時丽焊,則收到的通知直接展示出來较剃。

- (void)serviceExtensionTimeWillExpire
{
    // 將獲取到的內(nèi)容傳遞給content擴展
    self.contentHandler(self.bestAttemptContent);
}
Notification Content

在這兒做界面初始化的工作。

- (void)viewDidLoad
{
    [super viewDidLoad];
}

獲取通知信息技健,更新UI控件中的數(shù)據(jù)写穴。

- (void)didReceiveNotification:(UNNotification *)notification 
{    
    self.label.text = notification.request.content.body;
    self.titleLabelA.text = [NSString stringWithFormat:@"%@ + %@", notification.request.content.title, notification.request.content.subtitle];
    self.titleLabelB.text = [NSString stringWithFormat:@" = %ld", [notification.request.content.title integerValue] + [notification.request.content.subtitle integerValue]];
}

d、調(diào)整 info.plist

使用自定義的NotificationContent的時候雌贱,需要對應extensioninfo.plist啊送,因為推送通知內(nèi)容中的category字段偿短,與UNNotificationContentExtensioninfo.plistUNNotificationExtensionCategory字段的值要匹配,系統(tǒng)才能找到自定義的UI馋没。

categoryIdentifier

UNNotificationExtensionCategory默認是string類型昔逗,可以手動更改成array類型,array中的item(string)是categoryName篷朵。在收到通知的時候勾怒,我們可以讓服務(wù)器把這個通知的categoryIdentifier帶上,作用是我們可以根據(jù)視頻声旺、音樂笔链、圖片來分別自定義我們的通知內(nèi)容。不同的分類標識符腮猖,也會在使用UNNotificationAction的時候幫助我們區(qū)分是什么類型的通知鉴扫,方便我們對不同類型的通知做出不同的操作行為。我們目前在Service澈缺、Content坪创、aps寫死了categoryIdentifier,其實在收到系統(tǒng)推送時谍椅,每一個推送內(nèi)容最好帶上一個跟服務(wù)器約定好了的categoryIdentifier误堡,這樣方便我們根據(jù)categoryIdentifier來自定義不同類型的視圖,以及action雏吭。

UNNotificationExtensionInitialContentSizeRatio

UNNotificationExtensionInitialContentSizeRatio 這個值必須要有锁施,類型是一個浮點類型,代表的是高度與寬度的比值杖们。系統(tǒng)會使用這個比值悉抵,作為初始化view的大小。舉個簡單的例子來說摘完,如果該值為1姥饰,則該視圖為正方形。如果為0.5孝治,則代表高度是寬度的一半列粪。注意這個值只是初始化的一個值,在這個擴展添加后谈飒,可以重寫frame岂座,展示的時候,在我們還沒打開這個視圖預覽時杭措,背景是個類似圖片占位的灰色费什,那個灰色的高度寬度之比,就是通過這個值來設(shè)定手素。

UNNotificationExtensionDefaultContentHidden

UNNotificationExtensionDefaultContentHidden 這個值可選鸳址,是一個BOOL值瘩蚪。當為YES時,會隱藏上方原本推送的內(nèi)容視圖稿黍,只會顯示我們自定義的視圖疹瘦,因為在自定義視圖的時候闻察,我們可以取得推送內(nèi)容辕漂,然后按照我們想要的布局吴超,展示出來钉嘹。如果為NO時(默認為NO),推送視圖就會既有我們的自定義視圖鲸阻,也會有系統(tǒng)原本的推送內(nèi)容視圖(這里附件是不會顯示的跋涣,只會顯示body里面的文字喲)。這里需要隱藏默認消息框鸟悴,所以添加UNNotificationExtensionDefaultContentHidden屬性陈辱,Bool(YES)

NSExtensionMainStoryboard

至于NSExtensionMainStoryboard以及NSExtensionPointIdentifier细诸,系統(tǒng)默認生成沛贪,大家直接用就好,如果需要更改的震贵,只能更改使用的storyboard的名字(不過應該沒人會把系統(tǒng)的刪除再建立一個吧 O(∩_∩)O)

最初的info.plist
最初的info.plist
修改成array后的info.plist
修改成array后的info.plist
修改后Service和aps的info.plist
修改后Service和aps的info.plist

2利赋、通知服務(wù)擴展(UNNotificationServiceExtension)

a、簡介
支持附帶 Media Attachments

本地推送和遠程推送同時都可支持附帶Media Attachments猩系。不過遠程通知需要實現(xiàn)通知服務(wù)擴展UNNotificationServiceExtension媚送,在service extension里面去下載attachment,但是需要注意吟秩,service extension會限制下載的時間(30s)收恢,并且下載的文件大小也會同樣被限制火窒。這里畢竟是一個推送,而不是把所有的內(nèi)容都推送給用戶褪储。所以你應該去推送一些縮小比例之后的版本昔榴。比如圖片吱肌,推送里面附帶縮略圖坟瓢,當用戶打開app之后粒褒,再去下載完整的高清圖。視頻就附帶視頻的關(guān)鍵幀或者開頭的幾秒,當用戶打開app之后再去下載完整視頻苛萎。

UNNotificationAttachment 支持的附件格式和大小限制
  • 音頻5M(kUTTypeWaveformAudio/kUTTypeMP3/kUTTypeMPEG4Audio/kUTTypeAudioInterchangeFileFormat
  • 圖片10M(kUTTypeJPEG/kUTTypeGIF/kUTTypePNG
  • 視頻50M(kUTTypeMPEG/kUTTypeMPEG2Video/kUTTypeMPEG4/kUTTypeAVIMovie
校驗附件

系統(tǒng)會在通知注冊前校驗附件,如果附件出問題,通知注冊失敻笪!吟吝;校驗成功后,附件會轉(zhuǎn)入attachment data store蛹磺;如果附件是在app bundle俗批,則是會被copy來取代move辛慰。attachment data store的位置麻汰?利用代碼測試獲取在磁盤上的圖片文件作為attachment,會發(fā)現(xiàn)注冊完通知后智亮,圖片文件被移除,在app的沙盒中找不到該文件在哪里。


b颠区、NotificationService文件
#import <UserNotifications/UserNotifications.h>

@interface NotificationService : UNNotificationServiceExtension

@end

@interface NotificationService ()

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

@end

@implementation NotificationService

@end

c、Demo演示
最多30秒重寫通知內(nèi)容
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler
{
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    
    // 修改通知的內(nèi)容
    self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [ServiceExtension modified]", self.bestAttemptContent.title];
    
    // 設(shè)置UNNotificationAction
    [self getAction];
    
    // category的唯一標識,Remote Notification保持一致
    self.bestAttemptContent.categoryIdentifier = @"categoryOperationAction";
    
    // 加載網(wǎng)絡(luò)請求
    NSDictionary *userInfo =  self.bestAttemptContent.userInfo;
    NSString *mediaUrl = userInfo[@"media"][@"url"];
    NSString *mediaType = userInfo[@"media"][@"type"];
    
    if (!mediaUrl.length)// 不存在url則使用基本的內(nèi)容
    {
        self.contentHandler(self.bestAttemptContent);
    }
    else// 否則使用網(wǎng)絡(luò)請求到的內(nèi)容
    {
        // 創(chuàng)建附件資源
        // UNNotificationAttachment的url接收的是本地文件的url
        // 附件資源必須存在本地拗慨,如果是遠程推送的網(wǎng)絡(luò)資源需要提前下載到本地
        [self loadAttachmentForUrlString:mediaUrl withType:mediaType completionHandle:^(UNNotificationAttachment *attach) {
            
            if (attach)
            {
                // 將附件資源添加到 UNMutableNotificationContent 中
                self.bestAttemptContent.attachments = [NSArray arrayWithObject:attach];
            }
            self.contentHandler(self.bestAttemptContent);
        }];
    }
}
將獲取到的內(nèi)容傳遞給content擴展
- (void)serviceExtensionTimeWillExpire
{
    self.contentHandler(self.bestAttemptContent);
}
網(wǎng)絡(luò)請求
- (void)loadAttachmentForUrlString:(NSString *)urlStr withType:(NSString *)type completionHandle:(void(^)(UNNotificationAttachment *attach))completionHandler
{
    __block UNNotificationAttachment *attachment = nil;
    // 附件的URL
    NSURL *attachmentURL = [NSURL URLWithString:urlStr];
    // 獲取媒體類型的后綴
    NSString *fileExt = [self getfileExtWithMediaType:type];
    
    // 從網(wǎng)絡(luò)下載媒體資源
    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;
            // category的唯一標識烦却,Remote Notification保持一致
            // URL 資源路徑
            // options 資源可選操作 比如隱藏縮略圖之類的
            attachment = [UNNotificationAttachment attachmentWithIdentifier:@"categoryOperationAction" URL:localURL options:nil error:&attachmentError];
            if (attachmentError)
            {
                NSLog(@"%@", attachmentError.localizedDescription);
            }
        }
        
        // 將附件傳遞出去
        completionHandler(attachment);
    }] resume];
}
用于將媒體類型的后綴添加到文件路徑上
// 服務(wù)端在處理推送內(nèi)容時醋闭,最好加上媒體類型字段
- (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)getAction
{
    NSMutableArray *actionMutableArray = [[NSMutableArray alloc] initWithCapacity:3];
    
    // UNNotificationActionOptionAuthenticationRequired 需要解鎖顯示,點擊不會進app
    UNNotificationAction *unUnlockAction = [UNNotificationAction actionWithIdentifier:@"IdentifierNeedUnUnlock" title:@"需要解鎖" options: UNNotificationActionOptionAuthenticationRequired];
    
    // UNNotificationActionOptionDestructive 紅色文字,點擊不會進app
    UNNotificationAction *destructiveAction = [UNNotificationAction actionWithIdentifier:@"IdentifierRed" title:@"紅色顯示" options: UNNotificationActionOptionDestructive];
    
    // UNNotificationActionOptionForeground 黑色文字伤疙,點擊會進app
    // UNTextInputNotificationAction是輸入框Action蛙讥,buttonTitle是輸入框右邊的按鈕標題旁涤,placeholder是輸入框占位符
    UNTextInputNotificationAction *inputTextAction = [UNTextInputNotificationAction actionWithIdentifier:@"IdentifierInputText" title:@"輸入文本" options:UNNotificationActionOptionForeground textInputButtonTitle:@"發(fā)送" textInputPlaceholder:@"說說今天發(fā)生了啥......"];
    
    NSArray *identifierArray = [[NSArray alloc] initWithObjects:@"IdentifierNeedUnUnlock", @"IdentifierRed", @"IdentifierInputText", nil];
    [actionMutableArray addObjectsFromArray:@[unUnlockAction, destructiveAction, inputTextAction]];
    
    if (actionMutableArray.count > 1)
    {
        /**categoryWithIdentifier方法
         * identifier:是這個category的唯一標識侵蒙,用來區(qū)分多個category犁功,這個id不管是Local Notification案糙,還是remote Notification限嫌,一定要有并且要保持一致
         * actions:創(chuàng)建action的操作數(shù)組
         * intentIdentifiers:意圖標識符 可在 <Intents/INIntentIdentifiers.h> 中查看,主要是針對電話时捌、carplay 等開放的 API
         * options:通知選項 枚舉類型 也是為了支持 carplay
         */
        UNNotificationCategory *categoryNotification = [UNNotificationCategory categoryWithIdentifier:@"categoryOperationAction" actions:actionMutableArray intentIdentifiers:identifierArray options:UNNotificationCategoryOptionCustomDismissAction];
        
        // 將創(chuàng)建的 category 添加到通知中心
        [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:[NSSet setWithObject:categoryNotification]];
    }
}

d怒医、APP Server的aps

mutable-content這個鍵值為1,這意味著此條推送可以被 Service Extension 進行更改奢讨,也就是說要用Service Extension需要加上這個鍵值為1飒泻。

{"aps":{"alert":{"title":"Title...","subtitle":"Subtitle...","body":"Body..."},"sound":"default","badge": 1,"mutable-content": 1,"category": "categoryOperationAction",},"msgid":"123","media":{"type":"image","url":"https://www.fotor.com/images2/features/photo_effects/e_bw.jpg"}}

e卦睹、特別說明

? Notification Service Extension在使用時需要配置相關(guān)證書,我沒有開發(fā)者賬號,所以無法調(diào)試苗沧。??(買不起,貧窮限制了我的開發(fā)能力......)

? 要選擇相應的target來運行工程宏榕。

target

? 加斷點調(diào)試怎么不走相應方法卜壕?一個朋友找了很長時間的原因發(fā)現(xiàn)是xcode的問題译隘,那個朋友就是我......


3拗馒、通知內(nèi)容擴展(UNNotificationContentExtension)

a、在展示通知時展示一個自定義的用戶界面育特。

這個就是個簡單的storyboard文件念搬,內(nèi)部有一個View栈虚,這個View就是在上面的圖層中的自定義View視圖了熔酷。它與NotificationViewController所綁定偎肃。

設(shè)置通知的界面

這里使用純代碼方式來創(chuàng)建界面朱监,所以需要刪除MainInterface文件岸啡,然后在Notifications Contentinfo.plist中把NSExtensionMainStoryboard替換為NSExtensionPrincipalClass,并且value對應我們的類名NotificationViewController赫编。

#define Margin      15

@interface NotificationViewController () <UNNotificationContentExtension>

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

@end

- (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);

    // 設(shè)置控件邊框顏色
    [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];
}

b巡蘸、Demo演示
接收到通知的內(nèi)容
// 生成時默認實現(xiàn)了UNNotificationContentExtension協(xié)議的方法
- (void)didReceiveNotification:(UNNotification *)notification
{
    self.label.text = notification.request.content.title;
    self.subLabel.text = [NSString stringWithFormat:@"%@ [ContentExtension modified]", notification.request.content.subtitle];
    
    // 提取附件
    UNNotificationAttachment *attachment = notification.request.content.attachments.firstObject;
    if ([attachment.URL startAccessingSecurityScopedResource])
    {
        NSData *imageData = [NSData dataWithContentsOfURL:attachment.URL];
        [self.imageView setImage:[UIImage imageWithData:imageData]];
        [attachment.URL stopAccessingSecurityScopedResource];
    }
}
用戶操作
// 點擊通知進入app時觸發(fā)(殺死/切到后臺喚起)
- (void)didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(UNNotificationContentExtensionResponseOption))completion
{
    
    [self.hintLabel setText:[NSString stringWithFormat:@"觸發(fā)了%@", response.actionIdentifier]];
    
    if ([response.actionIdentifier isEqualToString:@"IdentifierNeedUnUnlock"])
    {
        NSLog(@"點擊了解鎖");
    }
    else if([response.actionIdentifier isEqualToString:@"IdentifierRed"])
    {
        NSLog(@"點擊了紅色");
    }
    else if([response.actionIdentifier isEqualToString:@"IdentifierInputText"])
    {
        UNTextInputNotificationResponse *textInputResponse = (UNTextInputNotificationResponse *)response;
        [self.hintLabel setText:[NSString stringWithFormat:@"用戶輸入的文字是:%@", textInputResponse.userText]];
    }
    else
    {
        NSLog(@"啥?");
    }
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        // 必須設(shè)置completion擂送,否則通知不會消失
        // UNNotificationContentExtensionResponseOptionDismiss 直接讓該通知消失
        // UNNotificationContentExtensionResponseOptionDismissAndForwardAction 消失并傳遞按鈕信息給AppDelegate悦荒,是否進入App看Att的設(shè)置
        completion(UNNotificationContentExtensionResponseOptionDismiss);
    });
}

c、控制媒體文件的播放
枚舉 UNNotificationContentExtensionMediaPlayPauseButtonType
typedef NS_ENUM(NSUInteger, UNNotificationContentExtensionMediaPlayPauseButtonType) {  
    // 沒有播放按鈕
    UNNotificationContentExtensionMediaPlayPauseButtonTypeNone,
    // 有播放按鈕嘹吨,點擊播放之后搬味,按鈕依舊存在,類似音樂播放的開關(guān)
    UNNotificationContentExtensionMediaPlayPauseButtonTypeDefault,
    // 有播放按鈕躺苦,點擊后身腻,播放按鈕消失,再次點擊暫停播放后匹厘,按鈕恢復
    UNNotificationContentExtensionMediaPlayPauseButtonTypeOverlay,
}
設(shè)置播放按鈕的屬性
// 設(shè)置播放按鈕的屬性
@property (nonatomic, readonly, assign) UNNotificationContentExtensionMediaPlayPauseButtonType mediaPlayPauseButtonType;
// 設(shè)置播放按鈕的frame
@property (nonatomic, readonly, assign) CGRect mediaPlayPauseButtonFrame;
// 設(shè)置播放按鈕的顏色
@property (nonatomic, readonly, copy) UIColor *mediaPlayPauseButtonTintColor;

// 開始跟暫停播放
- (void)mediaPlay;
- (void)mediaPause;

這些屬性都是readonly的嘀趟,所以直接用self.屬性去修改肯定是報錯的,所以我們能用的就只有get方法了愈诚。

根據(jù)button的類型她按,我們可以聯(lián)想到,如果button沒有炕柔,這個播放開始暫停的方法也沒用了酌泰。如果有button,自然我們就有了播放的操作匕累,我們得出了以下結(jié)論陵刹。一定要重寫它的frame來確定他的位置。指定顏色來設(shè)置它的顯示顏色欢嘿。設(shè)置button的類型讓他顯示出來衰琐。

// 返回默認樣式的button
- (UNNotificationContentExtensionMediaPlayPauseButtonType)mediaPlayPauseButtonType
{
    return UNNotificationContentExtensionMediaPlayPauseButtonTypeDefault;
}

// 返回button的frame
- (CGRect)mediaPlayPauseButtonFrame
{
    return CGRectMake(100, 100, 100, 100);
}

// 返回button的顏色
- (UIColor *)mediaPlayPauseButtonTintColor
{
    return [UIColor blueColor];
}
開始跟暫停播放的方法
// 開始播放
- (void)mediaPlay
{
    NSLog(@"mediaPlay也糊,開始播放");
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.extensionContext mediaPlayingPaused];
    });
}

// 暫停播放
- (void)mediaPause
{
    NSLog(@"mediaPause,暫停播放");
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.extensionContext mediaPlayingStarted];
    });
}

四羡宙、極光推送

1狸剃、簡介

a、極光推送的概念

極光推送(JPush)是獨立的第三方云推送平臺狗热,致力于為全球移動應用開發(fā)者提供移動消息推送服務(wù)钞馁。極光,是國內(nèi)領(lǐng)先的移動大數(shù)據(jù)的服務(wù)商匿刮。擁有開發(fā)者服務(wù)僧凰、廣告服務(wù)和數(shù)據(jù)服務(wù)三大產(chǎn)品體系。開發(fā)者服務(wù)助力app精細運營僻焚,覆蓋極光推送允悦、極光IM、極光短信虑啤、極光統(tǒng)計隙弛、社會化分享、極光認證狞山,廣告服務(wù)全闷,數(shù)據(jù)服務(wù)。說白了萍启,極光推送類似于我們每天在手機上接收到的消息总珠。那我們怎么在自己的app中實現(xiàn)這種功能呢?


b勘纯、準備證書和權(quán)限
? 創(chuàng)建APP ID

APP ID 是每一個IOS應用的全球唯一標識局服。無論代碼怎么改,圖標和應用名稱怎么換驳遵,只要bundle id沒變淫奔,ios系統(tǒng)就認為這是同一個應用。每開發(fā)一個新應用堤结,首先都需要到member center->identifier->APP IDS去創(chuàng)建一個bundle id唆迁。Explicit App ID的格式是com.domainname.appname,這種id只能用在一個app上竞穷。每一個新應用都要創(chuàng)建一個唐责,其中domainname可以使用公司的縮寫,全拼瘾带。

1鼠哥、登錄蘋果開發(fā)者網(wǎng)站,登錄開發(fā)者賬戶。添加新的AppID肴盏,并填寫相關(guān)的NameBundle ID科盛。

創(chuàng)建APP ID

2、為創(chuàng)建的APP ID 開啟Push Notification功能菜皂,已有的appID也可以繼續(xù)添加Push Notification功能。

添加Push Notification功能

3厉萝、完成以上操作后依次點擊Continue恍飘,點擊Register,完成APP ID的注冊谴垫。

? 配置極光推送端需要的兩種證書:開發(fā)證書章母,生產(chǎn)證書汽畴。
  1. 打開系統(tǒng)里自帶的“鑰匙串訪問”嗡官,創(chuàng)建CSRCertificate Signing Request)文件 。填寫用戶郵箱和常用名稱察滑,并選擇存儲到磁盤前弯,證書文件后綴為.certSigningRequest蚪缀。
鑰匙串訪問
鑰匙串訪問
  1. 點擊蘋果開發(fā)者網(wǎng)站賬戶左側(cè)的development/Production,上傳請求生成的CSR文件恕出。生成證書后询枚,點擊downLoad將證書下載到本地中,后綴為.cer文件浙巫。點擊生成的證書金蜀,在鑰匙串中打開,導出為.p12文件的畴,并存儲到本地渊抄。
配置開發(fā)/生產(chǎn)證書
在鑰匙串中打開,導出為.p12文件
生成的證書
? 把配置好的證書丧裁,傳遞到極光開發(fā)平臺上护桦。

在極光官網(wǎng)申請好的極光推送賬號里創(chuàng)建應用。點擊極光開發(fā)者服務(wù)渣慕,找到推送設(shè)置嘶炭,選擇iOS,用下載好的開發(fā)證書和生產(chǎn)證書導入逊桦,填寫設(shè)置好的p12證書密碼眨猎,點擊保存會生成一個appkey。集成極光推送SDK到項目里的時候會用到此appkey强经。


c睡陪、極光推送的消息形式

通知(APNS):手機的通知欄(狀態(tài)欄)上會顯示的一條通知信息。
自定義消息(應用內(nèi)消息):不會被 SDK 展示到通知欄上。自定義消息主要用于應用的內(nèi)部業(yè)務(wù)邏輯兰迫。朋友圈紅點就可以用這個信殊。極光推送采用的是長連接,所以自定義消息在網(wǎng)絡(luò)正常汁果、App處于前臺的情況下會馬上收到涡拘。
本地通知:SDK集成蘋果實現(xiàn)本地通知。


d据德、極光推送的實現(xiàn)原理

通過我們的App服務(wù)器或極光Web端調(diào)用極光的API能發(fā)起極光推送鳄乏。舉個例子,用戶A(userIdA)發(fā)消息給用戶B(userIdB)棘利。這里只考慮兩個都綁定好了deviceToken等橱野,不存在離線消息。

蘋果原生態(tài)下的流程圖
蘋果原生態(tài)下的流程圖
極光下的流程圖
極光下的流程圖

e善玫、JPush APNS通知的意義

iOS平臺上推送通知水援,只有APNS這個官方的通道,是可以隨時送達的茅郎。一般開發(fā)者都是自己部署應用服務(wù)器向APNS Server推送蜗元。JPush推送相比直接向APNS推送有什么好處呢?

減少開發(fā)及維護成本
  • 應用開發(fā)者不需要去開發(fā)維護自己的推送服務(wù)器與APNS對接
  • 集成了JPush SDK后不必自己維護更新device token
  • 通過JPushWebPortal直接推送,也可以調(diào)用JPushHTTP協(xié)議API來完成只洒,開發(fā)工作量大大減少
減少運營成本
  • 極光推送支持一次推送许帐,同時向Android和iOS平臺。支持統(tǒng)一的API與推送界面
  • 極光推送提供標簽毕谴、別名綁定機制成畦,以及提供了非常細分的用戶分群方式,運營起來非常簡單涝开、直觀
提供應用內(nèi)推送
  • 除了使得APNS推送更簡單循帐,也另外提供應用內(nèi)消息推送,這在類似于聊天的場景里很有必要

2舀武、項目中極光SDK的配置

a拄养、導入極光SDK

方法1:可以通過CocoaPods進行導入JPush
方法2:手動導入可以參考極光文檔-iOS SDK集成指南银舱。


b瘪匿、進入項目中的appDelegate導入頭文件,遵循代理寻馏。
#import "JPUSHService.h"http:// 引入JPush功能所需頭文件
#import <UserNotifications/UserNotifications.h>// 注冊APNs所需頭文件

@interface JpushManager ()<JPUSHRegisterDelegate,UNUserNotificationCenterDelegate>

@end

c棋弥、在didFinishLaunchingWithOptions中進行JPush的相關(guān)初始化設(shè)置
在didFinishLaunching方法中調(diào)用極光推送的配置方法
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{  
    // 極光推送
    [self configureJpushWithLaunchingOption:launchOptions];

    return YES;
}
聲明配置過程中使用到的全局常量
static NSString * const JPushAppKey = @"e98bc4beea9b2988976bae04";// 極光appKey
static NSString * const JPushChannel = @"Publish channel";// 固定的
// NO為開發(fā)環(huán)境,YES為生產(chǎn)環(huán)境诚欠。虛擬機和真機調(diào)試屬于開發(fā)環(huán)境顽染。測試包漾岳、企業(yè)包和App Store屬于生產(chǎn)環(huán)境
BOOL isProduction = !DEBUG;
BOOL kFUserJPush = NO;
實現(xiàn)極光推送的配置方法
- (void)configureJpushWithLaunchingOption:(NSDictionary *)launchingOption
{
    // 初始化推送
    [[JpushManager shareManager] setupJPushWithLaunchingOption:launchingOption appKey:JPushAppKey channel:JPushChannel apsForProduction:isProduction advertisingIdentifier:nil];
    
    // 設(shè)置角標為0
    [[JpushManager shareManager] setBadge:0];
    
    __weak __typeof(self)weakSelf = self;
    [JpushManager shareManager].afterReceiveNoticationHandle = ^(NSDictionary *userInfo){
        NSLog(@"接收到消息后處理消息");
        
        [weakSelf getMessageToHandle];
    };
}
初始化推送
- (void)setupJPushWithLaunchingOption:(NSDictionary *)launchingOption appKey:(NSString *)appKey channel:(NSString *)channel apsForProduction:(BOOL)isProduction advertisingIdentifier:(NSString *)advertisingId;
{
    // 添加APNs代碼 注冊極光
    JPUSHRegisterEntity *entity = [[JPUSHRegisterEntity alloc] init];
    entity.types = JPAuthorizationOptionAlert|JPAuthorizationOptionBadge|JPAuthorizationOptionSound;
    [JPUSHService registerForRemoteNotificationConfig:entity delegate:self];
    
    // 可以添加自定義categories
    NSSet<UNNotificationCategory *> *categories;
    entity.categories = categories;
    
    // IDFA為設(shè)備廣告標示符,用于廣告投放粉寞。通常不會改變尼荆,不同App獲取到都是一樣的。但如果用戶完全重置系統(tǒng)((設(shè)置程序 -> 通用 -> 還原 -> 還原位置與隱私) 唧垦,這個廣告標示符會重新生成捅儒。
    // IDFA用于同一設(shè)備下的不同app信息共享,如不需要使用业崖,advertisingIdentifier 可為nil
    // NSString *advertisingId = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
    [JPUSHService setupWithOption:launchingOption appKey:appKey channel:channel apsForProduction:isProduction advertisingIdentifier:advertisingId];
    
    // 獲取極光推送注冊ID(RegistrationID)
    // 原生是采用deviceToken來標識設(shè)備唯一性野芒。在極光中采用RegistrationID
    // 其生成原則優(yōu)先采用IDFA(如果設(shè)備未還原IDFA,卸載App后重新下載双炕,還是能被識別出老用戶),次采用deviceToken
    // 集成了 JPush SDK 的應用程序在第一次 App 啟動后撮抓,成功注冊到 JPush 服務(wù)器時妇斤,JPush 服務(wù)器會給客戶端返回唯一的該設(shè)備的標識 -—— RegistrationID
    [JPUSHService registrationIDCompletionHandler:^(int resCode, NSString *registrationID) {
        if(resCode == 0)
        {
            NSLog(@"registrationID 獲取成功為:%@",registrationID);
            
            // 設(shè)置別名
            // 一個設(shè)備只能有一個別名(Alias),但能有多個標簽丹拯。所以別名可以用userId站超,針對一個用戶
            // 標簽(Tag)可以用用戶所處分組,方便針對目標用戶推送乖酬,針對一批用戶
            [JPUSHService setAlias:[[NSUserDefaults standardUserDefaults] valueForKey:@"userId"]  completion:^(NSInteger iResCode, NSString *iAlias, NSInteger seq) {
                
                NSLog(@"設(shè)置別名");
                
            } seq:0];
        }
        else
        {
            NSLog(@"registrationID 獲取失敗死相,code為:%d",resCode);
        }
    }];
}

d、注冊DevieceToken
遠端推送需要獲取設(shè)備的Device Token
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{  
    NSLog(@"設(shè)備的Device Token為:%@", deviceToken);
    
    // 極光推送注冊 DeviceToken
    [[JpushManager shareManager] registerDeviceToken:deviceToken];
}
在appdelegate注冊設(shè)備處調(diào)用極光推送注冊 DeviceToken
- (void)registerDeviceToken:(NSData *)deviceToken
{
    [JPUSHService registerDeviceToken:deviceToken];
}

3咬像、JPUSHRegisterDelegate

a算撮、收到通知消息后展示
// 收到推送的消息后的回調(diào)
typedef void(^AfterReceiveNoticationHandle)(NSDictionary *userInfo);

/** 接收到消息后的處理 */
@property(copy,nonatomic) AfterReceiveNoticationHandle afterReceiveNoticationHandle;
- (void)jpushNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(NSInteger))completionHandler
{
    NSDictionary *userInfo = notification.request.content.userInfo;
    if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]])
    {
       [JPUSHService handleRemoteNotification:userInfo];
        
        if (self.afterReceiveNoticationHandle)
        {
            self.afterReceiveNoticationHandle(userInfo);
        }
    }
    
    // 需要執(zhí)行這個方法,選擇是否提醒用戶县昂,有Badge肮柜、Sound、Banner三種類型可以設(shè)置
    completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionSound|UNNotificationPresentationOptionBanner);
}

b倒彰、點擊通知進入App
- (void)jpushNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
{
    NSDictionary * userInfo = response.notification.request.content.userInfo;
    UNNotificationRequest *request = response.notification.request;// 收到推送的請求
    UNNotificationContent *content = request.content;// 收到推送的消息內(nèi)容
    NSNumber *badge = content.badge;// 推送消息的角標
    NSString *body = content.body;// 推送消息體
    UNNotificationSound *sound = content.sound;// 推送消息的聲音
    NSString *subtitle = content.subtitle;// 推送消息的副標題
    NSString *title = content.title;// 推送消息的標題
    
    NSLog(@"點擊通知欄审洞,收到遠程通知的用戶信息為:%@", userInfo);
    NSLog(@"解析后信息為:{\nbody:%@,\ntitle:%@,\nsubtitle:%@,\nbadge:%@待讳,\nsound:%@芒澜,\nuserInfo:%@\n}",body,title,subtitle,badge,sound,userInfo);
    
    // 清空Jpush中存儲的badge值
    [self setBadge:0];
    
    if([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]])// 遠程通知
    {
        NSLog(@"遠程通知");
    }
    else// 本地通知
    {
        NSLog(@"本地通知");
    }
    
    [JPUSHService handleRemoteNotification:userInfo];

    // 點擊消息進行跳轉(zhuǎn)到消息的詳情界面中
    // [self goToMssageViewControllerWith:userInfo];
    
    // 系統(tǒng)要求執(zhí)行這個方法
    completionHandler();
}

c、點擊通知打開設(shè)置APP
- (void)jpushNotificationCenter:(UNUserNotificationCenter *)center openSettingsForNotification:(UNNotification *)notification
{
    if (notification && [notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]])
    {
        NSLog(@"通知界面進入應用");
    }
    else
    {
        NSLog(@"設(shè)置界面進入應用");
    }
}

4创淡、封裝的便利方法

a痴晦、設(shè)置角標
- (void)setBadge:(int)badge
{
    [[UIApplication sharedApplication] setApplicationIconBadgeNumber:badge];
    [JPUSHService setBadge:badge];
}
b、設(shè)置別名
- (void)setAlias:(NSString *)aliasName
{
    [JPUSHService getAlias:^(NSInteger iResCode, NSString *iAlias, NSInteger seq) {
        NSLog(@"舊的別名為:iResCode == %ld辩昆,iAlias == %@",(long)iResCode,iAlias);
        
        if (![iAlias isEqualToString:aliasName])
        {
            [JPUSHService setAlias:aliasName completion:^(NSInteger iResCode, NSString *iAlias, NSInteger seq) {
                
                NSLog(@"設(shè)置新的別名:callBackTextView %@",[NSString stringWithFormat:@"iResCode:%ld, \niAlias: %@, \nseq: %ld\n", (long)iResCode, iAlias, (long)seq]);
                
            } seq:0];
            
        }
        
    } seq:0];
}
c阅酪、刪除別名
- (void)deleteAlias
{
    [JPUSHService deleteAlias:^(NSInteger iResCode, NSString *iAlias, NSInteger seq) {
        NSLog(@"刪除別名");
    } seq:0];
}
d旨袒、接收到消息后的處理
__weak __typeof(self)weakSelf = self;
[JpushManager shareManager].afterReceiveNoticationHandle = ^(NSDictionary *userInfo){
    NSLog(@"接收到消息后處理消息");
    
    [weakSelf getMessageToHandle];
};

// 接收到消息后處理消息
- (void)getMessageToHandle
{
    NSLog(@"這條消息價值百萬英鎊!J醴砚尽!")
}

Demo

Demo在我的Github上,歡迎下載辉词。
BasicsDemo

參考文獻

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載必孤,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。
  • 序言:七十年代末瑞躺,一起剝皮案震驚了整個濱河市敷搪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌幢哨,老刑警劉巖赡勘,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異捞镰,居然都是意外死亡闸与,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門岸售,熙熙樓的掌柜王于貴愁眉苦臉地迎上來践樱,“玉大人,你說我怎么就攤上這事凸丸】叫希” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵屎慢,是天一觀的道長瞭稼。 經(jīng)常有香客問我,道長抛人,這世上最難降的妖魔是什么弛姜? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮妖枚,結(jié)果婚禮上廷臼,老公的妹妹穿的比我還像新娘。我一直安慰自己绝页,他們只是感情好荠商,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著续誉,像睡著了一般莱没。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酷鸦,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天饰躲,我揣著相機與錄音牙咏,去河邊找鬼。 笑死嘹裂,一個胖子當著我的面吹牛妄壶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播寄狼,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼丁寄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了泊愧?” 一聲冷哼從身側(cè)響起伊磺,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后即寡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡墩蔓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年涮雷,在試婚紗的時候發(fā)現(xiàn)自己被綠了样刷。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡箕母,死狀恐怖钙勃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情同木,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布洲尊,位于F島的核電站,受9級特大地震影響丽涩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜矮男,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望眨补。 院中可真熱鬧,春花似錦甘晤、人聲如沸遏弱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至枣宫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間歇拆,已是汗流浹背渠啊。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人镣煮。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓镊折,卻偏偏與公主長得像,于是被迫代替她去往敵國和親赃泡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

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

  • 總體內(nèi)容1.推送通知的介紹2.本地通知3.遠程通知4.極光推送的使用 一、推送通知的介紹 1.1、推送通知的作用:...
    IIronMan閱讀 5,125評論 4 34
  • 推送通知 推送通知是當程序沒有啟動或不在前臺運行時,告訴用戶有新消息的一種途徑议忽。有遠程推送和本地推送之分愤估。 本地推...
    Drodly閱讀 1,119評論 0 3
  • 注:此文只現(xiàn)在已經(jīng)不能適配iOS10了,iOS10推送采用了新的方法速址,做iOS9及以下的系統(tǒng)可讀此篇文章玩焰。 最近公...
    TIME_for閱讀 33,282評論 85 322
  • 版本記錄 前言 前一篇已經(jīng)對ios的SDK進行了研究,這一篇則對iOS SDK 常見問題進行說明芍锚。1. 極光推送集...
    刀客傳奇閱讀 1,202評論 0 1
  • 極光推送的問題:https://community.jiguang.cn/t/topic/5145/23 用戶的應...
    guoguojianshu閱讀 469評論 0 0