当应用不在前台运行时,可以通过user notifications推送信息给用户。user notifications包括local notifications和remote notifications两种类型。操作系统展示本地和远程通知的方式是一样的,包括展示提醒信息,应用图标标记和声音。当用户收到通知时,可打开应用去查看详细信息。
本地通知和远程通知最基本的区别是:
- 本地通知由应用本身安排和推送到本机
- 远程通知由服务器发送到APNs(Apple Push Notification service),再由其推送到设备
使用User Notification第一步,注册通知类型:
从iOS8开始,不管是本地还是远程通知,如果应用想使用该功能,必须先注册希望接收的通知类型。
UIUserNotificationType userNotificationTypes = UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound;
UIUserNotificationSettings *userNotificationSettings = [UIUserNotificationSettings settingsForTypes:userNotificationTypes categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:userNotificationSettings];
如果不注册,系统将不推送任何类型的通知,即使应用在前台运行也不会触发application:didReceiveLocalNotification:
方法,在系统设置中也没有设置该应用的通知选项。
当第一次执行注册代码时,系统会弹出一个警告窗口,告诉用户该应用希望推送通知,用户可以选择是否允许。使用不同的类型组合再次注册,则系统设置中也会相应改变,未注册的类型在系统设置中也就没有相应选项了。如果展示通知的类型设为UIUserNotificationTypeNone,那么之后在系统设置中设置该应用的通知选项将消失,并且即使改变展示通知的类型再去注册也没有了效果。
注册之后系统会调用UIApplicationDelegate对象的application:didRegisterUserNotificationSettings: 方法,传来的UIUserNotificationSettings参数指明了当前用户允许的通知展示类型。可以通过[UIApplication sharedApplication].currentUserNotificationSettings随时查看当前用户允许的通知展示类型。
所以,如果没有特殊情况,应该直接注册所有的通知展示类型。
Local Notification
本地通知是实现一些基于时间的行为的理想方式,例如日历事件,提醒事项等。每一个应用最多只能同时安排64条本地通知,若此时安排新的本地通知会被系统丢弃,重复安排的通知按同一条计算。
当应用收到本地通知时在前台,系统会调用AppDelegate中的application:didReceiveLocalNotification:方法,当应用在后台挂起或关闭时则不触发该方法。但是当应用在后台挂起时,用户通过点击或滑动提醒信息进入应用时,会调用application:didReceiveLocalNotification:方法,若用户通过点击应用图标进入应用则不会调用。若应用已关闭,则都不会调用application:didReceiveLocalNotification:方法,用户在应用关闭状态下通过本地通知打开应用,可以在application:didFinishLaunchingWithOptions:方法中获得该通知,如果通过点击应用图标打开则无法获得
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UILocalNotification *localNotification = launchOptions[UIApplicationLaunchOptionsLocalNotificationKey];
}
构建一个UILocalNotification:
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
// fireDate设置推送通知的日期和时间,受timeZone属性的影响,同时设置region会发生异常
localNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:5];
// timeZone设置时区,对fireDate有影响。默认为nil,此时fireDate将根据GMT时间
localNotification.timeZone = [NSTimeZone defaultTimeZone];
// repeatIntervar设置重复推送的间隔,默认为0,不重复推送。
localNotification.repeatInterval = kCFCalendarUnitMonth;
// repeatCalendar设置重复推送时使用的历法,默认为nil,则使用[NSCalendar currentCalendar]
localNotification.repeatCalendar = [NSCalendar currentCalendar];
// region设置推送通知的地区,同时设置fireDate会发生异常
localNotification.region = nil;
// regionTriggersOnce设置是否只在第一次到达地区边界时推送消息
localNotification.regionTriggersOnce = NO;
// alertBody设置通知要提醒的信息
localNotification.alertBody = @"alertBody";
// alertAction设置滑动动作的标题
localNotification.alertAction = @"alertAction";
// alertTitle设置通知提醒的标题
localNotification.alertTitle = @"alertTitle";
// hasAction设置是否展示动作按钮
localNotification.hasAction = YES;
// alertLaunchImage设置动作触发时应用启动图片
localNotification.alertLaunchImage = @"default";
// applicationIconBadgeNumber设置显示在应用图标上的未读信息数量
localNotification.applicationIconBadgeNumber = 1;
// soundName设置通知的提示声音的文件
localNotification.soundName = UILocalNotificationDefaultSoundName;
// userInfo设置通知的自定义信息
localNotification.userInfo = @{};
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
除了将通知列入安排外,应用还可以通过presentLocalNotificationNow: 方法立即推送通知。可以通过UIApplication对象的cancelLocalNotification:方法和cancelAllLocalNotifications方法来取消已安排的通知,同时这些方法可以使正在展示通知从屏幕上消失。
注意:
在使用关于位置的通知时,一定要指定CLRegion的identifier,CLRegion靠identifier来唯一区别,当通知对象的区域对象的identifier为空时,该通知对象最终无法被安排到通知队列中。拥有含相同identifier的区域对象的通知对象只有一个会被安排到队列中。
CLLocationCoordinate2D locationCoordinate = CLLocationCoordinate2DMake(0, 0);
NSString *regionIdentifier = [NSString stringWithFormat:@"%g%g", locationCoordinate.latitude, locationCoordinate.longitude];
CLRegion *region = [[CLCircularRegion alloc] initWithCenter:coordinate radius:100 identifier:regionIdentifier];
注册远程通知
如果应用希望接收服务器发送的远程通知,需要通过调用UIApplication对象的registerForRemoteNotifications方法向APNs(Apple Push Notification service)注册。当注册成功时,应用会调用代理对象的application:didRegisterForRemoteNotificationsWithDeviceToken:方法并传入一个device token(二进制编码),该device token需要发送给发送远程通知的服务器。如果注册失败,则会调用代理对象的application:didFailToRegisterForRemoteNotificationsWithError:方法。注意,device token是可变的,所以每次应用启动都需要重新注册。若设备处于未联网状态,则以上两个方法都不会被调用。
处理本地和远程通知
当收到通知时,如果应用并没有在前台运行,那么根据用户的设置,系统可以通过显示提醒,应用图标加角标,声音以及其他的动作按钮来展示应用。当用户点击了自定义的动作按钮,应用代理对象会调用application:handleActionWithIdentifier:forRemoteNotification:completionHandler:
方法或者application:handleActionWithIdentifier:forLocalNotification:completionHandler:
方法。当用户点击默认的提醒栏时,如果应用未启动,那么会调用代理对象的application:didFinishLaunchingWithOptions:
方法,在options字典中,可以通过key值UIApplicationLaunchOptionsLocalNotificationKey获取触发应用启动的UILocalNotification对象,或者通过key值UIApplicationLaunchOptionsRemoteNotificationKey获取远程通知的userInfo(NSDictionary对象),如果触发应用启动的是远程通知,那么系统还会调用代理对象的application:didReceiveRemoteNotification:fetchCompletionHandler:
方法并且执行顺序先与application:didFinishLaunchingWithOptions:方法。当用户通过点击应用图标启动应用时,则无法获得任何有关通知的信息。当应用在前台运行时收到通知,则会调用代理对象的application:didReceiveLocalNotification:方法或application:didReceiveRemoteNotification:fetchCompletionHandler:方法,还有一个方法是application:didReceiveRemoteNotification:,该方法只有应用在前台时才会被调用,但若application:didReceiveRemoteNotification:fetchCompletionHandler:方法被实现则会替代该方法,其不会被调用。当应用在后台挂起时收到远程通知,如果此时Background Mode设置了远程通知,那么应用会被唤醒并在后台调用application:didReceiveRemoteNotification:fetchCompletionHandler:方法,但是有30秒的时间限制,另外如果设置中的后台刷新选项被关闭同时通知选项被设为不允许通知,则也不会调用该方法。
有关处理方法中的最后一个参数completionHandler,当执行完处理通知的代码后,一定要调用传入的block参数completionHandler,否则会使应用结束运行。在异步获取数据结束后,执行application:didReceiveRemoteNotification:fetchCompletionHandler:方法中的completionHandler时还要传入一个描述获取数据结果的参数(UIBackgroundFetchResult类型)。
使用通知处理(Notification Actions)
从iOS8开始,用户对于通知的处理除了默认的方式(点击横幅或提醒中的默认处理,锁屏时滑动通知)外,还可以自定义其他处理方式供用户选择。在横幅,锁屏状态或者通知中心中最多可添加两种自定义处理,在提醒框的选项中最多可添加4种自定义处理。使用自定义通知处理的第一步就是注册处理。
UIMutableUserNotificationAction *acceptAction = [[UIMutableUserNotificationAction alloc] init];
// 当用户收到通知并选择该处理,系统会将identifier传给应用代理对象的相应方法,以便判断用户选择了哪一项处理。
acceptAction.identifier = @"ACTION_ACCEPT";
// 处理按钮的标题。
acceptAction.title = @"Accept";
// 设置当用户点击处理按钮后,应用在前台运行还是后台运行,如果为后台模式,应用将获得一定的时间运行,若此时应用在前台(锁屏情况下),应用将继续保持在前台。若该处理不需要用户与界面交互则可用后台模式。
acceptAction.activationMode = UIUserNotificationActivationModeBackground;
// 设为YES则按钮背景色为红色,起强调作用。
acceptAction.destructive = NO;
// 该设置针对锁屏情况下,用户选择处理后是否需要输入密码,若activationMode为后台模式则输入密码不会解锁,只是执行处理,若为前台模式,则必须验证密码,无论该属性的值是什么。
acceptAction.authenticationRequired = YES;
UIMutableUserNotificationAction *maybeAction = [[UIMutableUserNotificationAction alloc] init];
maybeAction.identifier = @"ACTION_MAYBE";
maybeAction.title = @"Maybe";
maybeAction.activationMode = UIUserNotificationActivationModeBackground;
maybeAction.destructive = NO;
maybeAction.authenticationRequired = NO;
UIMutableUserNotificationAction *declineAction = [[UIMutableUserNotificationAction alloc] init];
declineAction.identifier = @"ACTION_DECLINE";
declineAction.title = @"Decline";
declineAction.activationMode = UIUserNotificationActivationModeBackground;
declineAction.destructive = YES;
declineAction.authenticationRequired = NO;
// 需要将定义的一组Action放入到一个category中,在推送通知设置该通知对应的category,当系统收到通知展示时通过category的identifier匹配到已注册的category,并将其中的action展示出来。
UIMutableUserNotificationCategory *inviteCategory = [[UIMutableUserNotificationCategory alloc] init];
inviteCategory.identifier = @"CATEGORY_INVITE";
// 设置category的action时对应两种context,default和minimal。default应用于展示4个action的地方,minimal应用于展示2个action的地方。若未指定minimal,则只能展示2个action的地方将展示default中的前两个。
[inviteCategory setActions:@[acceptAction, maybeAction, declineAction] forContext:UIUserNotificationActionContextDefault];
[inviteCategory setActions:@[acceptAction, declineAction] forContext:UIUserNotificationActionContextMinimal];
// 可以注册多个category,所以注册前要将所有category放入一个set中用于注册。
NSSet *categories = [NSSet setWithObjects:inviteCategory, nil];
UIUserNotificationType userNotificationTypes = UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound;
UIUserNotificationSettings *userNotificationSettings = [UIUserNotificationSettings settingsForTypes:userNotificationTypes categories:categories];
[[UIApplication sharedApplication] registerUserNotificationSettings:userNotificationSettings];
当推送通知时,只要设置了远程通知的key值category或者本地通知的category属性为已注册的category的identifier,那么当系统展示通知时,就会将相应处理按钮展示出来。最后,当用户点击了自定义的处理按钮,系统会调用应用代理对象的application:handleActionWithIdentifier:forRemoteNotification:completionHandler:方法或application:handleActionWithIdentifier:forLocalNotification:completionHandler:方法。在该方法中可以通过传入的action的Identifier来判断用户点击了哪一个action按钮,另还传入了通知对象。
网友评论
https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/IPhoneOSClientImp.html#//apple_ref/doc/uid/TP40008194-CH103-SW1
>If the default action button is tapped (on a device running iOS), the system launches the app and the app calls its delegate’s application:didFinishLaunchingWithOptions: method, passing in the notification payload (for remote notifications) or the local-notification object (for local notifications). Although application:didFinishLaunchingWithOptions: isn’t the best place to handle the notification, getting the payload at this point gives you the opportunity to start the update process before your handler method is called.
For remote notifications, the system also calls the application:didReceiveRemoteNotification:fetchCompletionHandler: method of the app delegate.
[-application:didReceiveRemoteNotification:fetchCompletionHandler:](https://developer.apple.com/reference/uikit/uiapplicationdelegate/1623013-application?language=objc)
但这个方法的介绍里没有提到会在这种情况下执行,我现在没有可以远程通知的应用来测试,所以没法给出明确答案,如果你有条件,还是要自己执行下这种情况,记得把结果告诉我哦,可能要改下文章了。。。