I have a UILabel that randomly will show text from a list I have provided.
I want the UILabel to show one item per day.
What's the best way to handle this?
Should I use an NSTimer or is there a different method?
I'm not worried about a specific time of the day, just that the UILabel updates once per day.
Thank you!
One option would be to save the current date into NSUserDefaults when you show the label.
When your view controller is loaded you get that date from NSUserDefaults. If the difference between the saved date and "now" is more than 24 hours you update the label (and save the new date), otherwise show the current label.
You probably also want the view controller to listen for the "will enter foreground" notification. Each time your app returns to the foreground you will want to do the same check.
Store the date in preferences, and compare when the app comes into the foreground. Your appDelegate would look something like this:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs setObject:[NSDate date] forKey:#"savedDate"];
[prefs synchronize];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
NSDate *savedDate = [[NSUserDefaults standardUserDefaults] objectForKey:#"savedDate"];
NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSCalendarUnitDay fromDate:savedDate toDate:[NSDate date] options:0];
if ([dateComponents day] >= 1) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"updateLabel" object:nil];
}
}
Then in your view controller, listen for the notification:
-(void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateLabel) name:#"updateLabel" object:nil];
}
-(void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
-(void) updateLabel {
//update your label here
}
For updating at midnight, check out UIApplicationSignificantTimeChangeNotification. There is a relevant answer here: https://stackoverflow.com/a/15537806/1144632
Related
Hi i know this question maybe silly but anyway i ask it now.
i have this function :
- (int)getToday {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:#"fa_IR"];
[dateFormatter setDateFormat:#"dd"];
int day = [[dateFormatter stringFromDate:[NSDate date]] intValue];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(handleSysTimeChanged:)
name:NSSystemClockDidChangeNotification
object:nil];
return day;
}
and after that i handle notification like this :
-(void) handleSysTimeChanged: (NSNotification*) notification
{
if (NSSystemClockDidChangeNotification) {
NSLog(#"%i", [self getToday]);
}
}
and i get exactly the changes i want in my NSLOG. BUT after that how can i update my getToday to show new number after notification happened.
I'm new to objective-c and NSNotificationCenter. So don't be mad.
Firstly, as #rmaddy said, you should not observe for notifications inside of getToday. This code here is the problem:
[nc addObserver:self
selector:#selector(handleSysTimeChanged:)
name:NSSystemClockDidChangeNotification
object:nil];
You only want to set up your observer once and if you call getToday on each notification, it's going to be called a ton of times. Repetitive addObserver calls CAN break things. Set this up inside of applicationDidFinishLaunchingWithOptions or your class's initialization method. Also, don't forget to call removeObserver before your application terminates or you'll get some really weird errors.
Secondly, I don't think what you're doing is broken. Based on your provided code, you're getting the current date with [NSDate date] each time that the notification is received. This should provide you with the correct day as desired.
I have app that stores system events to Core Data Database. To perform saving I use MagicalRecord.
So, in my logger class in init I have:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleDidFinishLaunchingNotification) name:UIApplicationDidFinishLaunchingNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleWillTerminateNotification) name:UIApplicationWillTerminateNotification object:nil];
In handle functions I store simple entity with text and timestamp property:
- (void)handleDidFinishLaunchingNotification
{
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
DBMessage *dbMessage = [DBMessage createEntityInContext:localContext];
dbMessage.text = #"Did finish launching";
dbMessage.timestamp = [NSDate date];
}];
}
- (void)handleWillTerminateNotification
{
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
DBMessage *dbMessage = [DBMessage createEntityInContext:localContext];
dbMessage.text = #"Will terminate";
dbMessage.timestamp = [NSDate date];
}];
}
When I open and close (without crash) app few times, in my DB I can see "Did finish launching" entries (more that one, so I'm sure app was closed, not only moved to BG), but none of "Will terminate".
I would be less surprised if the launching event were missed, because I could expect that init method will be called after notification is posted.
What I can do to store terminate events?
I have an answer:
It looks like when application gets terminated, wont't let me to save in new background thread. But saving in current thread works fine. So all I had to do was change saving method to save in current thread, by calling saveWithBlockAndWait: instead of saveWithBlock:
- (void)handleWillTerminateNotification
{
[MagicalRecord saveWithBlockAndWait:^(NSManagedObjectContext *localContext) {
DBMessage *dbMessage = [DBMessage createEntityInContext:localContext];
dbMessage.text = #"Will terminate";
dbMessage.timestamp = [NSDate date];
}];
}
Now events are succesfully saved on DB.
How would I get the length of time a phone was locked if I wanted to use it to increment a timer progress view when the phone was resumed, or schedule a notification to fire when the phone was still locked?
Implement the UIApplicationDelegate method applicationWillResignActive: and applicationDidBecomeActive:.
You will have to store the current time when the former is called and calculate the difference when the latter is called. Specifically, in your application delegate:
#define TIMESTAMP_KEY #"timestamp"
- (void)applicationWillResignActive:(UIApplication *)application
{
NSInteger *timestamp = [[NSDate date] timeIntervalSince1970];
[[NSUserDefault standardUserDefaults] setInteger:timestamp forKey:TIMESTAMP_KEY];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
NSInteger *newTimestamp = [[NSDate date] timeIntervalSince1970];
NSInteger *oldTimestamp = [[NSUserDefault standardUserDefaults] integerForKey:TIMESTAMP_KEY];
NSInteger *secondsPassed = newTimestamp - oldTimestamp;
// Now you can resynch your timer with the secondsPassed
}
It seems that
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
and
didReceiveLocalNotification:(UILocalNotification *)notification
are only triggered if the user acknowledges the UILocalNotification, for example by swiping the slider or touching the entry in iOS's Notification pull-down.
Is there any way to tell that a UILocalNotification has gone off if the user ignores the UILocalNotification and re-enters the app by simply clicking on the app icon?
I should mention that this really only applies to repeating notifications because the firing of non-repeating notifications can be detected by observing the total count. That is, when they fire, the vanish from [[UIApplication sharedApplication] scheduledLocalNotifications].
I'm looking for something like..
[[UIApplication sharedApplication] unacknowledgedLocalNotifications]
Alas, I can't find anything like it.
Well, you can check your scheduled notifications inside [[UIApplication sharedApplication] scheduledLocalNotifications]. To find out if a scheduled repeating notification has fired access the fireDate property to see what was the initial date set for the notification. Then check the repeatInterval property.
So there you have 2 variables, one is the initial NSDate, lets say 2013-05-08 12:00 and second is the repeat interval, lets say daily. And by doing a [NSDate date] you will get the current date which where I'm located (in Sweden) is now 2013-05-09 22:45. So this means that there is one notification the user has not acted on.
So you will need to create a method that will take these arguments and then iterate from the initial date to see how many notifications that have been missed until the current datetime.
You will find NSCalendars dateByAddingComponents:toDate:options useful.
Everyone has likely since moved on, but I'd like to share my solution to this problem. (Sorry about the long variables names...)
The idea is simple: always keep the fireDate in the future.
-every time didFinishLaunchingWithOptions or didReceiveLocalNotification is invoked, simply cancel your current notification and reschedule a new one with a fireDate one interval unit in the future
-When your app launches iterate through all scheduled notifications, if the fireDate is not in the future you know that it was ignored
In my case, the notifications have a weekly repeat interval. I first reschedule any acknowledged notifications in didFinishLaunchingWithOptions:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UILocalNotification* localNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (localNotif != nil)
{
[NotificationsHelper rescheduleNotification:localNotif];
}
}
And also in didReceiveLocalNotification:
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *) notification
{
[NotificationsHelper rescheduleNotification:notification];
}
At App Launch I check all notifications for any with a fireDate in the past:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[self checkLocalNotifications:application];
}
Code for my "checkLocalNotifications" function:
- (void) checkLocalNotifications:(UIApplication *) application
{
UIApplication* app = [UIApplication sharedApplication];
NSArray* eventArray = [app scheduledLocalNotifications];
for (int i = 0; i < [eventArray count]; i++)
{
UILocalNotification* notification = [eventArray objectAtIndex:i];
if ([NotificationsHelper wasWeeklyRepeatingNotificationIgnored:notification])
{
[NotificationsHelper rescheduleNotification:notification];
NSLog(#"NotificationWasIgnored: %# %#",notification.alertAction, notification.alertBody );
}
}
}
Code for my "wasWeeklyRepeatingNotificationIgnored" function:
+ (BOOL) wasWeeklyRepeatingNotificationIgnored:(UILocalNotification*) the_notification
{
BOOL result;
NSDate* now = [NSDate date];
// FireDate is earlier than now
if ([the_notification.fireDate compare:now] == NSOrderedAscending)
{
result = TRUE;
}
else
{
result = FALSE;
}
return result;
}
Code for my "rescheduleNotification" function:
+ (void) rescheduleNotification:(UILocalNotification*) the_notification
{
UILocalNotification* new_notification = [[UILocalNotification alloc] init];
NSMutableDictionary* userinfo = [[NSMutableDictionary alloc] init];
[new_notification setUserInfo:userinfo];
[new_notification setRepeatInterval:the_notification.repeatInterval];
[new_notification setSoundName:UILocalNotificationDefaultSoundName];
[new_notification setTimeZone:[NSTimeZone defaultTimeZone]];
[new_notification setAlertAction:the_notification.alertAction];
[new_notification setAlertBody:the_notification.alertBody];
[new_notification setRepeatCalendar:[NSCalendar currentCalendar]];
[new_notification setApplicationIconBadgeNumber:the_notification.applicationIconBadgeNumber];
NSCalendar* gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents* weekdayComponents = [gregorian components:NSWeekdayCalendarUnit
fromDate:the_notification.fireDate];
NSInteger weekday = [weekdayComponents weekday];
NSDate* next_week = [self addDay:weekday toHourMinute:the_notification.fireDate];
[new_notification setFireDate:next_week];
[[UIApplication sharedApplication] scheduleLocalNotification:new_notification];
[[UIApplication sharedApplication] cancelLocalNotification:the_notification];
}
If your UILocalNotifications increment the application icon badge number (i.e. the number in the red circle on the top right of the app's icon), then there is a ridiculously simple way to check for unacknowledged UILocalNotifications: just check what the current applicationIconBadgeNumber is:
- (void)applicationWillEnterForeground:(UIApplication *)application
{
int unacknowledgedNotifs = application.applicationIconBadgeNumber;
NSLog(#"I got %d unacknowledged notifications", unacknowledgedNotifs);
//do something about it...
//You might want to reset the count afterwards:
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
}
I´m working on an application in which, when it is pushed in background via the home button, a timer should start and when the application gets back to foreground and the timer has passed a certain amount of time, something should be executed.
My questions are
How do I handle the events when my app goes to
background/foreground?
Is there a special method or an other technique?
Thanks a lot.
A possible implementation could look like:
#define YOUR_TIME_INTERVAL 60*60*5 //i.e. 5 hours
- (void)applicationDidEnterBackground:(UIApplication *)application
{
//... your oder code goes here
NSNumber *timeAppClosed = [NSNumber numberWithDouble:[[NSDate date] timeIntervalSince1970]];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults timeAppClosed forKey:#"time.app.closed"];
[defaults synchronize];
}
and
- (void)applicationWillEnterForeground:(UIApplication *)application
{
NSNumber *timeAppClosed = [[NSUserDefaults standardUserDefaults] valueForKey:#"time.app.closed"];
if(timeAppClosed == nil)
{
//No time was saved before so it is the first time the user
//opens the app
}
else if([[NSDate date] timeIntervalSinceDate:[NSDate dateWithTimeIntervalSince1970:[timeAppClosed doubleValue]]] > YOUR_TIME_INTERVAL)
{
//Place your code here
}
}
In the appDelegate of the app you have some delegate methods which you can implement.
You can check out the UIApplicationDelegate protocol to which you AppDelegate should conform.
When the app is pushed in the background the function applicationDidEnterBackground: will be called. When entering the foreground applicationWillEnterForeground: is called.
Better not use a timer, but store a NSDate reference in the applicationDidEnterBackground: method. When your app is entering the foreground you can calculate the timeDifference using the stored NSDate using the
- (NSTimeInterval)timeIntervalSinceDate:(NSDate *)anotherDate
function.