我已经对这个问题进行了大量的研究,并在Objective-C中写了几个替代方案,但似乎没有任何效果。我的问题是:这在iOS中是否可行?
具体来说,我希望用户能够在每小时的顶部设置“每小时报时”,通知,然后让应用重启计时器,即使用户没有响应。我知道我不能使用NSTimer,因为Apple限制了在后台运行的计时器。如果用户没有响应UILocalNotification
,我已经尝试UILocalNotification
和“看门狗”计时器来重新启动每小时计时器。但是,如果应用程序在后台,则在appdelegate.m中导致小时计时器重置的代码将不会执行,直到用户响应UILocalNotification
。当应用程序在后台时,“看门狗”计时器不会触发。
用户还可以将“钟声”设置为在超过一小时的15分钟,30分钟和45分钟时关闭。
像Sciral HabiTimer这样的应用程序如何做到这一点?代码片段如下。
AppDelegate.m
UILocalNotification *localNotification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (localNotification) {
// Set icon badge number to zero
application.applicationIconBadgeNumber = 0;
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller stopTimer];
[controller startTimer];
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
NSInteger interval = [storage integerForKey:@"intervalToWatch"];
[controller setLocalNotificationExpirationTime:interval];
}
return YES;
}
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
UIApplicationState state = [application applicationState];
if (state == UIApplicationStateActive) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Reminder"
message:notification.alertBody
delegate:self cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
}
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller stopTimer];
[controller startTimer];
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
NSString *received = @"YES";
[storage setObject:received forKey:@"notificationWasReceived"];
[storage synchronize];
NSInteger intervalToWatch = [storage integerForKey:@"intervalToWatch"];
[controller setLocalNotificationExpirationTime:intervalToWatch];
// if a timer was started, stop it.
NSTimer *timer = controller.expirationTimer;
if(timer){
[timer invalidate];}
// Set icon badge number to zero
application.applicationIconBadgeNumber = 0;
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
NSLog(@"application did enter background");
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller stopTimer];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller startTimer];
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
@end
HourlyChimeTableViewController:
-(void)setLocalNotificationExpirationTime:(NSInteger)intervalToWatch{
NSDate *today = [[NSDate alloc] init];
NSLog(@"Today: %@", today);
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components= [calendar components:(NSCalendarUnitMinute) | (NSCalendarUnitSecond) fromDate: today];
NSInteger minute = [components minute];
NSInteger second = [components second];
NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
switch(intervalToWatch){
case 0:{
NSInteger difference;
//minute =13;
// NSInteger difference = 10;
[offsetComponents setHour:0];
[offsetComponents setMinute:0];
if(minute >= 0 && minute < 15){
difference = 900 - minute*60 - second;//seconds left to quarter past
NSLog(@"Minute: %ld Second: %ld", (long)minute, (long)second);
}else{
if(minute >= 15 && minute < 30){
difference = 1800 - minute*60 - second;// seconds to half past
NSLog(@"Minute: %ld Second: %ld", (long)minute, (long)second);
}else{
if (minute >= 30 && minute < 45){// seconds to quarter to
difference = 2700 - minute*60 - second;
}else{// seconds to the hour
difference = 3600 - minute*60 - second;
NSLog(@"Minute: %ld Second: %ld", (long)minute, (long)second);
}
}
}
// difference = 60;
[offsetComponents setSecond:difference];
NSDate *fireDate = [calendar dateByAddingComponents:offsetComponents
toDate:today options:0];
self.expirationInterval = difference + 5;
UILocalNotification *notification = [self startLocalNotification:fireDate];
self.localNotification = notification;
[self startNotificationTimer:self.expirationInterval];
break;
-(UILocalNotification *)startLocalNotification:(NSDate *)fireDate{
// [[UIApplication sharedApplication] cancelAllLocalNotifications];
UILocalNotification *notification = [[UILocalNotification alloc]init];
notification.fireDate = fireDate;
NSLog(@"firedate %@", fireDate);
notification.alertBody =@"Timer Expired!";
notification.alertTitle = @"TimeChime Alert";
notification.timeZone = [NSTimeZone defaultTimeZone];
notification.soundName = UILocalNotificationDefaultSoundName;
self.notificationWasReceived = @"NO";
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
[storage setObject:self.notificationWasReceived forKey:@"notificationWasReceived"];
[storage synchronize];
// notification.repeatInterval = NSCalendarUnitMinute;
[[UIApplication sharedApplication]scheduleLocalNotification:notification];
return notification;
}
-(void)startNotificationTimer:(NSInteger)seconds{
NSTimer *notificationTimer = [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(notificationTimerDidExpire:) userInfo:nil repeats:YES];
self.expirationTimer = notificationTimer;
NSLog(@"started notification timer for %ld", (long)seconds);
}
-(void)notificationTimerDidExpire:(NSTimer *)notification{
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
self.notificationWasReceived = [storage objectForKey:@"notificationWasReceived"];
NSLog(@"notification timer expired and notificationWasReceived =%@", self.notificationWasReceived);
if(self.localNotification)
{
[[UIApplication sharedApplication]cancelLocalNotification:self.localNotification];
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
NSInteger intervalToWatch = [storage integerForKey:@"intervalToWatch"];
// and reschedule the notification.
[self setLocalNotificationExpirationTime:intervalToWatch];
}
}
我通过安排多个重复通知解决了这个问题。这是代码(还为iOS8添加了自定义操作)。请注意,iOS10有一种完全不同的方式来安排本地通知):
//
// AppDelegate.m
// Hourly Chime2
//
// Created by Nelson Capes on 9/20/16.
// Copyright © 2016 Nelson Capes. All rights reserved.
//
#import "AppDelegate.h"
#import <AVFoundation/AVFoundation.h>
#import "HourlyChimeTableViewController.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
NSError *sessionError = nil;
NSError *activationError = nil;
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error:&sessionError];
[[AVAudioSession sharedInstance] setActive: YES error: &activationError];
// ask the user to allow local notifications
if ([UIApplication instancesRespondToSelector:@selector(registerUserNotificationSettings:)]){
[application registerUserNotificationSettings:[UIUserNotificationSettings
settingsForTypes:UIUserNotificationTypeAlert categories:nil]];
}
UILocalNotification *localNotification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (localNotification) {
// Set icon badge number to zero
application.applicationIconBadgeNumber = 0;
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller stopTimer];
[controller startTimer];
[[UIApplication sharedApplication]cancelLocalNotification:localNotification];
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
NSInteger intervalToWatch = [storage integerForKey:@"intervalToWatch"];
NSMutableArray *array = [storage objectForKey:@"timerArray"];
// BOOL YES means to schedule a set of local notifications; NO means don't schedule.
[controller setLocalNotificationExpirationTime:intervalToWatch : NO];
// Set icon badge number to zero
application.applicationIconBadgeNumber = 0;
}
// define a notification action
UIMutableUserNotificationAction *acceptAction = [[UIMutableUserNotificationAction alloc]init];
acceptAction.identifier =@"ACCEPT_IDENTIFIER";
acceptAction.title = @"Continue";
acceptAction.activationMode = UIUserNotificationActivationModeBackground;
acceptAction.destructive = NO;
acceptAction.authenticationRequired = NO;
UIMutableUserNotificationAction *declineAction = [[UIMutableUserNotificationAction alloc]init];
declineAction.identifier =@"DECLINE_IDENTIFIER";
declineAction.title = @"Stop";
declineAction.activationMode = UIUserNotificationActivationModeBackground;
declineAction.destructive = NO;
declineAction.authenticationRequired = NO;
UIMutableUserNotificationCategory *inviteCategory = [[UIMutableUserNotificationCategory alloc]init];
inviteCategory.identifier = @"INVITE_CATEGORY";
[inviteCategory setActions:@[acceptAction, declineAction] forContext:UIUserNotificationActionContextDefault];
NSSet *categories = [NSSet setWithObjects:inviteCategory, nil];
if([UIApplication instancesRespondToSelector:@selector(registerUserNotificationSettings:)]){
UIUserNotificationSettings *settings = [UIUserNotificationSettings
settingsForTypes:UIUserNotificationTypeAlert categories:categories];
[[UIApplication sharedApplication]registerUserNotificationSettings:settings];
}
return YES;
}
// determine whether the user will allow local notifications.
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings;{
if ([[UIApplication sharedApplication] respondsToSelector:@selector(currentUserNotificationSettings)]){ // Check it's iOS 8 and above
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
UIUserNotificationSettings *grantedSettings = [[UIApplication sharedApplication] currentUserNotificationSettings];
if (grantedSettings.types == UIUserNotificationTypeNone) {
NSLog(@"No permission granted");
[storage setBool:NO forKey:@"permission granted"];
}
else if (grantedSettings.types & UIUserNotificationTypeSound & UIUserNotificationTypeAlert ){
NSLog(@"Sound and alert permissions ");
[storage setBool:YES forKey:@"permission granted"];
[storage setBool:YES forKey:@"sound permission granted"];
[storage setBool:YES forKey:@"alert permission granted"];
}
else if (grantedSettings.types & UIUserNotificationTypeAlert){
NSLog(@"Alert Permission Granted");
[storage setBool:YES forKey:@"permission granted"];
[storage setBool:YES forKey:@"alert permission granted"];
[storage setBool:NO forKey:@"sound permission granted"];
}
[storage synchronize];
}
}
-(void)application:(UIApplication *)application handleActionWithIdentifier:(nullable NSString *)identifier forLocalNotification:(nonnull UILocalNotification *)notification completionHandler:(nonnull void (^)())completionHandler{
if ([identifier isEqualToString:@"ACCEPT_IDENTIFIER"]){
[self handleAcceptActionWithNotification:notification];
}else{
if ([identifier isEqualToString:@"DECLINE_IDENTIFIER"]){
[self handleDeclineActionWithNotification:notification];
}
}
completionHandler();
}
-(void)handleAcceptActionWithNotification:notification{
NSLog(@"accept action received");
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller stopTimer];
[controller startTimer];
[[UIApplication sharedApplication]cancelLocalNotification:notification];
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
NSInteger intervalToWatch = [storage integerForKey:@"intervalToWatch"];
// BOOL YES means to schedule a set of local notifications; NO means don't schedule.
[controller setLocalNotificationExpirationTime:intervalToWatch : NO];
}
-(void)handleDeclineActionWithNotification:notification{
NSLog(@"decline Action received");
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller stopTimer];
}
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
UIApplicationState state = [application applicationState];
if (state == UIApplicationStateActive) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Reminder"
message:notification.alertBody
delegate:self cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
}
// [[UIApplication sharedApplication]cancelAllLocalNotifications];
NSLog(@"app delegate: notification received");
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller stopTimer];
[controller startTimer];
[[UIApplication sharedApplication]cancelLocalNotification:notification];
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
NSInteger intervalToWatch = [storage integerForKey:@"intervalToWatch"];
// NSMutableArray *array = [storage objectForKey:@"timerArray"];
// BOOL YES means to schedule a set of local notifications; NO means don't schedule.
[controller setLocalNotificationExpirationTime:intervalToWatch : NO];
// Set icon badge number to zero
// application.applicationIconBadgeNumber = 0;
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
NSLog(@"application did enter background");
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
// [controller stopTimer];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
NSLog(@"application did enter foreground");
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.a
NSLog(@"app delegate: applicationDidBecomeActive");
HourlyChimeTableViewController *controller = [[HourlyChimeTableViewController alloc]init];
[controller startTimer];
if ([[UIApplication sharedApplication] respondsToSelector:@selector(currentUserNotificationSettings)]){ // Check it's iOS 8 and above
UIUserNotificationSettings *grantedSettings = [[UIApplication sharedApplication] currentUserNotificationSettings];
if (grantedSettings.types == UIUserNotificationTypeNone) {
NSLog(@"No permission granted");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"You have not granted permission for Alerts"
message:@"We can't let you know when a timer expires"
delegate:self cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
}
else if (grantedSettings.types & UIUserNotificationTypeSound & UIUserNotificationTypeAlert ){
NSLog(@"Sound and alert permissions ");
}
else if (grantedSettings.types & UIUserNotificationTypeAlert){
NSLog(@"Alert Permission Granted");
}else{
NSLog(@"No permissions ");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"You have not granted permission for Alerts"
message:@"We can't let you know when a timer expires"
delegate:self cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
}
}
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
@end
以下代码显示了如何安排本地通知:
-(void)setLocalNotificationExpirationTime:(NSInteger)intervalToWatch :(BOOL)option{
// BOOL YES means to schedule a set of local notifications; NO means don't schedule.
NSDate *today = [[NSDate alloc] init];
NSLog(@"Today: %@", today);
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components= [calendar components:(NSCalendarUnitMinute) | (NSCalendarUnitSecond) fromDate: today];
NSInteger minute = [components minute];
NSInteger second = [components second];
NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
// intervalToWatch is an NSInteger set by the UI to determine what kind of notification to set.
// 1 = every 15 minutes; 2 = every half hour; 3 = every hour.
// we calculate the seconds left until the next interval, then schedule a local notification with the seconds left.
switch(intervalToWatch){
case 0:{// notification every 15 minutes
NSInteger difference;
[offsetComponents setHour:0];
[offsetComponents setMinute:0];
if(minute >= 0 && minute < 15){
difference = 900 - minute*60 - second;//seconds left to quarter past
// NSLog(@"Minute: %ld Second: %ld", (long)minute, (long)second);
}else{
if(minute >= 15 && minute < 30){
difference = 1800 - minute*60 - second;// seconds to half past
// NSLog(@"Minute: %ld Second: %ld", (long)minute, (long)second);
}else{
if (minute >= 30 && minute < 45){// seconds to quarter to
difference = 2700 - minute*60 - second;
}else{// seconds to the hour
difference = 3600 - minute*60 - second;
// NSLog(@"Minute: %ld Second: %ld", (long)minute, (long)second);
}
}
}
// schedule repeating local notifications every 15 minutes.
if(option == YES){
[offsetComponents setSecond:difference];
NSDate *fireDate = [calendar dateByAddingComponents:offsetComponents
toDate:today options:0];
UILocalNotification *notification = [self startLocalNotification:fireDate];
self.localNotification = notification;// at the next 15 minute interval.
[offsetComponents setSecond:difference + 900];
fireDate = [calendar dateByAddingComponents:offsetComponents
toDate:today options:0];
UILocalNotification *notification1 = [self startLocalNotification:fireDate];// at next 30 minutes
[self.timerArray addObject:notification1];
[offsetComponents setSecond:difference + 1800];
fireDate = [calendar dateByAddingComponents:offsetComponents
toDate:today options:0];
UILocalNotification *notification2 = [self startLocalNotification:fireDate];// at next 45 minutes
[self.timerArray addObject:notification2];
[offsetComponents setSecond:difference + 2700];
fireDate = [calendar dateByAddingComponents:offsetComponents
toDate:today options:0];
UILocalNotification *notification3 = [self startLocalNotification:fireDate];// at next hour
[self.timerArray addObject:notification3];
}
}
break;
case 1:{// notification every 30 minutes at half past the hour.
NSInteger difference;
[offsetComponents setHour:0];
[offsetComponents setMinute:0];
if(minute >= 0 && minute < 30){
difference = 1800 - minute*60 - second;// seconds to half past
}else{
difference = 3600 - minute*60 - second;// seconds to the hour
}
if(option == YES){
[offsetComponents setSecond:difference];
NSDate *fireDate = [calendar dateByAddingComponents:offsetComponents
toDate:today options:0];
UILocalNotification *notification = [self startLocalNotification:fireDate];
self.localNotification = notification;// at the next 30 minute interval.
[offsetComponents setSecond:difference + 1800];
fireDate = [calendar dateByAddingComponents:offsetComponents
toDate:today options:0];
UILocalNotification *notification2 = [self startLocalNotification:fireDate];// at next 45 minutes
[self.timerArray addObject:notification2];
}
break;
}
case 2:{// notification every hour on the hour
minute = 58;
NSInteger difference = 3600 - minute*60 - second;
if(option == YES){
[offsetComponents setSecond:difference];
NSDate *fireDate = [calendar dateByAddingComponents:offsetComponents
toDate:today options:0];
UILocalNotification *notification = [self startLocalNotification:fireDate];
self.localNotification = notification;// at the next 60 minute interval.
}
break;
}
}
}
-(UILocalNotification *)startLocalNotification:(NSDate *)fireDate{
// [[UIApplication sharedApplication] cancelAllLocalNotifications];
UILocalNotification *notification = [[UILocalNotification alloc]init];
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
notification.fireDate = fireDate;
NSLog(@"firedate %@", fireDate);
notification.alertBody =@"Timer Expired!";
notification.alertTitle = @"TimeChime Alert";
notification.timeZone = [NSTimeZone defaultTimeZone];
BOOL sound = [storage boolForKey:@"sound permission granted"];
if(sound){
notification.soundName = UILocalNotificationDefaultSoundName;}
notification.repeatInterval = NSCalendarUnitHour;
notification.category = @"INVITE_CATEGORY";
[[UIApplication sharedApplication]scheduleLocalNotification:notification];
return notification;
}
答案 0 :(得分:0)
经过大量测试后,我对自己问题的回答是“这是不可能的”。应用程序开始等待本地通知后,它将被阻止。没有办法,除了编写一些多线程代码(我从未做过),做任何事情,但让用户做出反应(即,当应用程序被阻止时,没有看门狗定时器会关闭,例如,当它被阻止时是在背景中。
然而,我确实找到了解决问题的方法,我已编辑此帖以反映。