首先,按照個推SDK集成指南配置好一個完整的工程。或者直接下載現有工程(需要修改bundle identifier、kGtAppId、kGtAppKey、kGtAppSecret)。
如有錯誤和待完善的地方,還請指正。
新建推送:

本文將介紹對推送消息的兩種處理方式。
在接收到推送消息時,分為3種情況,本文還將后兩者細分為兩種情況:
- APP處於前台
1.1 APP接收到推送后推送后首先彈出一個Alert提示是否跳轉頁面 - APP處於后台
2.1 點擊通知欄使APP進入前台后,直接跳轉頁面
2.2 點擊icon圖標使APP進入前台后,不作操作 - APP處於關閉狀態
3.1 點擊通知欄啟動APP,直接跳轉頁面
3.2 點擊icon圖標啟動APP,不作操作
方式一:
首先為AppDelegate添加一個屬性,
// 用來判斷是否是通過點擊通知欄開啟(喚醒)APP @property (nonatomic) BOOL isLaunchedByNotification;
當通過點擊通知欄來啟動或喚醒APP時,會調用didReceiveRemoteNotification:
方法,在該方法里將isLaunchedByNotification
的值置為YES:
/** APP已經接收到“遠程”通知(推送) - 透傳推送消息 */ - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { // 當APP處於后台或者關閉狀態,點擊通知欄就會先走這個方法,再使得個推SDK收到透傳消息回調 // 處理APNs代碼,通過userInfo可以取到推送的信息(包括內容,角標,自定義參數等)。如果需要彈窗等其他操作,則需要自行編碼。 NSLog(@"\n>>>APP已經接收到“遠程”通知(推送)[Receive RemoteNotification - Background Fetch]:%@\n\n",userInfo); completionHandler(UIBackgroundFetchResultNewData); self.isLaunchedByNotification = YES; }
然后會調用以下方法(當APP處於前台時會直接調用此方法),其中payloadData的值轉為NSString對象即為圖2中的消息內容里的JSON數據:
/** SDK收到透傳消息回調 */ - (void)GeTuiSdkDidReceivePayloadData:(NSData *)payloadData andTaskId:(NSString *)taskId andMsgId:(NSString *)msgId andOffLine:(BOOL)offLine fromGtAppId:(NSString *)appId { // 收到個推消息 NSString *payloadMsg = nil; if (payloadData) { payloadMsg = [[NSString alloc] initWithBytes:payloadData.bytes length:payloadData.length encoding:NSUTF8StringEncoding]; } // 當app不在前台時,接收到的推送消息offLine值均為YES // 判斷app是否是點擊通知欄消息進行喚醒或開啟 // 如果是點擊icon圖標使得app進入前台,則不做操作,並且同一條推送通知,此方法只執行一次 if (offLine) { // 離線消息,說明app接收推送時不在前台 if (self.isLaunchedByNotification) { // app是通過點擊通知欄進入前台 [[NSNotificationCenter defaultCenter] postNotificationName:kNOTIFICATION_PUSH object:nil userInfo:@{kNOTIFICATION_PUSH : payloadMsg}]; self.isLaunchedByNotification = NO; } else { // app是通過點擊icon進入前台,在這里不做操作 } } else if(!self.isLaunchedByNotification) { // app已經處於前台,提示框提示 [[NSNotificationCenter defaultCenter] postNotificationName:kNOTIFICATION_ALERT object:nil userInfo:@{kNOTIFICATION_ALERT : payloadMsg}]; }elf.isLaunchedByNotification = NO; } }
2.2和3.2情況下的問題
這兩種情況下,如果用戶不點擊通知欄而是點擊桌面icon圖標啟動或喚起APP,會直接調用GeTuiSdkDidReceivePayloadData:
方法,根據本文的方式處理的話確實不會有任何操作,但是通知欄的消息仍然存在,如果再次點擊通知欄消息,仍會調動didReceiveRemoteNotification:
方法,但是不會再調用GeTuiSdkDidReceivePayloadData:
方法,這樣的話isLaunchedByNotification
的值會被置為YES,而且無法再被置為NO。
解決辦法:在app進入前台后通過將Badge角標置為0來移除通知欄信息,代碼如下:
- (void)applicationDidBecomeActive:(UIApplication *)application { // 這里的寫法是為了在app進入前台后,清除通知欄消息 NSInteger badge = [UIApplication sharedApplication].applicationIconBadgeNumber; badge = badge == 1 ? 2 : 1; // 這里經過兩次賦值才可以移除通知欄消息 [UIApplication sharedApplication].applicationIconBadgeNumber = badge; [UIApplication sharedApplication].applicationIconBadgeNumber = 0; // // 下面這個方法只有Badge角標不為0時才執行,如果個推推送時Badge為0,那么不會走下面的方法 // if (badge) { // // badge = badge == 1 ? 2 : 1; // [UIApplication sharedApplication].applicationIconBadgeNumber = badge; // [UIApplication sharedApplication].applicationIconBadgeNumber = 0; // } }
方式二:
這種方式默認在通過點擊icon使app進入前台時不做操作。
當通過點擊通知欄來啟動或喚醒APP時,會調用didReceiveRemoteNotification:
方法,接收到的推送內容包含在userInfo
參數里,可以在此方法里對推送消息進行操作:
/** APP已經接收到“遠程”通知(推送) - 透傳推送消息 */ - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { // 當APP處於后台或者關閉狀態,點擊通知欄就會先走這個方法,再使得個推SDK收到透傳消息回調 // 處理APNs代碼,通過userInfo可以取到推送的信息(包括內容,角標,自定義參數等)。如果需要彈窗等其他操作,則需要自行編碼。 NSLog(@"\n>>>APP已經接收到“遠程”通知(推送)[Receive RemoteNotification - Background Fetch]:%@\n\n",userInfo); completionHandler(UIBackgroundFetchResultNewData); // app是通過點擊通知欄進入前台 [[NSNotificationCenter defaultCenter] postNotificationName:kNOTIFICATION_PUSH object:nil userInfo:@{kNOTIFICATION_PUSH : payloadMsg}]; }
同時,由於系統方法調用完成后,個推仍會調用一次GeTuiSdkDidReceivePayloadData:
方法,需要在GeTuiSdkDidReceivePayloadData:
方法里判斷當前消息是否為offLine離線消息,如果是離線消息則不做任何處理:
/** SDK收到透傳消息回調 */ - (void)GeTuiSdkDidReceivePayloadData:(NSData *)payloadData andTaskId:(NSString *)taskId andMsgId:(NSString *)msgId andOffLine:(BOOL)offLine fromGtAppId:(NSString *)appId { // 收到個推消息 NSString *payloadMsg = nil; if (payloadData) { payloadMsg = [[NSString alloc] initWithBytes:payloadData.bytes length:payloadData.length encoding:NSUTF8StringEncoding]; } // 當app在前台時,接收到的推送消息offLine值均為NO // 對於離線消息,這里不做操作 if (!offLine) { // app已經處於前台,提示框提示 [[NSNotificationCenter defaultCenter] postNotificationName:kNOTIFICATION_ALERT object:nil userInfo:@{kNOTIFICATION_ALERT : payloadMsg}]; } }
對於角標的處理可以參考方式一中的處理方法。
前言 在去年的蘋果大會上,蘋果帶來的iOS 10 系統中將之前繁雜的推送通知統一成UserNotifications.framework 來集中管理和使用通知功能,還增加一些實用的功能——撤回單條通知、更新已展示通知、中途修改通知內容、在通知中顯示多媒體資源、自定義UI等功能。
前言
在去年的蘋果大會上,蘋果帶來的iOS 10 系統中將之前繁雜的推送通知統一成UserNotifications.framework 來集中管理和使用通知功能,還增加一些實用的功能——撤回單條通知、更新已展示通知、中途修改通知內容、在通知中顯示多媒體資源、自定義UI等功能。
那么在ios10之前,ios的消息推送是怎么分類的呢?
ios 10之前
在ios之前,iOS推送分為Local Notifications(本地推送) 和 Remote Notifications(遠程推送)。
本地推送
不需要服務器支持(無需聯網)就能發出的推送通知,app本地創建通知,加入到系統的Schedule里,如果觸發器條件達成時會推送相應的消息內容,如常見的定時任務鬧鍾等。
使用上也是非常簡單。
/*
@property(nonatomic,copy) NSDate *fireDate; @property(nonatomic,copy) NSTimeZone *timeZone; 時區 @property(nonatomic) NSCalendarUnit repeatInterval; 重復間隔(枚舉) @property(nonatomic,copy) NSCalendar *repeatCalendar; 重復日期(NSCalendar) @property(nonatomic,copy) CLRegion *region 設置區域(設置當進入某一個區域時,發出一個通知) @property(nonatomic,assign) BOOL regionTriggersOnce YES,只會在第一次進入某一個區域時發出通知.NO,每次進入該區域都會發通知 @property(nonatomic,copy) NSString *alertBody; @property(nonatomic) BOOL hasAction; 是否隱藏鎖屏界面設置的alertAction @property(nonatomic,copy) NSString *alertAction; 設置鎖屏界面一個文字 @property(nonatomic,copy) NSString *alertLaunchImage; 啟動圖片 @property(nonatomic,copy) NSString *alertTitle @property(nonatomic,copy) NSString *soundName; @property(nonatomic) NSInteger applicationIconBadgeNumber; @property(nonatomic,copy) NSDictionary *userInfo; // 設置通知的額外的數據 */ - (IBAction)addLocalNote:(id)sender { // 創建一個本地通知 UILocalNotification *localNote = [[UILocalNotification alloc] init]; // 設置本地通知的一些屬性(通知發出的時間/通知的內容) // 設置通知發出的時間 localNote.fireDate = [NSDate dateWithTimeIntervalSinceNow:5.0]; //設置通知的內容 localNote.alertBody = @"吃飯了嗎?"; //設置鎖屏界面的文字 localNote.alertAction = @"查看具體的消息"; //設置鎖屏界面alertAction是否有效 localNote.hasAction = YES; //設置通過點擊通知打開APP的時候的啟動圖片(無論字符串設置成什么內容,都是顯示應用程序的啟動圖片) localNote.alertLaunchImage = @"111"; //設置通知中心通知的標題 localNote.alertTitle = @"222222222222"; //設置音效 localNote.soundName = @"buyao.wav"; //設置應用程序圖標右上角的數字 localNote.applicationIconBadgeNumber = 1; //設置通知之后的屬性 localNote.userInfo = @{@"name" : @"張三", @"toName" : @"李四"}; //調度通知 [[UIApplication sharedApplication] scheduleLocalNotification:localNote]; }
當用戶點擊本地推送通知的時候,會自動打開app,這里有2種情況:app在后台運行,或者被系統進程殺死,對於這兩種情況,我們怎么處理呢?
app后台運行
這時候我們只需要調用下AppDelegate方法即可。代碼實現
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification { // 跳轉邏輯 if (application.applicationState == UIApplicationStateActive) return; if (application.applicationState == UIApplicationStateInactive) { // 當應用在后台收到本地通知時執行的跳轉代碼 [self jumpToSession]; } NSLog(@"local notifacation %@", notification); } - (void)jumpToSession { UILabel *redView = [[UILabel alloc] init]; redView.backgroundColor = [UIColor redColor]; redView.frame = CGRectMake(0, 100, 300, 400); redView.numberOfLines = 0; // redView.text = [NSString stringWithFormat:@"%@", launchOptions]; [self.window.rootViewController.view addSubview:redView]; }
app被殺死
對於app被殺死的情況,要先啟動app,啟動完畢會調用AppDelegate方法。
需要特別注意的是:在iOS8.0以后本地通知有了一些變化,如果要使用本地通知,需要得到用戶的許可。
部分代碼實現:
#define IS_iOS8 ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) @interface AppDelegate () @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { /* UIUserNotificationTypeNone = 0, 不發出通知 UIUserNotificationTypeBadge = 1 << 0, 改變應用程序圖標右上角的數字 UIUserNotificationTypeSound = 1 << 1, 播放音效 UIUserNotificationTypeAlert = 1 << 2, 是否運行顯示橫幅 */ [application setApplicationIconBadgeNumber:0]; if (IS_iOS8) { UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIUserNotificationTypeSound categories:nil]; [application registerUserNotificationSettings:settings]; } // 如果是正常啟動應用程序,那么launchOptions參數是null,其他方式需要對launchOptions設置 if (launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]) { // 當被殺死狀態收到本地通知時執行的跳轉代碼 // [self jumpToSession]; UILabel *redView = [[UILabel alloc] init]; redView.backgroundColor = [UIColor redColor]; redView.frame = CGRectMake(0, 100, 300, 400); redView.numberOfLines = 0; redView.text = [NSString stringWithFormat:@"%@", launchOptions]; [self.window.rootViewController.view addSubview:redView]; } return YES; }
遠程推送
遠程推送指從遠程服務器推送給客戶端的通知(需要聯網),遠程推送服務一般采用蘋果的APNS (Apple Push Notification Service)。
要實現遠程推送,一般會涉及到三個階段:
- APNS Pusher應用程序把要發送的消息、目的iPhone的標識打包,發給APNS。
- APNS在自身的已注冊Push服務的iPhone列表中,查找有相應標識的iPhone,並把消息發到iPhone。
- iPhone把發來的消息傳遞給相應的應用程序, 並且按照設定彈出Push通知。
基本配置
條件:新建一個對應你bundle的push 證書,打開Push Notifications 開關(XCode7不打開也可以正常使用,XCode8以后必須打開)。
代碼實現:
注冊接受APNs通知。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { if ([[UIDevice currentDevice].systemVersion doubleValue] >= 8.0) { // 1.注冊UserNotification,以獲取推送通知的權限 UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge categories:nil]; [application registerUserNotificationSettings:settings]; // 2.注冊遠程推送 [application registerForRemoteNotifications]; } else { [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeNewsstandContentAvailability | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound]; } return YES; }
調用AppDelegate方法,獲取到用戶的deviceToken。
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { // <32e7cf5f 8af9a8d4 2a3aaa76 7f3e9f8e 1f7ea8ff 39f50a2a e383528d 7ee9a4ea> // <32e7cf5f 8af9a8d4 2a3aaa76 7f3e9f8e 1f7ea8ff 39f50a2a e383528d 7ee9a4ea> NSLog(@"%@", deviceToken.description); }
推送通知,和本地通知一樣有兩種狀況。
// 當接受到遠程退職時會執行該方法(當進入前台或者應用程序在前台) - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { NSLog(@"%@", userInfo); UIView *redView = [[UIView alloc] init]; redView.backgroundColor = [UIColor redColor]; redView.frame = CGRectMake(100, 100, 100, 100); [self.window.rootViewController.view addSubview:redView]; }
蘋果建議使用方法
/* 1.開啟后台模式 2.調用completionHandler,告訴系統你現在是否有新的數據更新 3.userInfo添加一個字段:"content-available" : "1" : 只要添加了該字段,接受到通知都會在后台運行 */ - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { NSLog(@"%@", userInfo); UIView *redView = [[UIView alloc] init]; redView.backgroundColor = [UIColor redColor]; redView.frame = CGRectMake(100, 100, 100, 100); [self.window.rootViewController.view addSubview:redView]; completionHandler(UIBackgroundFetchResultNewData); }
UserNotitfication
iOS10 中統一了本地推送和遠程推送的 API,在 UserNotifications.framework 來統一處理與推送相關任務,並增加了圖片、音頻、視頻,自定義通知 UI 等新特性。
通知界面
多媒體
在此次版本中,iOS10 不僅新增消息的3dtouch等,還對圖片、音頻、視頻等多媒體做了改進和優化。
類型 | 限制大小 |
---|---|
圖片 | 10M |
音頻 | 5M |
視頻 | 50M |
多媒體推送代碼:
if #available(iOS 10.0, *) { let content = UNMutableNotificationContent() content.title = "iOS10 推送測試" content.body = "附件" content.userInfo = ["icon":"1","mutable-content":1] content.categoryIdentifier = "InputSomething" let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 3, repeats: false) let requestIdentifier = "imageLocal" if let imageURL = Bundle.main.url(forResource: "avatar@2x", withExtension: "png"), let attachment = try? UNNotificationAttachment(identifier: "imageAttachment", url: imageURL, options: nil) { content.attachments = [attachment] } let request = UNNotificationRequest(identifier: requestIdentifier, content: content, trigger: trigger) UNUserNotificationCenter.current().add(request, withCompletionHandler: { (error) in if (error != nil) { print("error: \(error.debugDescription)") } }) }
通常在做多媒體自定義推送的時候,一般會用到UNNotificationServiceExtension應用擴展,通過在 payload 中增加 mutable-content 字段來觸發擴展。
{
"aps":{ "alert":"IOS10 推送測試", "sound":"default", "badge":1, "mutable-content":1, "category":"InputSomething" }, "image":"https://ws1.sinaimg.cn/mw690/934b5ef8gw1fapg2ssteej20oz0oz420.jpg" }
當推送達到 app 時,會啟動擴展並回調 didReceive 方法。在該方法里面可以對推送的 UNMutableNotificationContent 做出相應的修改。在 didReceive 回調方法中的 request 包含了推送的具體信息,可以通過其 userInfo 屬性來解析出多媒體的 url。
let imageURL = Bundle.main.url(forResource: "lufei", withExtension: "jpg")
值得注意的是這里 Bundle 指的是擴展的沙盒,不是 app 的沙盒,所以資源的路徑要正確。
而讀取遠程資源比讀取本地資源一般要多一步保存操作。
private func downloadAndSave(url: URL, handler: @escaping (_ localURL: URL?) -> Void) { let task = URLSession.shared.dataTask(with: url, completionHandler: { data, res, error in var localURL: URL? = 下載完之后保存到本地並返回本地的 url handler(localURL) }) task.resume() }
得到本地的 url 之后操作就一樣了,都是通過 url 來生成一個 UNNotificationAttachment 對象。一切都操作完之后將這個 UNMutableNotificationContent 對象返還 contentHandler(bestAttemptContent)。
自定義界面
其中上面的黃色區域可以理解成一個 ViewController 操作,下面綠色部分就是 Title 之類的顯示內容。這部分是可以隱藏的。在擴展的目錄下的 info.plist 編輯一些界面相關的東西。
說明:
- UNNotificationExtensionCategory 觸發 Extension 的 category 這里需要在注冊才能有效的觸發 字符串類型
- UNNotificationExtensionInitialContentSizeRatio 上圖黃色區域的長寬比,float 類型
- UNNotificationExtensionDefaultContentHidden 默認內容是否隱藏,Bool 類型