变更记录

序号 录入时间 录入人 备注
1 2015-04-23 Alfred Jiang -
2 2015-12-23 Alfred Jiang -

方案名称

通知 - 本地推送通知(Local Notification)的测试与实现

关键字

通知 \ 推送 \ UILocalNotification \ 本地推送 \ 本地通知 \ 闹钟

需求场景

  1. 需要实现类似闹铃功能的本地推送通知需求

参考链接

  1. GitHub - JRNLocalNotificationCenter
  2. CSDN - iOS本地推送与取消本地通知
  3. CSDN - 闹钟app小结(ios)
  4. 《本地和推送通知编程指南》

详细内容

1. JRNLocalNotificationCenter
  1. JRNLocalNotificationCenter.h ```objective-c // // JRNLocalNotificationCenter.h // DemoApp // // Created by jarinosuke on 7/27/13. // Copyright (c) 2013 jarinosuke. All rights reserved. //

import

import

extern NSString const JRNLocalNotificationHandlingKeyName; extern NSString const JRNApplicationDidReceiveLocalNotification;

typedef void (^JRNLocalNotificationHandler)(NSString key, NSDictionary userInfo);

@interface JRNLocalNotificationCenter : NSObject @property (nonatomic, copy) JRNLocalNotificationHandler localNotificationHandler;

  • (instancetype)defaultCenter;
  • (NSArray *)localNotifications;

//Handling

  • (void)didReceiveLocalNotificationUserInfo:(NSDictionary *)userInfo;

//Cancel

  • (void)cancelAllLocalNotifications;
  • (void)cancelLocalNotification:(UILocalNotification *)localNotification;
  • (void)cancelLocalNotificationForKey:(NSString *)key;

//Post on now

  • (UILocalNotification )postNotificationOnNowForKey:(NSString )key

                                         alertBody:(NSString *)alertBody;
    
  • (UILocalNotification )postNotificationOnNowForKey:(NSString )key

                                         alertBody:(NSString *)alertBody
                                          userInfo:(NSDictionary *)userInfo;
    
  • (UILocalNotification )postNotificationOnNowForKey:(NSString )key

                                         alertBody:(NSString *)alertBody
                                       alertAction:(NSString *)alertAction
                                         soundName:(NSString *)soundName
                                       launchImage:(NSString *)launchImage
                                          userInfo:(NSDictionary *)userInfo
                                        badgeCount:(NSUInteger)badgeCount
                                    repeatInterval:(NSCalendarUnit)repeatInterval;
    

//Post on specified date

  • (UILocalNotification )postNotificationOn:(NSDate )fireDate

                                   forKey:(NSString *)key
                                alertBody:(NSString *)alertBody;
    
  • (UILocalNotification )postNotificationOn:(NSDate )fireDate

                                   forKey:(NSString *)key
                                alertBody:(NSString *)alertBody
                                 userInfo:(NSDictionary *)userInfo;
    
  • (UILocalNotification )postNotificationOn:(NSDate )fireDate

                                   forKey:(NSString *)key
                                alertBody:(NSString *)alertBody
                                 userInfo:(NSDictionary *)userInfo
                               badgeCount:(NSInteger)badgeCount;
    
  • (UILocalNotification )postNotificationOn:(NSDate )fireDate

                                   forKey:(NSString *)key
                                alertBody:(NSString *)alertBody
                              alertAction:(NSString *)alertAction
                                soundName:(NSString *)soundName
                              launchImage:(NSString *)launchImage
                                 userInfo:(NSDictionary *)userInfo
                               badgeCount:(NSUInteger)badgeCount
                           repeatInterval:(NSCalendarUnit)repeatInterval;
    

@end


2. JRNLocalNotificationCenter.m
```objective-c
//
//  JRNLocalNotificationCenter.m
//  DemoApp
//
//  Created by jarinosuke on 7/27/13.
//  Copyright (c) 2013 jarinosuke. All rights reserved.
//

#import "JRNLocalNotificationCenter.h"

NSString *const JRNLocalNotificationHandlingKeyName = @"JRN_KEY";
NSString *const JRNApplicationDidReceiveLocalNotification = @"JRNApplicationDidReceiveLocalNotification";

@interface JRNLocalNotificationCenter()
@property (nonatomic) NSMutableDictionary *localPushDictionary;
@property (nonatomic) BOOL checkRemoteNotificationAvailability;
@end

static JRNLocalNotificationCenter *defaultCenter;

@implementation JRNLocalNotificationCenter

+ (instancetype)defaultCenter
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        defaultCenter = [JRNLocalNotificationCenter new];
        defaultCenter.localPushDictionary = [NSMutableDictionary new];
        [defaultCenter loadScheduledLocalPushNotificationsFromApplication];
        defaultCenter.checkRemoteNotificationAvailability = NO;
        defaultCenter.localNotificationHandler = nil;
    });
    return defaultCenter;
}

- (void)loadScheduledLocalPushNotificationsFromApplication
{
    NSArray *scheduleLocalPushNotifications = [[UIApplication sharedApplication] scheduledLocalNotifications];
    for (UILocalNotification *localNotification in scheduleLocalPushNotifications) {
        if (localNotification.userInfo[JRNLocalNotificationHandlingKeyName]) {
            [self.localPushDictionary setObject:localNotification forKey:localNotification.userInfo[JRNLocalNotificationHandlingKeyName]];
        }
    }
}

- (NSArray *)localNotifications
{
    return [[NSArray alloc] initWithArray:[self.localPushDictionary allValues]];
}

- (void)didReceiveLocalNotificationUserInfo:(NSDictionary *)userInfo
{
    NSString *key = userInfo[JRNLocalNotificationHandlingKeyName];
    if (!key) {
        return;
    }
    [self.localPushDictionary removeObjectForKey:key];

    [[NSNotificationCenter defaultCenter] postNotificationName:JRNApplicationDidReceiveLocalNotification
                                                        object:nil
                                                      userInfo:userInfo];

    if (self.localNotificationHandler) {
        self.localNotificationHandler(userInfo[JRNLocalNotificationHandlingKeyName], userInfo);
    }
}

- (void)cancelAllLocalNotifications
{
    [[UIApplication sharedApplication] cancelAllLocalNotifications];
    [self.localPushDictionary removeAllObjects];
}

- (void)cancelLocalNotification:(UILocalNotification *)localNotification
{
    if (!localNotification) {
        return;
    }

    [[UIApplication sharedApplication] cancelLocalNotification:localNotification];
    if (localNotification.userInfo[JRNLocalNotificationHandlingKeyName]) {
        [self.localPushDictionary removeObjectForKey:localNotification.userInfo[JRNLocalNotificationHandlingKeyName]];
    }
}

- (void)cancelLocalNotificationForKey:(NSString *)key
{
    if (!self.localPushDictionary[key]) {
        return;
    }

    UILocalNotification *localNotification = self.localPushDictionary[key];
    [[UIApplication sharedApplication] cancelLocalNotification:localNotification];
    [self.localPushDictionary removeObjectForKey:key];
}

#pragma mark -
#pragma mark - Post on now

- (UILocalNotification *)postNotificationOnNowForKey:(NSString *)key
                                           alertBody:(NSString *)alertBody
{
    return [self postNotificationOnNow:YES
                              fireDate:nil
                                forKey:key
                             alertBody:alertBody
                           alertAction:nil
                             soundName:nil
                           launchImage:nil
                              userInfo:nil
                            badgeCount:0
                        repeatInterval:0];
}

- (UILocalNotification *)postNotificationOnNowForKey:(NSString *)key
                          alertBody:(NSString *)alertBody
                           userInfo:(NSDictionary *)userInfo
{
    return [self postNotificationOnNow:YES
                              fireDate:nil
                                forKey:key
                             alertBody:alertBody
                           alertAction:nil
                             soundName:nil
                           launchImage:nil
                              userInfo:userInfo
                            badgeCount:0
                        repeatInterval:0];
}

- (UILocalNotification *)postNotificationOnNowForKey:(NSString *)key
                          alertBody:(NSString *)alertBody
                           userInfo:(NSDictionary *)userInfo
                         badgeCount:(NSInteger)badgeCount
{
    return [self postNotificationOnNow:YES
                              fireDate:nil
                                forKey:key
                             alertBody:alertBody
                           alertAction:nil
                             soundName:nil
                           launchImage:nil
                              userInfo:userInfo
                            badgeCount:badgeCount
                        repeatInterval:0];
}

- (UILocalNotification *)postNotificationOnNowForKey:(NSString *)key
                          alertBody:(NSString *)alertBody
                        alertAction:(NSString *)alertAction
                          soundName:(NSString *)soundName
                        launchImage:(NSString *)launchImage
                           userInfo:(NSDictionary *)userInfo
                         badgeCount:(NSUInteger)badgeCount
                     repeatInterval:(NSCalendarUnit)repeatInterval
{
    return [self postNotificationOnNow:YES
                              fireDate:nil
                                forKey:key
                             alertBody:alertBody
                           alertAction:alertAction
                             soundName:soundName
                           launchImage:launchImage
                              userInfo:userInfo
                            badgeCount:badgeCount
                        repeatInterval:repeatInterval];
}

#pragma mark -
#pragma mark - Post on specified date

- (UILocalNotification *)postNotificationOn:(NSDate *)fireDate
                    forKey:(NSString *)key
                 alertBody:(NSString *)alertBody
{
    return [self postNotificationOnNow:NO
                              fireDate:fireDate
                                forKey:key
                             alertBody:alertBody
                           alertAction:nil
                             soundName:nil
                           launchImage:nil
                              userInfo:nil
                            badgeCount:0
                        repeatInterval:0];
}

- (UILocalNotification *)postNotificationOn:(NSDate *)fireDate
                    forKey:(NSString *)key
                 alertBody:(NSString *)alertBody
                  userInfo:(NSDictionary *)userInfo
{
    return [self postNotificationOnNow:NO
                              fireDate:fireDate
                                forKey:key
                             alertBody:alertBody
                           alertAction:nil
                             soundName:nil
                           launchImage:nil
                              userInfo:userInfo
                            badgeCount:0
                        repeatInterval:0];
}

- (UILocalNotification *)postNotificationOn:(NSDate *)fireDate
                    forKey:(NSString *)key
                 alertBody:(NSString *)alertBody
                  userInfo:(NSDictionary *)userInfo
                badgeCount:(NSInteger)badgeCount
{
    return [self postNotificationOnNow:NO
                              fireDate:fireDate
                                forKey:key
                             alertBody:alertBody
                           alertAction:nil
                             soundName:nil
                           launchImage:nil
                              userInfo:userInfo
                            badgeCount:badgeCount
                        repeatInterval:0];
}

- (UILocalNotification *)postNotificationOn:(NSDate *)fireDate
                    forKey:(NSString *)key
                 alertBody:(NSString *)alertBody
               alertAction:(NSString *)alertAction
                 soundName:(NSString *)soundName
               launchImage:(NSString *)launchImage
                  userInfo:(NSDictionary *)userInfo
                badgeCount:(NSUInteger)badgeCount
            repeatInterval:(NSCalendarUnit)repeatInterval
{
    return [self postNotificationOnNow:NO
                              fireDate:fireDate
                                forKey:key
                             alertBody:alertBody
                           alertAction:alertAction
                             soundName:soundName
                           launchImage:launchImage
                              userInfo:userInfo
                            badgeCount:badgeCount
                        repeatInterval:repeatInterval];
}

- (UILocalNotification *)postNotificationOnNow:(BOOL)presentNow
                     fireDate:(NSDate *)fireDate
                       forKey:(NSString *)key
                    alertBody:(NSString *)alertBody
                  alertAction:(NSString *)alertAction
                    soundName:(NSString *)soundName
                  launchImage:(NSString *)launchImage
                     userInfo:(NSDictionary *)userInfo
                   badgeCount:(NSUInteger)badgeCount
               repeatInterval:(NSCalendarUnit)repeatInterval;
{
    if (self.localPushDictionary[key]) {
        //same key already exists
        return self.localPushDictionary[key];
    }

    UILocalNotification *localNotification = [[UILocalNotification alloc] init];
    if (!localNotification) {
        return nil;
    }

    UIRemoteNotificationType notificationType = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
    if (self.checkRemoteNotificationAvailability && notificationType == UIRemoteNotificationTypeNone) {
        return nil;
    }

    BOOL needsNotify = NO;

    //Alert
    if (self.checkRemoteNotificationAvailability && (notificationType & UIRemoteNotificationTypeAlert) != UIRemoteNotificationTypeAlert) {
        needsNotify = NO;
    } else {
        needsNotify = YES;
    }
    //add key name for handling it.
    NSMutableDictionary *userInfoAddingHandlingKey = [NSMutableDictionary dictionaryWithDictionary:userInfo];
    [userInfoAddingHandlingKey setObject:key forKey:JRNLocalNotificationHandlingKeyName];
    localNotification.userInfo         = userInfoAddingHandlingKey;
    localNotification.alertBody        = alertBody;
    localNotification.alertAction      = alertAction;
    localNotification.alertLaunchImage = launchImage;
    localNotification.repeatInterval   = repeatInterval;

    //Sound
    if (self.checkRemoteNotificationAvailability && (notificationType & UIRemoteNotificationTypeSound) != UIRemoteNotificationTypeSound) {
        needsNotify = NO;
    } else {
        needsNotify = YES;
    }
    if (soundName) {
        localNotification.soundName = soundName;
    } else {
        localNotification.soundName = UILocalNotificationDefaultSoundName;
    }

    //Badge
    if (self.checkRemoteNotificationAvailability && (notificationType & UIRemoteNotificationTypeBadge) != UIRemoteNotificationTypeBadge) {
    } else {
        localNotification.applicationIconBadgeNumber = badgeCount;
    }

    if (needsNotify) {
        if (presentNow && !fireDate) {
            [[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];
        } else {
            localNotification.fireDate = fireDate;
            localNotification.timeZone = [NSTimeZone defaultTimeZone];
            [[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
        }
        [self.localPushDictionary setObject:localNotification forKey:key];
        return localNotification;
    } else {
        return nil;
    }
}
@end
2. 使用举例:REXLocalNotificationManager
//
//  REXLocalNotificationManager.swift
//  REX
//
//  Created by Alfred Jiang on 4/23/15.
//  Copyright (c) 2015 REX. All rights reserved.
//

import UIKit

class REXLocalNotificationManager: NSObject {

    var keysForAuctions : NSMutableArray = []

    class var sharedInstance : REXLocalNotificationManager {
        struct Static {
            static var onceToken : dispatch_once_t = 0
            static var instance : REXLocalNotificationManager? = nil
        }
        dispatch_once(&Static.onceToken) {
            Static.instance = REXLocalNotificationManager()
        }
        return Static.instance!
    }

    func cancelAllAuctionsLocalNotification()
    {
        for key in keysForAuctions
        {
            JRNLocalNotificationCenter.defaultCenter().cancelLocalNotificationForKey(key as String)
        }
    }

    func addNotificationsForList(auctions : NSArray)
    {
        self.cancelAllAuctionsLocalNotification()

//        let keyEnded : String = "Test_Auction_End_Time"
//        self.addPost(keyEnded, aTime: NSDate(timeIntervalSinceNow:30.0),aBody: "Test Auction ended")

        for obj in auctions
        {
            var aAuction = obj as Auction

            if aAuction.aAuctionStatus == "0"   //进行中
            {
                let keyEnded : String = "Auction_End_Time_\(aAuction.aId)"
                self.addPost(keyEnded, aTime: aAuction.endDateValue(0),aBody: "Auction ended")

                let keyEnded_1_Hour : String = "Auction_End_1_Hour_Time_\(aAuction.aId)"
                self.addPost(keyEnded_1_Hour, aTime: aAuction.endDateValue(60 * 60),aBody: "\(aAuction.aName) will end in 1 hour")

                let keyEnded_3_Hour : String = "Auction_End_3_Hour_Time_\(aAuction.aId)"
                self.addPost(keyEnded_3_Hour, aTime: aAuction.endDateValue(3 * 60 * 60),aBody: "\(aAuction.aName) will end in 3 hours")

                let keyEnded_24_Hour : String = "Auction_End_24_Hour_Time_\(aAuction.aId)"
                self.addPost(keyEnded_24_Hour, aTime: aAuction.endDateValue(24 * 60 * 60),aBody: "\(aAuction.aName) will end in 24 hours")

            }
            else if aAuction.aAuctionStatus == "1"  //未开始
            {
                let keyEnded : String = "Auction_Start_Time_\(aAuction.aId)"
                self.addPost(keyEnded, aTime: aAuction.startDateValue(0),aBody: "Auction start")

                let keyEnded_1_Hour : String = "Auction_Start_1_Hour_Time_\(aAuction.aId)"
                self.addPost(keyEnded_1_Hour, aTime: aAuction.startDateValue(60 * 60),aBody: "\(aAuction.aName) will start 1 hour later")

                let keyEnded_3_Hour : String = "Auction_Start_3_Hour_Time_\(aAuction.aId)"
                self.addPost(keyEnded_3_Hour, aTime: aAuction.startDateValue(3 * 60 * 60),aBody: "\(aAuction.aName) will start 3 hours later")

                let keyEnded_24_Hour : String = "Auction_Start_24_Hour_Time_\(aAuction.aId)"
                self.addPost(keyEnded_24_Hour, aTime: aAuction.startDateValue(24 * 60 * 60),aBody: "\(aAuction.aName) will start 24 hours later")
            }
        }
    }

    func addPost(aKey : NSString, aTime : NSDate, aBody : NSString)
    {
        let dateNow : NSDate = NSDate()
        let overTime : Bool = aTime.timeIntervalSince1970 - dateNow.timeIntervalSince1970 > 0

        if !overTime
        {
            return
        }

        JRNLocalNotificationCenter.defaultCenter().postNotificationOn(aTime, forKey: aKey, alertBody: aBody)
        self.keysForAuctions.addObject(aKey)
    }
}
3. 接受通知
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
    println("Local Notification userInfo==\(notification.userInfo)")
    let body : String = notification.alertBody! as String
    var alertView : UIAlertView = UIAlertView(title: "", message: body, delegate: nil, cancelButtonTitle: "OK")
    alertView.show()
}
4. 闹钟实现注意事项

使用 LocalNotification 实现闹钟时需要注意以下事项

  1. 本地的 apns 最多只能设置 64 个,如果超过了的话,以最新设置的 64 个 apns 为准,最早设置的会被忽略,这个当初没有仔细看文档,导致了不该出现的 bug ,后文会讲到

  2. 当在后台收到 apns 时,发出的 apns 声音不能超过 30s,如果指定的 apns 的 sound 文件的实际播放声音超过 30s,则只播放 30s

  3. 删除 app 重新下载后,原先设置的本地 apns 还在,这个当时也没注意到,吃了大亏,后来一查才知,本地的 apns 是系统进程,不会随着你的 app 删除而消失,不过如果你的 app 被删除,原先存在的 apns 是不会被触发的!

  4. 闹钟的功能是以叫醒人为主,但是很遗撼,每个 UILocalization 的声音最多只能响一次, app 推出后,不少用户也抱怨此功能不人性化,现在我们的做法是将 64 个本地的 apns 全部分配给最近的那个闹钟,比如我设置在 13:00 响铃,声音的实际播放时间为 30s ,这样每隔 30s 推送一次本地的 apns ,这样 64 次就足以将用户叫醒(当然如果在此期间用户没将手机带在身边,那就悲剧了,下次就不会再响了,不过此概率极小),如果用户点击 apns 进入 app ,那就 cancel 全部的 UILocalization ,然后再重新设置最新的闹钟,将 64 个 apns 全部赋予此闹钟

  5. 计算最近的闹钟有一个问题,如何判定这个最近?比如有的闹钟是今天,有的闹钟是昨天(但此闹钟设置为每天响铃),我的做法是,将所有重复闹钟响铃时间的年月日全部换成今天的年月日(时分秒不变),然后再与 today[NSDate date] 进行比较,如果比 today 早,则将响铃时间设置为之后最近的会响铃的那一天,这样设置之后,就可以获取最近的闹钟时间了

  6. 何时重新设置这个闹钟时间,通常的做法是在收到 UILocalization (即 application:didReceiveLocalNotification: )时重新设置,但是这有一个问题,此方法只有当用户点了 apns 的提示时(或在锁屏状态下滑动进入 app )时才能触发,也就是说,如果在后台收到了 apns ,但用户是通过点击 app 的 icon 进入 app 的话,此方法是不会触发的!这也是苹果的一个 bug ,所以我的做法是在 application:didReceiveLocalNotification: 此方法里触发,也在 applicationdidbecomeactive 方法里触发!这样就可以解决此问题了

效果图

(无)

备注

(无)

results matching ""

    No results matching ""