做完这个本地通知 感觉是时候写一波本地通知了。
首先 你得知道官方开放的东西其实基本都有,就看你怎么去用它。
说说几个性质
在AppDelegate里面
//点击通知
-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
这个就是当本地通知来了的时候 你点击通知会调用的方法。
此时 你可以直接
//取消通知
[[UIApplication sharedApplication] cancelLocalNotification:notification];
// user info
@property(nonatomic,copy) NSDictionary *userInfo; // throws if contains non-property list types
这个属性。
创建本地通知的有关属性
UILocalNotification *notification = [[UILocalNotification alloc] init];
// 使用本地时区
notification.timeZone = [NSTimeZone defaultTimeZone];
// 设置提醒的文字内容
notification.alertBody = des;
// 通知提示音 使用默认的
notification.soundName= UILocalNotificationDefaultSoundName;
{
//当前时间
NSDate *currentDate = [NSDate date];
//当前时间再增加5秒,即 5秒后通知。
//通知时间
notification.fireDate = [currentDate dateByAddingTimeInterval:5.0];
}或
{
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"HH:mm:ss"];
//触发通知的时间
NSDate *now = [formatter dateFromString:@"15:00:00"];
notification.fireDate = now;
}
// 设置重复间隔
notification.repeatInterval = kCFCalendarUnitDay;
//在通知中携带参数信息
NSDictionary *dic = [NSDictionary dictionaryWithObject:@"name" forKey:@"key"];
notification.userInfo = dic;
//添加
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
有几个参数得讲一讲,首先,
通知的时间 fireData
想必你也看懂了,在上述代码中,有个或,意思就是有两种写法,或下面是随便定义一个想要的时间,这点非常强大,一些Todo和闹钟类应用都有通知用户的功能,使用的就是iOS中的本地通知UILocalNotification,还有些应用会在每天、每周、每月固定时间提示用户回到应用看看,也是用的本地通知。 比如我还可以这么写,
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"YYYY:MM:dd:HH:mm:ss"];
NSDate *time = [formatter dateFromString:[NSString stringWithFormat:@"%@:%@:%@:%@:%@:00",设置好的年,月,日,时,分]];
notification.fireDate = time;
上述代码实现在规定的时间通知。
再或者
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"YYYY:MM:dd:HH:mm:ss"];
NSDate *time = [formatter dateFromString:[NSString stringWithFormat:@"%@:%@:%@:%@:%@:00",设置好的年,月,日,时,分]];
NSDate *yesterday = [self calculationSpecifiedTimeAgo:time howTimeAgo:rem];
notification.fireDate = yesterday;
-(NSDate *)calculationSpecifiedTimeAgo:(id)time howTimeAgo:(NSString *)rem{
NSTimeInterval secondsPerDay;
if ([rem isEqualToString:@"事件发生时"]) {
secondsPerDay = 0;
}else if ([rem isEqualToString:@"5分钟"]) {
secondsPerDay = 5 * 60;
}else if([rem isEqualToString:@"10分钟"]){
secondsPerDay = 10 * 60;
}else if([rem isEqualToString:@"15分钟"]){
secondsPerDay = 15 * 60;
}else if([rem isEqualToString:@"半小时"]){
secondsPerDay = 30 * 60;
}else if([rem isEqualToString:@"一小时"]){
secondsPerDay = 60 * 60;
}else if([rem isEqualToString:@"两小时"]){
secondsPerDay = 2 * 60 * 60;
}else if([rem isEqualToString:@"一天"]){
secondsPerDay = 24 * 60 * 60;
}
return [[NSDate alloc] initWithTimeInterval:-secondsPerDay sinceDate:time];
}
上述代码实现了在一个时间点然后用户选择是否提前提醒,根据字段 提前通知。
重复间隔时间repeatInterval
具体可以重复哪些 就不再概述,这个参数的意思是 多久就会重复一次你设置时间的通知,比如你这里设为每天,这样在你设置的时间,每天的这个时间都会收到通知,当然,基于你没有取消掉这个通知。当你不设 也就是默认会为0,此时是不重复,仅提醒一次。(但是好像我设了0之后一直在重复。。所以我给设成一世纪重复一次了。。。)携带参数userInfo!
添加通知 并且可开关
[UserDefaultssetObject:_arryforKey:@"SchArry"];
类似一个数据库,在表格中,只要有显示的行,都会在这个数组内,不管本地通知中有没有。
当你刚新建的时候,就已经开始创建数组了,把所有的信息放入,并且同时创建本地通知,也放入userInfo。当你开关关闭,本地通知中根据_arry中的字典这行的tag 去遍历本地通知中的tag,找到则删除。当你开关开启时,又等于是新建一个本地通知,虽然看起来仅仅是开启关闭而已,其实又新建了,此时tag累加,重新赋值。
并且每行长按可编辑,同样的道理,假如开关为开,编辑的话也就是删除这个通知,再新添加一个通知。假如开关为关,编辑的话 只需要编辑_arry中的字典。
直接贴上代码 实现了很多功能
@interface SchedulingVC () <UIActionSheetDelegate,UITableViewDataSource,UITableViewDelegate>
{
NSUInteger _selectRow;
NSInteger _tag;
NSMutableDictionary *_dic;
NSMutableArray *_arry;
NSInteger _swtich;
}
- (void)viewDidLoad
if ([[UserDefaults objectForKey:@"Schflag"] intValue] == 1) {
_tag = [UserDefaults integerForKey:@"SchTag"];
//取得SchArry中的数组赋给_arry 用来显示表格
_arry = [NSMutableArray arrayWithArray:[UserDefaults objectForKey:@"SchArry"]];
NSLog(@"当前的本地通知 %@",[[UIApplication sharedApplication] scheduledLocalNotifications]);
}else{
//假如程序第一次启动,实例化
_dic = [[NSMutableDictionary alloc] init];
_arry = [[NSMutableArray alloc] init];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(cellLongPress:)];
longPressGesture.view.tag = indexPath.row;
[cell addGestureRecognizer:longPressGesture];
// UISwitch *t_switch = (UISwitch *)[cell.contentView viewWithTag:101 + indexPath.row];
t_switch.onTintColor = [UIColor greenColor];
//从_arry中取出这行的开关状态
if (YES ==[[_arry[indexPath.row] objectForKey:@"switch"] boolValue]){
[t_switch setOn:YES animated:NO];
}else{
[t_switch setOn:NO animated:NO];
}
UILabel *data = (UILabel *)[cell.contentView viewWithTag:100];
UILabel *time = (UILabel *)[cell.contentView viewWithTag:99];
UILabel *des = (UILabel *)[cell.contentView viewWithTag:98];
if ([[_arry[indexPath.row] objectForKey:@"data"] hasContain:@"月"]){
data.frame = CGRectMake(7, 12, 105, 25);
time.frame = CGRectMake(105, 12, 150, 25);
}else if ([[_arry[indexPath.row] objectForKey:@"data"] hasContain:@"周"]){
data.frame = CGRectMake(7, 12, 70, 25);
time.frame = CGRectMake(80, 12, 150, 25);
}else{
data.frame = CGRectMake(7, 12, 60, 25);
time.frame = CGRectMake(70, 12, 150, 25);
}
[data setText:[_arry[indexPath.row] objectForKey:@"data"]];
[time setText:[_arry[indexPath.row] objectForKey:@"time"]];
[des setText:[_arry[indexPath.row] objectForKey:@"des"]];
return cell;
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
_selectRow = indexPath.row;
[self editScheduleVC];
}
#pragma mark 跳入编辑日程安排方法
-(void)editScheduleVC{
//处于编辑状态,_swtich要设为0,因为等下编辑状态执行的代码要区分。
_swtich = 0;
//编辑,把数据传给界面
SetScheduleVC *vc = [[SetScheduleVC alloc] initWithNibName:@"SetScheduleVC" bundle:nil];
vc.delegate = (id<SetScheduleVCDelegate>)self;
vc.desStr = [_arry[_selectRow] objectForKey:@"des"];
vc.timeStr = [_arry[_selectRow] objectForKey:@"time"];
vc.dataStr = [_arry[_selectRow] objectForKey:@"yea-mon"];
vc.redTimeStr = [_arry[_selectRow] objectForKey:@"rmdTime"];
vc.editState = YES;
//让编辑界面打钩的在某行打钩
if ([[_arry[_selectRow] objectForKey:@"data"] hasContain:@"月"]){
vc.select = 2;
}else if([[_arry[_selectRow] objectForKey:@"data"] hasContain:@"周"]){
vc.select = 1;
}else{
vc.select = 0;
}
[self.navigationController pushViewController:vc animated:YES];
}
#pragma mark 长按手势
- (void)cellLongPress:(UIGestureRecognizer *)recognizer
{
if(recognizer.state == UIGestureRecognizerStateBegan)
{
//取出长按的 IndexPath.row
CGPoint point = [recognizer locationInView:_tableView];
NSIndexPath * indexPath = [_tableView indexPathForRowAtPoint:point];
if(indexPath == nil) return ;
_selectRow = [indexPath row];
//弹出 UIActionSheet 功能菜单
UIActionSheet *t_actionSheet = [[UIActionSheet alloc]initWithTitle:nil delegate:self cancelButtonTitle:@"取消" destructiveButtonTitle:@"编辑" otherButtonTitles:@"删除", nil];
t_actionSheet.actionSheetStyle = UIActionSheetStyleBlackOpaque;
[t_actionSheet showInView:self.view];
}
}
-(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 1) {
//删除该行通知,首先、遍历。
for (UILocalNotification *noti in [[UIApplication sharedApplication] scheduledLocalNotifications]) {
NSString *notiID = [noti.userInfo objectForKey:@"tag"];
if ([notiID isEqualToString:([[UserDefaults objectForKey:@"SchArry"][_selectRow] objectForKey:@"tag"])]) {
[[UIApplication sharedApplication] cancelLocalNotification:noti];
}
}
//其次、必须移除这行
[_arry removeObjectAtIndex:_selectRow];
// NSLog(@"删除后的本地通知 %@",[[UIApplication sharedApplication] scheduledLocalNotifications]);
//更新 “SchArry”
[UserDefaults setObject:_arry forKey:@"SchArry"];
[self.tableView reloadData];
}else if (buttonIndex == 0){
[self editScheduleVC];
}
}
#pragma mark SetScheduleVCDelegate代理方法 (含有本地通知)
-(void)getMo:(NSString *)month getHou:(NSString *)hour getdes:(NSString *)des getDay:(NSArray *)yeMonDay getSta:(BOOL)sta getRem:(NSString *)rem{
NSArray *hou = [hour componentsSeparatedByString:@":"];
UILocalNotification *notification = [[UILocalNotification alloc] init];
// 使用本地时区
notification.timeZone = [NSTimeZone defaultTimeZone];
// 设置通知的提醒时间
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"YYYY:MM:dd:HH:mm:ss"];
NSDate *now = [formatter dateFromString:[NSString stringWithFormat:@"%@:%@:%@:%@:%@:00",yeMonDay[1],yeMonDay[0][0],yeMonDay[0][1],hou[0],hou[1]]];
//是否提前通知
NSDate *yesterday = [self calculationSpecifiedTimeAgo:now howTimeAgo:rem];
notification.fireDate = yesterday;
// 设置重复间隔
if ([month hasContain:@"月"]){
notification.repeatInterval = kCFCalendarUnitMonth;
}else if([month hasContain:@"周"]){
notification.repeatInterval = kCFCalendarUnitWeek;
}else{
notification.repeatInterval = kCFCalendarUnitEra;
}
// 设置提醒的文字内容
notification.alertBody = des;
// 通知提示音 使用默认的
notification.soundName= UILocalNotificationDefaultSoundName;
//初始时 tag为1,不断累加,放入_dic,用来辨识本地通知
NSString *data = [[NSString alloc] initWithFormat:@"%@-%@",yeMonDay[0][0],yeMonDay[0][1]];
_tag = _tag +1;
_dic = [[NSMutableDictionary alloc] init];
[_dic setObject:[NSString stringWithFormat:@"%ld",(long)_tag] forKey:@"tag"];
[_dic setObject:hour forKey:@"time"];
[_dic setObject:month forKey:@"data"];
[_dic setObject:des forKey:@"des"];
[_dic setObject:data forKey:@"yea-mon"];
[_dic setObject:rem forKey:@"rmdTime"];
[_dic setObject:@"NO" forKey:@"earSta"];
//sta 判断是否为编辑状态 YES:在编辑状态下添加本地通知,NO:新建本地通知
if (sta) {
// _swtich !=0 仅仅是从开关从关到开的过程
if (_swtich!=0) {
//该通知userInfo的 "switch" 要设为YES,与_arry同步。
[_dic setObject:@"YES" forKey:@"switch"];
notification.userInfo = _dic;
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
/*
同时更新一直存在的 _arry,因为程序关闭后再进来 是根据_arry来显示的,不关 LocalNotification 的事情,
LocalNotification仅仅是放本地通知的地方,注:其中每个LocalNotification都有相应的 userInfo, 但是它们之间是没有顺序的。
*/
[_arry replaceObjectAtIndex:_swtich-1 withObject:_dic];
}else{
//保持原有开关状态不变 仅仅是编辑状态过来的
if ([[_arry[_selectRow] objectForKey:@"switch"] boolValue]) {
//开启状态下编辑 首先、遍历本地通知
[_dic setObject:@"YES" forKey:@"switch"];
for (UILocalNotification *noti in [[UIApplication sharedApplication] scheduledLocalNotifications]) {
NSString *notiID = [noti.userInfo objectForKey:@"tag"];
//其次、假如在本地通知中的tag 等于 该行的字典中的tag 则删除需要编辑的通知,替换新的内容。
if ([notiID isEqualToString:([[UserDefaults objectForKey:@"SchArry"][_selectRow] objectForKey:@"tag"])]) {
[[UIApplication sharedApplication] cancelLocalNotification:noti];
[_arry replaceObjectAtIndex:_selectRow withObject:_dic];
}
}
notification.userInfo = _dic;
//最后、添加进新的通知
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
// NSLog(@"开启状态下编辑后的本地通知 %@",[[UIApplication sharedApplication] scheduledLocalNotifications]);
}else{
//关闭状态下编辑 替换掉_arry中的字典。本地通知不变,只是显示有变化
[_dic setObject:@"NO" forKey:@"switch"];
[_arry replaceObjectAtIndex:_selectRow withObject:_dic];
// NSLog(@"关闭状态下编辑后的本地通知 %@",[[UIApplication sharedApplication] scheduledLocalNotifications]);
}
}
}else{
[_dic setObject:@"YES" forKey:@"switch"];
//新建通知后,会增加一行,此时_arry也要增加,把这个通知的信息_dic加入到_arry
[_arry addObject:_dic];
notification.userInfo = _dic;
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
// NSLog(@"新建后的本地通知 %@",[[UIApplication sharedApplication] scheduledLocalNotifications]);
}
// NSLog(@"加入后的本地通知 %@",[[UIApplication sharedApplication] scheduledLocalNotifications]);
//将每行显示的 即对应 _arry 存进 SchArry.以便下次进入 依然正常显示
[UserDefaults setObject:_arry forKey:@"SchArry"];
//记住此时最后一个通知对应的tag,下次进入程序,先取出,假如要对本地通知进行删改。增加,则在此基础上累加_tag值,保证每个通知对应的tag唯一。
[UserDefaults setInteger:_tag forKey:@"SchTag"];
//判断程序是否存在本地通知,若存在 则为1,下次程序启动则要取上述两个值来显示。
[UserDefaults setFloat:1 forKey:@"Schflag"];
[UserDefaults synchronize];
//增删改之后 要刷新表格
[self.tableView reloadData];
}
#pragma mark 开关
//开关开到一半再关闭,或者关到一半再开启,此时 switchChanged 方法执行或发生错误。所以要在在开关滑动的时候 视图不可用。
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event{
self.view.userInteractionEnabled = NO;
return YES;
}
- (void)switchChanged:(UISwitch *)sender{
//因为从UserDefaults中取出的值为不可变,所以要转为可变数组,以便开关状态改变时,同步改变_arry中 每行一个字典 的“switch”参数。
_arry = [NSMutableArray arrayWithArray:[UserDefaults objectForKey:@"SchArry"]];
if (sender.on) {
if (![[_arry[sender.tag-101] objectForKey:@"switch"] boolValue]) {
//_swtichbu 不为0,以区分 仅仅是从开关从关到开的过程 而执行不同的代码,并且_arry可以根据_switch来替换这行的_dic(内容)
_swtich = sender.tag -100;
//把_arry中该行的字典变为可变字典,改变其中“switch”参数的值为YES。
_arry[sender.tag-101] = [[NSMutableDictionary alloc] initWithDictionary:_arry[sender.tag-101]];
[_arry[sender.tag-101] setObject:@"YES" forKey:@"switch"];
NSArray *day = [[_arry[sender.tag-101] objectForKey:@"yea-mon"] componentsSeparatedByString:@"-"];
NSArray *yeMonDay = [NSArray arrayWithObjects:day,[XCCommonUtility ReadTheYear], nil];
//开启相当于编辑状态下 由开关从关到开的过程,getSta 为YES,_swtich不为0,跳入方法,具体实现见方法。
[self getMo:[_arry[sender.tag-101] objectForKey:@"data"] getHou:[_arry[sender.tag-101] objectForKey:@"time"] getdes:[_arry[sender.tag-101] objectForKey:@"des"] getDay:yeMonDay getSta:YES getRem:[_arry[sender.tag-101] objectForKey:@"rmdTime"]];
// NSLog(@"打开后的本地通知 %@",[[UIApplication sharedApplication] scheduledLocalNotifications]);
}
}else{
if ([[_arry[sender.tag-101] objectForKey:@"switch"] boolValue]) {
//同上
_arry[sender.tag-101] = [[NSMutableDictionary alloc] initWithDictionary:_arry[sender.tag-101]];
[_arry[sender.tag-101] setObject:@"NO" forKey:@"switch"];
/*
开关由开到关过程中 只需要遍历本地通知中 与_arry中这行tag 相同的tag的通知 然后取消即可。
不用删除这行,只需改变switch,以判断下次进入是开是关。
*/
for (UILocalNotification *noti in [[UIApplication sharedApplication] scheduledLocalNotifications]) {
NSString *notiID = [noti.userInfo objectForKey:@"tag"];
//显示有多少行,_arry中就有多少组。不管本地通知中有多少,与_arry无关。
if ([notiID isEqualToString:([[UserDefaults objectForKey:@"SchArry"][sender.tag-101] objectForKey:@"tag"])]) {
[[UIApplication sharedApplication] cancelLocalNotification:noti];
// NSLog(@"关闭后的本地通知 %@",[[UIApplication sharedApplication] scheduledLocalNotifications]);
}
}
//更新SchArry
[UserDefaults setObject:_arry forKey:@"SchArry"];
[self.tableView reloadData];
}
}
}
-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification{
// NSLog(@"Application did receive local notifications");
[UIApplication sharedApplication].applicationIconBadgeNumber = 1;
[UIApplication sharedApplication].applicationIconBadgeNumber = 0;
NSMutableArray * arry = [NSMutableArray arrayWithArray:[UserDefaults objectForKey:@"SchArry"]];
int a = 0;
int flag = 0;
//以下判断 只提醒一次 进入日程安排 开关自动关闭
NSMutableDictionary *muNoti;
for (NSDictionary *noti in arry) {
a = a+1;
NSString *notiID = [noti objectForKey:@"tag"];
if ([notiID isEqualToString:[notification.userInfo objectForKey:@"tag"]] && ([[notification.userInfo objectForKey:@"data"] rangeOfString:@"-"].location !=NSNotFound)) {
flag = 1;
[[UIApplication sharedApplication] cancelLocalNotification:notification];
muNoti = [[NSMutableDictionary alloc] initWithDictionary:noti];
[muNoti setObject:@"NO" forKey:@"switch"];
// NSLog(@"关闭后的本地通知 %@",[[UIApplication sharedApplication] scheduledLocalNotifications]);
break;
}
}
if (flag == 1) {
[arry replaceObjectAtIndex:a-1 withObject:muNoti];
[UserDefaults setObject:arry forKey:@"SchArry"];
}
ScheduleRemindVC *vc = [[ScheduleRemindVC alloc] initWithNibName:@"ScheduleRemindVC" bundle:nil];
vc.tempTime = [notification.userInfo objectForKey:@"time"];
vc.tempdata = [notification.userInfo objectForKey:@"data"];
vc.tempdes = [notification.userInfo objectForKey:@"des"];
vc.tag = [[notification.userInfo objectForKey:@"tag"] intValue];
self.window.rootViewController = vc;
}
总结
注释应该写的很详细了,主要有几点,就是1、思维一定要理清楚,不同的功能对应不同的代码。
2、当你设置为每周一个时间点,每月一个时间点,这些情况是不用取消本地通知的,一旦取消 就没有了。
3、当你用开关的时候,一定要像我这种 让视图不可用 一下,否则 你慢慢的关到一半,不关,又开回来,这时开关的方法会有问题,使得通知重复增加。
4、_arry是数组,对应着每行,每行有一个_dic,这点跟通知一样。但是_arry是对应的行!行 存在,不管开关开没开,_arry都会有着这行的位置,而通知的话,一旦开关关闭,本地通知中就没有它了。显示的时候是根据_arry显示,而且_arry中 字典"switch"为开,(即开关为开) 的元素的_dic,要与本地通知一致。
5、当你想点击通知 假如是一次的话,进入表格 开关就会自动关闭时,这是就要依赖这个"switch"参数了,来通知,判断 重复间隔,取消,参数设为NO。记住,此时_arry中不用remove!因为行在 _arry就会存在。
6、不管你是新建、还是开关为开的状态下编辑、还是开关为关的状态下编辑、还是移除,想办法取好对应的tag,遍历好对应的tag,再进行操作,参数该设为开的设为开,添加的添加,修改内容的修改内容,就一切OK了。