user1949873
user1949873

Reputation: 460

Activate local notification based on time in the background

So I have an app that includes repeat intervals local notification and I would like to add a feature that will pause the notifications during sleep time.

So far, I have created two datepickers for the user to spicify what time they would like to stop the repeat interval and what time to restart again automatically. And I have added a uiswitch for them to activate the sleep mode or ignore the feature.

Now, how would I make my main uipickerview -( which they choose there notification from here) - to listen to the uiswitch if it is on, then it pause the notifications at the time that comes from my first datepicker and reintitiate the notifications at the time that comes from the second datepicker?

I have already setup my datepickers and my uiswitch but have no idea how to implement it with my uipickerview.. Should it be under the method of DidSelectRow? Or a method in appdelegate ( like DidEnterBackground)?

Please ask if you need more info or codes to understand the idea and help me. Thanks.

ADD ON:

Here is my code that I have prepared for the datepicker, however, I'm just missing the connection to add it to my picker view properly.

NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
dateFormatter.timeZone=[NSTimeZone defaultTimeZone];
dateFormatter.timeStyle=NSDateFormatterShortStyle;
dateFormatter.dateStyle=NSDateFormatterShortStyle;
NSString *dateTimeString=[dateFormatter stringFromDate:startTime.date];
NSLog(@"Start time is %@",dateTimeString);


NSDateFormatter *dateFormatter2 = [[NSDateFormatter alloc]init];
dateFormatter2.timeZone=[NSTimeZone defaultTimeZone];
dateFormatter2.timeStyle=NSDateFormatterShortStyle;
dateFormatter2.dateStyle=NSDateFormatterShortStyle;
NSString *dateTimeString2=[dateFormatter2 stringFromDate:endTime.date];
NSLog(@"End time is %@",dateTimeString2);

I also have this code to compare between the times:

if ([[NSDate date] isEqualToDate:startTime.date]) {
NSLog(@"currentDate is equal to startTime"); 
}

if ([[NSDate date] isEqualToDate:endTime.date]) {
NSLog(@"currentDate is equal to endTime"); 
}

Upvotes: 2

Views: 1540

Answers (2)

gavdotnet
gavdotnet

Reputation: 2224

Posting a local repeating notification between two specific dates/times is not supported by iOS. Additionally, unlike geo-based notifications, the app is not alerted of fired notifications when not in the foreground. So we need to work around this by creating many separate notifications for the time the user isn't asleep.

Follow these steps to create a basic solution:

  1. Connect your controls to IBOutlets in your view controller's header:

    SomeViewController.h:

    @interface SomeViewController : UIViewController
    
    @property (weak, nonatomic) IBOutlet UISwitch *sleepToggleSwitch;
    @property (weak, nonatomic) IBOutlet UIDatePicker *notificationIgnoreStartTime;
    @property (weak, nonatomic) IBOutlet UIDatePicker *notificationIgnoreEndTime;
    @property (weak, nonatomic) IBOutlet UIPickerView *notificationTypePickerView;
    
    @end
    
  2. Create IBAction methods and connect for each of your controls (two UIDatePickers, one UISwitch and one UIPickerView) in your view controller's implementation file. Each method should call the private method startUserOptionInteractionTimer.

    SomeViewController.m:

    - (IBAction)noNotificationPeriodStartDateChanged:(id)sender
    {
        [self startUserOptionInteractionTimer];
    }
    
    - (IBAction)noNotificationPeriodEndDateChanged:(id)sender
    {
        [self startUserOptionInteractionTimer];
    }
    
    - (IBAction)sleepToggleSwitchToggled:(id)sender
    {
        [self startUserOptionInteractionTimer];
    }
    
    - (IBAction)notificationTypeChanged:(id)sender
    {
        [self startUserOptionInteractionTimer];
    }
    
  3. In the startUserOptionInteractionTimer private method, we (re)start an NSTimer. We use a timer here so that if the user changes the dates or toggles the switch quickly - which they are likely to do - you don't tear down and set up the notifications quickly in succession. (The NSTimer property userOptionInteractionTimer should be declared in the interface continuation of the implementation file).

    SomeViewController.m:

    - (void)startUserOptionInteractionTimer
    {
        // Remove any existing timer
        [self.userOptionInteractionTimer invalidate];
        self.userOptionInteractionTimer = [NSTimer scheduledTimerWithTimeInterval:4.f
                                                                           target:self
                                                                         selector:@selector(setupNotifications)
                                                                         userInfo:nil
                                                                          repeats:NO];
    }
    
  4. Create another private method to tear down pre-existing notifications and setup the new ones.

    Setting up the notifications here depends on how long and how often you want to notify your user. Let's say that you want to notify your user every hour and the user has the sleep feature enabled, then you would setup 14-18 notifications (depending on how long the user sleeps) for each hour that repeat daily.

    SomeViewController.m:

    - (void)setupNotifications
    {
        [[UIApplication sharedApplication] cancelAllLocalNotifications];
    
        // Read the notification type from the notification type picker view
        NSInteger row = [self.notificationTypePickerView selectedRowInComponent:0]; // Assumes there is only one component in the picker view.
        NSString *notificationType = [self.notificationList objectAtIndex:row]; // Where notificationList is the array storing the list of notification strings that appear in the picker view.
    
        // If the user has turned the sleep feature on (via the UISwitch):
        if (self.sleepToggleSwitch.on) {
            // Set the first notification to start after the user selected 'noNotificationPeriodEndDate' and repeat daily.
            // Add 1 hour to the notification date
            // Do while the notification date < the user selected 'noNotificationPeriodStartDate' ...
            //     Create the notification and set to repeat daily
            //     Add 1 hour to the notification date
            // Loop
        } else {
            // Set up 24 repeating daily notifications each one hour apart.
        }
    }
    

    Keep in mind that a single app can only create a maximum of 64 notifications (with repeating notifications counting as one) so, if you want notifications to fire at different times over a period of days or weeks, you might have to rethink your design a little.

  5. Load and store the user's chosen preferences in NSUserDefaults:

    SomeViewController.m:

    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        // Load the user defaults
        NSDate *sleepStartDate = [[NSUserDefaults standardUserDefaults] objectForKey:@"SleepStartDate"];
        self.notificationIgnoreStartTime.date = sleepStartDate ? sleepStartDate : [NSDate date];
        NSDate *sleepEndDate = [[NSUserDefaults standardUserDefaults] objectForKey:@"SleepEndDate"];
        self.notificationIgnoreEndTime.date = sleepEndDate ? sleepEndDate : [NSDate date];
        self.sleepToggleSwitch.on = [[NSUserDefaults standardUserDefaults] boolForKey:@"SleepEnabled"];
    
        // Watch for when the app leaves the foreground
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(applicationWillResignActive)
                                                     name:UIApplicationWillResignActiveNotification object:nil];
    }
    
    - (void)applicationWillResignActive
    {
        // If the timer is still waiting, fire it so the notifications are setup correctly before the app enters the background.
        if (self.userOptionInteractionTimer.isValid)
            [self.userOptionInteractionTimer fire];
    
        // Store the user's selections in NSUserDefaults
        [[NSUserDefaults standardUserDefaults] setObject:self.notificationIgnoreStartTime.date forKey:@"SleepStartDate"];
        [[NSUserDefaults standardUserDefaults] setObject:self.notificationIgnoreEndTime.date forKey:@"SleepEndDate"];
        [[NSUserDefaults standardUserDefaults] setBool:self.sleepToggleSwitch.on forKey:@"SleepEnabled"];
    }
    

    Also, notice above that if the app is about to enter the background (ie. leave the foreground) and the timer is still ticking, we force it to fire so the notifications are set up before the timer is killed by the OS.

  6. Remember to hook up the delegate IBActions for all your date picker views and all your IBActions as well as the delegates and data sources for your picker view. Also remember to setup your delegate and data source methods so the picker view is populated.

  7. That's it!

The above design will ensure that notifications get fired at the correct times whether or not the app is in the foreground, background or terminated. (However if the app is in the foreground when the notification is received, the user will not receive a notification. Instead, application:didReceiveLocalNotification: on the appDelegate will be called).

Obviously the above code is not "execution-ready" but you should be able to fill in the gaps.

Upvotes: 2

geo
geo

Reputation: 1791

The UISwitch sets an attribute in a plist or the NSUserDefaults that can be checked everywhere, maybe activating and deactivating the Picker (userInteractionEnabled etc.).

Also connecting the UISwitch with an IBAction methode for ValueChanged to be able to switch always the status.

Further using the DidSelectRowAtIndexPath: methode of UIPickerView just to update the times (would also save them in a plist or NSUserDefaults if you need them in multiple places.
Or you use the attributes as class attributes (static) ^^

For automaticaly checking i would just use a NSTimer that runs circulary with 60 seconds in AppDelegate and fire an event that checks itself if it can continue or return, or check already here if you want to fire it (can extend to get sure you are firering the events not anywhere in the minute but at 00 seconds).
The sample for checking if you can fire or not could look something like this for the state

startSilentTimePicker : 10:30pm
stopSilentTimePicker : 7:15am
activitySwitch : on

// filtered date by only time
NSDateComponents *startComps = [[NSCalendar currentCalendar] components:NSHourCalendarUnit|NSMinuteCalendarUnit fromDate:[startSilentTimePicker date]];
NSDateComponents *endComps = [[NSCalendar currentCalendar] components:NSHourCalendarUnit|NSMinuteCalendarUnit fromDate:[stopSilentTimePicker date]];
NSDateComponents *currentComps = [[NSCalendar currentCalendar] components:NSHourCalendarUnit|NSMinuteCalendarUnit fromDate:[NSDate date]];

if([[startComps date] laterDate:[currentComps date]] == [currentComps date] || 
[[endComps date] earlierDate:[currentComps date]] == [currentComps date])
{
    // set the value, that tells you beeing in silent time, set a return from the methode or invert to fire notification here
}

haven't tested it actualy with my code, but I'm using something similar. If you are missing something, so tell please and maybe I can complete ;)

Upvotes: 1

Related Questions