变更记录
序号 | 录入时间 | 录入人 | 备注 |
---|---|---|---|
1 | 2015-04-23 | Alfred Jiang | - |
2 | 2015-12-23 | Alfred Jiang | - |
方案名称
通知 - 本地推送通知(Local Notification)的测试与实现
关键字
通知 \ 推送 \ UILocalNotification \ 本地推送 \ 本地通知 \ 闹钟
需求场景
- 需要实现类似闹铃功能的本地推送通知需求
参考链接
详细内容
1. JRNLocalNotificationCenter
- 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 实现闹钟时需要注意以下事项
本地的 apns 最多只能设置 64 个,如果超过了的话,以最新设置的 64 个 apns 为准,最早设置的会被忽略,这个当初没有仔细看文档,导致了不该出现的 bug ,后文会讲到
当在后台收到 apns 时,发出的 apns 声音不能超过 30s,如果指定的 apns 的 sound 文件的实际播放声音超过 30s,则只播放 30s
删除 app 重新下载后,原先设置的本地 apns 还在,这个当时也没注意到,吃了大亏,后来一查才知,本地的 apns 是系统进程,不会随着你的 app 删除而消失,不过如果你的 app 被删除,原先存在的 apns 是不会被触发的!
闹钟的功能是以叫醒人为主,但是很遗撼,每个 UILocalization 的声音最多只能响一次, app 推出后,不少用户也抱怨此功能不人性化,现在我们的做法是将 64 个本地的 apns 全部分配给最近的那个闹钟,比如我设置在 13:00 响铃,声音的实际播放时间为 30s ,这样每隔 30s 推送一次本地的 apns ,这样 64 次就足以将用户叫醒(当然如果在此期间用户没将手机带在身边,那就悲剧了,下次就不会再响了,不过此概率极小),如果用户点击 apns 进入 app ,那就 cancel 全部的 UILocalization ,然后再重新设置最新的闹钟,将 64 个 apns 全部赋予此闹钟
计算最近的闹钟有一个问题,如何判定这个最近?比如有的闹钟是今天,有的闹钟是昨天(但此闹钟设置为每天响铃),我的做法是,将所有重复闹钟响铃时间的年月日全部换成今天的年月日(时分秒不变),然后再与 today[NSDate date] 进行比较,如果比 today 早,则将响铃时间设置为之后最近的会响铃的那一天,这样设置之后,就可以获取最近的闹钟时间了
何时重新设置这个闹钟时间,通常的做法是在收到 UILocalization (即 application:didReceiveLocalNotification: )时重新设置,但是这有一个问题,此方法只有当用户点了 apns 的提示时(或在锁屏状态下滑动进入 app )时才能触发,也就是说,如果在后台收到了 apns ,但用户是通过点击 app 的 icon 进入 app 的话,此方法是不会触发的!这也是苹果的一个 bug ,所以我的做法是在 application:didReceiveLocalNotification: 此方法里触发,也在 applicationdidbecomeactive 方法里触发!这样就可以解决此问题了
效果图
(无)
备注
(无)