本地推送通知机制和实现步骤详解

/ 0评 / 0

如何发送本地推送通知

推送通知也属于UI的一部分,所以推送通知对象是以UI开头。

将发送通知的代码方法控制器的-touchesBegan: withEvent: 中测试比较合适,如果放到viewDidLoad方法,用户的注册请求还没有完成方法就调用了。

创建本地通知对象

//  创建本地通知对象
UILocalNotification *ln = [[UILocalNotification alloc] init];

设置本地通知属性

//  1.设置通知的内容(如果此属性不设置是不会发送通知的)
ln.alertBody = @"小明,你妈叫你回家吃饭了!";
//  2.设置通知触发的开始时间
ln.fireDate = [NSDate dateWithTimeIntervalSinceNow:3];
//  3.设置重复通知的时间,间隔
ln.repeatInterval = NSCalendarUnitSecond;
//  4.设置重复执行使用日历(用户设置的日历)
ln.repeatCalendar = [NSCalendar  currentCalendar];
//    NSString * const NSGregorianCalendar; 公历
//    NSString * const NSChineseCalendar; 农历
//    ln.repeatCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSChineseCalendar];
//  5.设置应用图标右上角的数字
ln.applicationIconBadgeNumber = 3;
//  6.设置点击推送通知进入界面的时候显示,加载图片
ln.alertLaunchImage = @"";
//  7 设置通知的音效(只有真机有效)
local.soundName = UILocalNotificationDefaultSoundName;
//  8 设置一些额外信息
local.userInfo = @{@"QQ":@"55555",@"info":@"约了没"};

//  iOS8.0 以后新增属性
//  ************************************
//  1.设置区域,进入或离开某个区域的时候触发
//    CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(40.1,106.1);
//    ln.region = [[CLCircularRegion alloc] initWithCenter:coordinate radius:10.0 identifier:@"ab"];
//  2.设置进入或离开某个区域只执行一次
//    ln.regionTriggersOnce = YES;
//  ***************************************

//  iOS8.2 新增属性
//    ln.alertTitle = @"通知标题";

使用应用 UIApplication 调度本地通知

//  让应用调度通知
[[UIApplication sharedApplication] scheduleLocalNotification:ln];

本地推送通知页面跳转

无论应用是在前台,后台还是已经关闭都能如期接收到本地通知,但是当用户点击通知进入应用的时候,我们需要根据不同情况,进行处理

AppDelegate本地通知代理方法

/**
 *  一旦接收到本地通知就会调用该方法
 *  注意这个方法:应用在前台也会调用
 *  @param application  应用
 *  @param notification 本地通知对象
 */
- (void) application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
    
    //  当应用在前台时候什么都不做
    if (application.applicationState == UIApplicationStateActive) {
        return;
    }
    
    //  当应用不再前台的时候才去跳转,这样用户体检更好
    UITabBarController *tbVc = (UITabBarController *)application.keyWindow.rootViewController;
    tbVc.selectedIndex = 1;
    
}

但是当应用已经退出的时候,点击通知进入本应用时候,不在调用application:didReceiveLocalNotification:的代理方法。难道当应用退出后,用户再进入应用我们就不再跳转指定界面了吗?

为了更好用户体验,我此时也应该让应用跳转到指定的界面,怎么才能实现这个功能呢?

我们知道当应用程序启动的时候一定会调用application: didFinishLaunchingWithOptions:的代理方法,在这里我们能拿到本地通知信息,也可以跳转相应的界面。

//  如果是点击本地通知进来的那么launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]就会有内容
if(launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]) {
    //页面跳转
    UITabBarController *tbVc = (UITabBarController *)self.window.rootViewController;
    tbVc.selectedIndex = 1;
}

一个应用可能要各种不同的通but知,点击不同的通知可以跳转不同界面,这个有该怎么做呢?

在发送通知时候,设置userInfo属性

//  7.设置应用信息
ln.userInfo = @{@"pageKey":@"friend"};

在AppDelegate本地通知代理方法中进行判断

/**
 *  一旦接收到本地通知就会调用该方法
 *  注意这个方法:应用在前台也会调用
 *  @param application  应用
 *  @param notification 本地通知对象
 */
- (void) application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
    
    //  当应用在前台时候什么都不做
    if (application.applicationState == UIApplicationStateActive) {
        return;
    }
    //  当应用不再前台的时候才去跳转,这样用户体检更好
    //  获取tabBarController
    UITabBarController *tbVc = (UITabBarController *)self.window.rootViewController;
    
    
    //  获取用户设置的跳转页
    NSString *page = notification.userInfo[@"pageKey"];
    //  如果是朋友圈
    if ([page isEqualToString:@"session"]) {
        tbVc.selectedIndex = 1;
    }else{
        //       否则跳转到好友
        tbVc.selectedIndex = 0;
    }
}

测试launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]中的内容

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    //  获取UIApplicationLaunchOptionsLocalNotificationKey对应内容
    id obj = launchOptions[UIApplicationLaunchOptionsLocalNotificationKey];
    //  获取控制器(注意此时需要通过self.window,通过application.keyWindow无法获取到,因为此时的window还没有成为keyWindow)
    UITabBarController *tbVc = (UITabBarController *)self.window.rootViewController;
    //  获取索引为0的控制(注意此时tbVc.selectedViewController为nil)
    UIViewController *vc = tbVc.viewControllers[0];
    
    //  创建一个文本
    UILabel *label = [[UILabel alloc] init];
    label.backgroundColor = [UIColor brownColor];
    //  设置text为UIApplicationLaunchOptionsLocalNotificationKey对应的内容
    label.text =  [obj description] ;
    label.frame = CGRectMake(10, 100, 300, 400);
    label.numberOfLines = 0;
    //  添加到控制器的View上
    [vc.view addSubview:label];
    
    
    return YES;
}

显示结果:

336729BE-B7DA-4B83-9515-E10864F3FADE

我们从中可以出他是一个UILocalNotification对象

所以我们取出UILocalNotification对象,剩下的做法与接收到本地通知代理方法中处理相同,所以我们把它提取为一个公用的方法

/**
 *  根据通知跳转到不同页面
 */
- (void) jumpToPageWithLocalNotification:(UILocalNotification *) notification {
    //  获取tabBarController
    UITabBarController *tbVc = (UITabBarController *)self.window.rootViewController;
    
    //  获取用户设置的跳转页
    NSString *page = notification.userInfo[@"pageKey"];
    //  如果是朋友圈
    if ([page isEqualToString:@"session"]) {
        tbVc.selectedIndex = 0;
    }else{
        //       否则跳转到好友
        tbVc.selectedIndex = 1;
    }
}

在didReceiveLocalNotification方法中

/**
 *  一旦接收到本地通知就会调用该方法
 *  注意这个方法:应用在前台也会调用
 *  @param application  应用
 *  @param notification 本地通知对象
 */
- (void) application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
    
    //  当应用在前台时候什么都不做
    if (application.applicationState == UIApplicationStateActive) {
        return;
    }
    //  当应用不再前台的时候才去跳转,这样用户体检更好
    [self jumpToPageWithLocalNotification:notification];
}

在didFinishLaunchingWithOptions方法中

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    //  如果是点击本地通知进来的那么launchOptions[UIApplicationLaunchOptionsLocalNotificationKey]就会有内容
    UILocalNotification *notifcation = launchOptions[UIApplicationLaunchOptionsLocalNotificationKey];
    //  如果存在通知
    if(notifcation){
        [self jumpToPageWithLocalNotification:notifcation];
    }
    
    return YES;
}

iOS8的不同点

你如果把上面的程序运行在iOS8上,会爆出如下错误

Attempting to schedule a local notification {fire date = Monday, July 13, 2015 at 9:02:25 AM China Standard Time, time zone = (null), repeat interval = 0, repeat count = UILocalNotificationInfiniteRepeatCount, next fire date = Monday, July 13, 2015 at 9:02:25 AM China Standard Time, user info = { pageKey = friend; }} with an alert but haven't received permission from the user to display alerts

也就是在iOS8上要发送本地通知需要 请求用户权限 如何请求用户权限呢?

一般在新版有变化的地方,在头文件中都会有相应的说明,所以点击到scheduleLocalNotification:方法中,看看有没有我们需要信息。

//  让应用调度通知
[[UIApplication sharedApplication] scheduleLocalNotification:ln];
// In iOS 8.0 and later, your application must register for user notifications using -[UIApplication registerUserNotificationSettings:] before being able to schedule and present UILocalNotifications

意思就是说:在iOS8.0以后,在调度通知之前你需要使用UIApplication的对象方法registerUseNotificationSetting:来请求用户授权。

这种请求权限的代码一般放在didFinishLaunchingWithOptions:方法中,在用户不卸载的情况下,只需要请求一次,下次在运行就不用请求了!

//  1.如果是iOS8请求用户权限
if ([UIDevice currentDevice].systemVersion.doubleValue >= 8.0) {
    
    /*
     UIUserNotificationType:
     
     UIUserNotificationTypeBadge   = 1 << 0, // 接收到通知可更改程序的应用图标
     UIUserNotificationTypeSound   = 1 << 1, // 接收到通知可播放声音
     UIUserNotificationTypeAlert   = 1 << 2, // 接收到通知课提示内容
     如果你需要使用多个类型,可以使用 "|" 来连接
     */
    
    //      向用户请求通知权限
    //      categories暂时传入nil
    UIUserNotificationSettings *setting = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert categories:nil];
    
    [application registerUserNotificationSettings:setting];
}

运行程序

21E244DC-087A-4679-9D62-BFDEC648672C

测试点击通知,进入应用也没问题。

接下来,我们说说-[UIUserNotificationSettings settingsForTypes:categories:] 中的 categories

categories可以让我们发送通知之前预定义一些通知也就是通知上可以显示按钮,他需要是一个装有UIUserNotificationCategory类的对象的NSSet的对象.。但是官方推荐我们使用它的子类UIMutableUserNotificationCategory,来动态的添加通知的行为按钮,iOS8支持前台和后台的两种行为。

通知Action按钮以长条展示如图

notifcation_action_button

通知Action按钮以AlertView展示如图

notification_action_allertview

注册分类,并在分类中添加不同的行为 由于注册用户通知设置代码量比较大我们实现一个新的方法registerUserNotification

- (void)registerUserNotification {
    //      向用户请求通知权限
    /*
     UIUserNotificationType:用户通知的类型
     
     UIUserNotificationTypeBadge   = 1 << 0, // 接收到通知可更改程序的应用图标
     UIUserNotificationTypeSound   = 1 << 1, // 接收到通知可播放声音
     UIUserNotificationTypeAlert   = 1 << 2, // 接收到通知课提示内容
     如果你需要使用多个类型,可以使用 "|" 来连接
     */
    //  1.设置用户通知权限类型
    UIUserNotificationType types = UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert;
    
    
    //  2.创建通知的行为按钮
    
    //  2.1创建第一个行为
    UIMutableUserNotificationAction *action1 = [[UIMutableUserNotificationAction alloc] init];
    //  2.1.1 设置行为的唯一标示
    action1.identifier = UIMutableUserNotificationActionBackground;
    //  2.1.2 设置通知按钮的的标题
    action1.title = @"后台";
    //      以什么样模式运行应用
    //        UIUserNotificationActivationModeForeground, // 当应用在前台的时候触发
    //        UIUserNotificationActivationModeBackground  // 即使应用不在前台也触发
    action1.activationMode = UIUserNotificationActivationModeBackground;
    //  2.1.3 是否只有锁屏的锁屏状态下才能显示
    action1.authenticationRequired = NO;
    //  2.1.4 按钮的性质
    action1.destructive = NO;
    
    //  2.1创建第一个行为
    UIMutableUserNotificationAction *action2 = [[UIMutableUserNotificationAction alloc] init];
    //  2.1.1 设置行为的唯一标示
    action2.identifier =UIMutableUserNotificationActionForeground;
    //  2.1.2 设置通知按钮的的标题
    action2.title = @"前台";
    //      以什么样模式运行应用
    //        UIUserNotificationActivationModeForeground, // 当应用在前台的时候触发
    //        UIUserNotificationActivationModeBackground  // 即使应用不在前台也触发
    action2.activationMode = UIUserNotificationActivationModeForeground;
    //  2.1.3 用户必须输入密码才能执行
    action2.authenticationRequired = YES;
    //  2.1.4 按钮的性质(没有效果)
    action2.destructive = YES;
    
    
    
    //  3.创建用户通知分类
    UIMutableUserNotificationCategory *category = [[UIMutableUserNotificationCategory  alloc]  init];
    //  3.1 设置类别的唯一标识
    category.identifier = @"myCategory";
    //  3.2 设置通知的按钮
    //    Context:
    //        UIUserNotificationActionContextDefault,  //默认上下文(情景)下的英文(通常都是)
    //        UIUserNotificationActionContextMinimal   //通知内容区域受限情况下内容
    [category   setActions:@[action1,action2] forContext:UIUserNotificationActionContextDefault];
    
    
    //  4.创建用户通知的设置信息
    UIUserNotificationSettings *setting = [UIUserNotificationSettings settingsForTypes:types categories:[NSSet setWithObject:category]];
    
    //  注册设置
    [[UIApplication sharedApplication] registerUserNotificationSettings:setting];
}

在发送本地推送通知时候指定通知的分类标示

//  9.设置通知的类别
ln.category = @"myCategory";

监听点击通知按钮的行为,在AppDelegate中实现监听通知按钮点击方法

/**
 *  当用户点击通知上定制的按钮执行的行为(注意:不点击行为按钮,不会进入该方法)
 *
 *  @param application       应用
 *  @param identifier        行为标识符
 *  @param notification      本地通知
 *  @param completionHandler 完成回调
 */
- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification completionHandler:(void (^)())completionHandler {
//  处理不同行为
    if ([identifier isEqualToString:UIMutableUserNotificationActionBackground]) {
        NSLog(@"后台运行程序");
    }else if ([identifier isEqualToString:UIMutableUserNotificationActionForeground]) {
        NSLog(@"前台运行程序");
    }else{
        NSLog(@"其他");
    }
    /**
       You should call the completion handler as soon as you've finished handling the action.
       当任务处理完毕时候,你应该尽快的调用completion的block.
     */

// 在当任务完成的时候,调用任务完成的block
    completionHandler();
}

iOS8 基于位置的本地通知

基于位置的本地通知就是当用户进入或离开某个区域,就会给该用户发送一条本地通知,基于位置的本地通知只有在iOS8.0以后才能使用。

实现步骤:

在- (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions 方法中注册通知权限

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    if ([application respondsToSelector:@selector(registerUserNotificationSettings:)]) {

        UIUserNotificationType types = UIUserNotificationTypeBadge|UIUserNotificationTypeAlert|UIUserNotificationTypeSound;

        UIUserNotificationSettings *setting = [UIUserNotificationSettings settingsForTypes:types categories:nil];

        [application registerUserNotificationSettings:setting];
    }

    return YES;
}

在发送通知之前要请求使用用户的位置权限,在info.plist增加 NSLocationWhenInUseUsageDescription 的Key,类型为String,值填写需要提示内容。

定一个 CLLocationManager 类型的属性

@property (nonatomic, strong) CLLocationManager *locManager;

通过懒加载的方式其进行初始化并设置其代理

// 懒加载位置管理器对象
 - (CLLocationManager *)locManager {
     if (_locManager == nil) {
         _locManager = [[CLLocationManager alloc] init];
         _locManager.delegate = self;
     }
     return _locManager;
}

发送基于位置的推送通知之前首先判断用户是否已经拥有了访问位置的权限。如果如果没有请求权限,如果已经有就直接发送推送通知。

- (IBAction)baseLocationNotification {
    // 请求注册权限
    if([CLLocationManager authorizationStatus] != kCLAuthorizationStatusAuthorizedWhenInUse){
        //请求使用用户位置权限
        [self.locManager requestWhenInUseAuthorization];
    } else {
        // 发送基于位置本地通知
        [self scheduleBaseLocationNotification];
    }
}

// 监听用户授权的改变
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
    if (status == kCLAuthorizationStatusAuthorizedWhenInUse) {
        // 发送基于位置通知
        [self scheduleBaseLocationNotification];
    }
}

实现发送基于位置的通知方法

- (void) scheduleBaseLocationNotification {
  //  创建本地通知对象  
      UILocalNotification *loc = [[UILocalNotification alloc] init];  
  //  设置通知提示内容
      loc.alertBody = @"欢迎来到传智博客";
  //  创建经纬度坐标
      CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(23.139009,113.363798);
  //  设置触发的区域,当用户进入或离开这个区域的时候就会触发
          loc.region = [[CLCircularRegion alloc] initWithCenter:coordinate radius:100 identifier:@"loc"];
      //  设置是否只触发一次
      //  YES 表示只触发一次(用户进入或离开这个区域的时候触发一次)  NO 表示可以(用户每次进入或离开这个区域的时候都会触发)
          loc.regionTriggersOnce = NO;
      //  调度通知
          [[UIApplication sharedApplication] scheduleLocalNotification:loc];

}

说明:

1.基于位置本地通知,提醒做不到非常精确他依赖与GPS和周围的信号基站。

2.基于位置的本地通知,只有真机才能测试。

3.基于位置的本地通知,无需编程人员主动获取用户位置,我们添加应用调度中,如果用户手机打开了定位,系统会根据用户当前的位置来决定是否要给该用户发送通知。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注