注意事项:
- 需要打开通知权限,
- 推送证书、设置info.plist文件,设置自动生成的 .entitlements文件
【本地和远程通知编程指南】:
apple api文档: 设置远程通知服务器
他人的demo:github
APNs (Apple远程推送通知服务)远程通知
本地通知、远程通知
本地通知和远程通知 (使用APNs).
代码:
//
// AppDelegate.m
//
#import "AppDelegate.h"
#import <UserNotifications/UserNotifications.h>
@interface AppDelegate ()<UNUserNotificationCenterDelegate>
@end
@implementation AppDelegate
// 远程推送APNS优点:长连接、离线状态也可以、安装identifier分组通知
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 检查网络
[self checkNetword];
//注册远程推送服务
//1.enable the Push Notifications capability in your Xcode project
CGFloat version = [[[UIDevice currentDevice] systemVersion] floatValue];
//设置通知类型
if (version >= 10.0)
{
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
/// 在设备可以接受任何通知之前,调用请求授权会产生提示(是否打开通知,“允许”、“不允许”):requestAuthorization
[center requestAuthorizationWithOptions:UNAuthorizationOptionCarPlay | UNAuthorizationOptionSound | UNAuthorizationOptionBadge | UNAuthorizationOptionAlert completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
NSLog(@" iOS 10 request notification success");
}else{
NSLog(@" iOS 10 request notification fail");
}
}];
}
else if (version >= 8.0)
{
UIUserNotificationSettings *setting = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeSound | UIUserNotificationTypeBadge | UIUserNotificationTypeAlert categories:nil];
[application registerUserNotificationSettings:setting];
}else
{ //iOS <= 7.0
UIRemoteNotificationType type = UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound;
[application registerForRemoteNotificationTypes:type];
}
//2.注册app 适用于iOS 8+
[[UIApplication sharedApplication] registerForRemoteNotifications];
// APNs内容获取,如果apn有值,则是通过远程通知点击进来
NSDictionary *apn = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
return YES;
}
// 新打开/后台到前台/挂起到运行 均调用 (挂起比如来电/双击home)
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
// App icon上的徽章清零 (APNs)
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
}
/// 2. 注册成功调用的方法 (远程推送通知):成功获取deviceToken,你需要将令牌deviceToken发送到后端推送服务器,这样你就可以发送通知到此设备上了
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
// token 不要本地缓存,当你重新启动、用户换个手机、升级系统都会返回新的token
// 安全的加密上传到我们的服务器❗️❗️❗️
NSString *deviceTokenStr = [self deviceTokenStrWithDeviceToken:deviceToken];
NSLog(@"注册远程通知 成功 deviceToken:%@, deviceTokenStr:%@", deviceToken, deviceTokenStr);
if (deviceToken) {
// (将器转换成字符串,发送后台)
/// 为了实现这一点,可以将数组分离到其组件中,再将这些组件转换为十六进制字符串,然后将它们重新连接到字符串
NSMutableString *deviceTokenString = [NSMutableString string];
const char *bytes = deviceToken.bytes;
NSInteger count = deviceToken.length;
for (int i = 0; i < count; i++) {
[deviceTokenString appendFormat:@"%02x", bytes[i]&0x000000FF];
}
//NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
// [defaults setObject: deviceTokenString forKey: @"phoneToken"];
//[defaults synchronize];
//上传远程通知deviceToken到我们的后台服务器
}
}
///2. 注册失败调用的方法 (远程推送通知):未获得为设备提供的令牌deviceToken时调用该方法,并显示为什么注册失败
-(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
//失败之后,找个合适机会再试一次
NSLog(@"注册远程通知 失败 error:%@", error);
}
#pragma mark - app收到通知调用的方法
#pragma mark - ios 10+ “后台通知:在APP未运行的情况下,也可以保持及时更新”
// ios 10+ : Asks the delegate how to handle a notification that arrived while the app was running in the foreground.
/// 仅当App在前台运行时:此时用户正在使用 App,收到推送消息时默认不会弹出消息提示框, 准备呈现通知时, 才会调用该委托方法.
/// 一般在此方法里选择将通知显示为声音, 徽章, 横幅, 或显示在通知列表中.
/// @param center 用户通知中心
/// @param notification 当前通知
/// @param completionHandler 回调通知选项: 横幅, 声音, 徽章...
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
UNNotificationRequest *request = notification.request;
UNNotificationContent *conten = request.content;
NSDictionary *userInfo = conten.userInfo;
if ([request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
NSLog(@"即将展示远程通知");
}else {
NSLog(@"即将展示本地通知");
}
NSLog(@"title:%@, subtitle:%@, body:%@, categoryIdentifier:%@, sound:%@, badge:%@, userInfo:%@", conten.title, conten.subtitle, conten.body, conten.categoryIdentifier, conten.sound, conten.badge, userInfo);
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:[[NSString stringWithFormat:@"%@", conten.badge] integerValue]];
// 以下是在App前台运行时, 仍要显示的通知选项
completionHandler(UNNotificationPresentationOptionAlert + UNNotificationPresentationOptionSound + UNNotificationPresentationOptionBadge);
}
// ios 10+ : 用户点击远程通知启动app,此时用户点击推送消息会将 App 从后台唤醒,(后台进入)“提醒通知”
/// 当用户通过点击通知打开App/关闭通知或点击通知按钮时, 调用该方法.
/// (必须在application:didFinishLaunchingWithOptions:里设置代理)
/// @param center 用户通知中心
/// @param response 响应事件
/// @param completionHandler 处理完成的回调
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
{
UNNotificationRequest *request = response.notification.request;
UNNotificationContent *conten = request.content;
NSDictionary *userInfo = conten.userInfo;
if ([request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
NSLog(@"点击了远程通知");
}else {
NSLog(@"点击了本地通知");
}
NSLog(@"title:%@, subtitle:%@, body:%@, categoryIdentifier:%@, sound:%@, badge:%@, userInfo:%@, actionIdentifier:%@", conten.title, conten.subtitle, conten.body, conten.categoryIdentifier, conten.sound, conten.badge, userInfo, response.actionIdentifier);
// Always call the completion handler when done。
// 无论是不是预期数据,都要在最后调用completionHandler(),告诉系统,你已经完成了通知打开处理
completionHandler();
}
#pragma mark - ios 7,8,9
// 基于iOS7及以上的系统版本,如果是使用 iOS 7 的 Remote Notification 特性那么此函数将被调用
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
NSLog(@"%@",userInfo);
}
// 将deviceToken转换成字符串
- (NSString *)deviceTokenStrWithDeviceToken:(NSData *)deviceToken {
NSString *tokenStr;
if (deviceToken) {
if ([[deviceToken description] containsString:@"length = "]) { // iOS 13 DeviceToken 适配。
NSMutableString *deviceTokenString = [NSMutableString string];
const char *bytes = deviceToken.bytes;
NSInteger count = deviceToken.length;
for (int i = 0; i < count; i++) {
[deviceTokenString appendFormat:@"%02x", bytes[i]&0x000000FF];
}
tokenStr = [NSString stringWithString:deviceTokenString];
}else {
tokenStr = [[[[deviceToken description]stringByReplacingOccurrencesOfString:@"<" withString:@""]stringByReplacingOccurrencesOfString:@">" withString:@""]stringByReplacingOccurrencesOfString:@" " withString:@""];
}
}
return tokenStr;
}
// 检查联网状态 (为了使国行手机在第一次运行App时弹出网络权限弹框, 故需要请求网络连接)
- (void)checkNetword {
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.baidu.com"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:3];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
[task resume];
}
/**
3.上传payload和device token到APNS
(1、服务器有两种方式建立和apns的安全连接(token和证书)
(2、服务器发送POST请求:必须包含以下信息
(3、证书建立连接的话:证书和CSR文件绑定,CSR文件作为私钥加密证书,证书当做公钥用来和APNS交互。我们服务器安装这两种证书,证书有效期1年。
The JSON payload that you want to send
The device token for the user’s device
Request-header fields specifying how to deliver the notification
For token-based authentication, your provider server’s current authentication token(大多是证书)
HEADERS
- END_STREAM
+ END_HEADERS
:method = POST
:scheme = https
:path = /3/device/00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0
host = api.sandbox.push.apple.com
apns-id = eabeae54-14a8-11e5-b60b-1697f925ec7b
apns-push-type = alert
apns-expiration = 0
apns-priority = 10
DATA
+ END_STREAM
{ "aps" : { "alert" : "Hello" } }
*/
/**
4.创造一个新的远程通知
大小限制在4~5KB之间
json payload:aps字段告诉怎么显示,是弹框、声音或者badge
可以自定义key,和aps字典同级
{
“aps” : {
“alert” : {
“title” : “Game Request”,
“subtitle” : “Five Card Draw”
“body” : “Bob wants to play poker”,
},
"badge" : 9,
"sound" : "bingbong.aiff"
“category” : “GAME_INVITATION”
},
“gameID” : “12345678”
}
*/
#pragma mark - UISceneSession lifecycle
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options API_AVAILABLE(ios(13.0)){
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
}
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions API_AVAILABLE(ios(13.0)){
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
@end
本地通知:
代码:
-(void)HandleTimerName
{
NSMutableArray *arr = [[NSMutableArray alloc] init];
for (int i =0; i<1; i++) {
NSDictionary *dic=@{@"site_name":@"您有单词学习计划,请开始学习吧~",
@"timer":self.btnRemindTime.titleLabel.text,
@"infoKey":self.bookUuid,
@"type":@"-1",
@"plan_name":self.nameTextField.text
};
[arr addObject:dic];
}
[self deleteLocalNotification:[arr objectAtIndex:0]];
[self addLocalNotification:arr];
}
- (void)addLocalNotification:(NSArray *)array
{
// 设置一个按照固定时间的本地推送
NSDate *now = [NSDate date];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *components = [[NSDateComponents alloc] init];
NSInteger unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitWeekday | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
components = [calendar components:unitFlags fromDate:now];
// 通过循环 将每一个时间都设置成本地推送
for (int i=0; i<array.count; i++) {
//设置时区(跟随手机的时区)
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
localNotification.timeZone = [NSTimeZone defaultTimeZone];
if (localNotification) {
// 设置推送时的显示内容
localNotification.alertBody = array[i][@"site_name"];
localNotification.alertAction = NSLocalizedString(@"All_open",@"");
// 推送的铃声 不能超过30秒 否则会自定变为默认铃声
localNotification.soundName = @"2.caf";
//小图标数字
localNotification.applicationIconBadgeNumber++;
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"HH:mm"];
NSDate *date = [formatter dateFromString:array[i][@"timer"]];
//通知发出的时间
localNotification.fireDate = date;
}
//循环通知的周期 每天
localNotification.repeatInterval = kCFCalendarUnitDay;
//设置userinfo方便撤销
NSDictionary * info = @{@"plan_name":array[i][@"plan_name"],@"type":array[i][@"type"],@"infoKey":array[i][@"infoKey"],@"site_name":array[i][@"site_name"]};
localNotification.userInfo = info;
//启动任务
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
}
}
-(void) deleteLocalNotification:(NSDictionary *)dict
{
// 获取所有本地通知数组
NSArray *localNotifications = [UIApplication sharedApplication].scheduledLocalNotifications;
for (UILocalNotification *notification in localNotifications)
{
NSDictionary *userInfo = notification.userInfo;
if ([dict[@"infoKey"] isEqualToString:userInfo[@"infoKey"]]) {
[[UIApplication sharedApplication] cancelLocalNotification:notification];
}
}
}
网友评论