通知 - 本地推送通知(Local Notification)的测试与实现
变更记录
序号 | 录入时间 | 录入人 | 备注 |
---|---|---|---|
1 | 2015-04-23 | Alfred Jiang | - |
2 | 2015-12-23 | Alfred Jiang | - |
方案名称
通知 - 本地推送通知(Local Notification)的测试与实现
关键字
通知 \ 推送 \ UILocalNotification \ 本地推送 \ 本地通知 \ 闹钟
需求场景
- 需要实现类似闹铃功能的本地推送通知需求
参考链接
详细内容
######1. JRNLocalNotificationCenter
JRNLocalNotificationCenter.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76//
// JRNLocalNotificationCenter.h
// DemoApp
//
// Created by jarinosuke on 7/27/13.
// Copyright (c) 2013 jarinosuke. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
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;
@endJRNLocalNotificationCenter.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320//
// 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
1 | // |
######3. 接受通知
1 | func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) { |
######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 方法里触发!这样就可以解决此问题了
效果图
(无)
备注
(无)