I totally understand if they are, but what I'm looking for is a timer that pauses when the application enters the background and unpauses after the user returns to the app. I do not need a background task; I just want to make sure that after approximately x minutes within the app a certain action occurs whether that is today or tomorrow.
Thanks!
Brett
Backgrounding the app (assuming you have no background task) doesn't "pause" the timer. It's still counting down in theory so if the app is reopened, it will fire if enough time has passed. This goes for NSTimer's as well. (Let me know if you want more details as to why and I'll edit the answer).
Consider using the following code:
#implementation MyCustomClass {
int elapsedTime;
NSTimer *timer;
}
- (id) init {
if ( ( self = [super init] ) ) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationEnteredBackground)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationEnteredForeground)
name:UIApplicationDidBecomeActiveNotification
object:nil];
}
return self;
}
- (void) applicationEnteredForeground {
timer = [NSTimer timerWithTimeInterval:1
target:self
selector:#selector(timerTicked)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
- (void) applicationEnteredBackground {
[timer invalidate];
}
- (void) timerTicked {
elapsedTime += 1;
// If enough time passed, do something
}
Related
My application is a quizz game. The user has a limited time to answer the question.
A timer is used for that. When the time runs out, a simple sound is triggered.
NSTimer *m_timer;
In function viewDidAppear:
m_timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(decrementSpin) userInfo:nil repeats:YES];
In my fisrt version, I encountered the following situation:
If during a question, an incoming call interrupts the game, the timer was still counting during the call.
I fixed this problem by adding in function viewDidLoad:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appDidEnterInBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
- (void)appDidEnterInBackground:(NSNotification *)notification {
[[SoundManager sharedManager]stopMusic:NO];
[m_timer invalidate];
m_timer = nil;
}
- (void)appWillEnterForeground:(NSNotification *)notification {
if(m_timer) {
[m_timer invalidate];
m_timer = nil;
}
//NSLog(#"%d", self.TimerbackgroundView.percent);
m_timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(decrementSpin) userInfo:nil repeats:YES];
}
The function decrementSpin updates the clock image and plays the sound if the player has run out of time.
Everything works well.
Since my last version, I added a feature. The user can report a question (for incorrect content) by pressing a button.
When a button is pressed it opens the mail app with a prefilled content.
MFMailComposeViewController*mailComposerVC = [[MFMailComposeViewController alloc] init];
if ([MFMailComposeViewController canSendMail]) {
mailComposerVC.mailComposeDelegate = self;
[mailComposerVC setToRecipients:#emailAddress];
[mailComposerVC setSubject:emailSubject];
[mailComposerVC setMessageBody:emailBody isHTML:NO];
[self presentViewController:mailComposerVC animated:YES completion:^{
}];
}
else{
NSLog(#"Unable to send message");
}
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error{
[controller dismissViewControllerAnimated:YES completion:^{
}];
}
It seems that my application doesn't handle correctly when the user sends the mail and returns to the Question page.
The timer doesn't pause (like the incoming call case), and in addition, when the Question page re-appears the clock-image shows the initial image (exactly like when the page appears for the first time)
This bug causes the sound (the one triggered when the user runs out of time) to be played during the next Page.
The only thing that comes in my mind that the events notified by UIApplicationDidEnterBackgroundNotification and UIApplicationWillEnterForegroundNotification are not covered in the case of MFMailComposeViewController.
Any idea ?
You are correct. The app is still running the in the foreground.
So a possible solution would be to execute the same code of appDidEnterInBackground when opening the Mail.app, and to do the same as appWillEnterForeground in the body of
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error{
[controller dismissViewControllerAnimated:YES completion:^{
}];
}
What do you think ?
I've encountered problem with closing the app while in background.
When working in the background, tapping 2x and swiping the app to close it, the app doesn't call the applicationWillTerminate:(UIApplication *)application, but goes to #autoreleasespool, which is definitely crash.
I suppose it is connected with working NSTimer, because without initializing it the app closes properly from background.
I wanted to disable the NSTimer in the appDelegate with no success, since NSTimer can be disabled only from the same thread.
I am starting NSTtimer in init of my class:
[NSTimer scheduledTimerWithTimeInterval:5.0f target:self selector:#selector(checkLastSeenTimeofPeripheral) userInfo:nil repeats:YES];
I wanted to stop it while going to background using the answer given here, but still it doesn't seem to stop the timer and the app crashes on termination.
EDIT:
Initializing in myClass
- (id)init {
self = [super init];
if(self){
//check the connection timer
[self startCheckLastSeenTimeOfPeripheralTimer];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(stopCheckLastSeenTimeOfPeripheralTimer) name:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(startCheckLastSeenTimeOfPeripheralTimer) name:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication]];
methods to start/stop timer
-(void)startCheckLastSeenTimeOfPeripheralTimer {
_checkLastSeenTimeOfPeripheralTimer = [NSTimer scheduledTimerWithTimeInterval:5.0f
target:self
selector:#selector(checkLastSeenTimeofPeripheral)
userInfo:nil
repeats:YES];
NSLog(#"checkLastSeenTimeofPeripheralTimer started");
}
-(void)stopCheckLastSeenTimeOfPeripheralTimer {
if (_checkLastSeenTimeOfPeripheralTimer) {
[_checkLastSeenTimeOfPeripheralTimer invalidate];
_checkLastSeenTimeOfPeripheralTimer = nil;
NSLog(#"checkLastSeenTimeofPeripheralTimer stopped");
}
else {
NSLog(#"checkLastSeenTimeofPeripheralTimer not initialized - can't stop");
}
}
According to documentation appWillTerminate is not being called when closing suspended App: Suspended apps receive no notification when they are terminated; the system kills the process and reclaims the corresponding memory.
Apps get suspended by the system while in background without informing about it.
I am making an iOS application that uses wireless communication. One of its features is checking if the external devices that it is connected with are responding. So what I tried to do, is to make a "Device" class for every connected device, and then for each of them create a NSTimer that would handle the timeouts. And I made it like this:
The "Device" class init:
NSTimer* communicationChecker;
- (id)initWithAddress: (NSString*) address;
{
self = [super init];
if (self)
{
_address = address;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateStatus:) name:NOTIFICATION_STATUS object:nil];
communicationChecker = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(iAmDead:) userInfo:nil repeats:YES];
self.readyToRoll = true;
}
return self;
}
The timer selector:
- (IBAction)iAmDead:(NSTimer*)sender
{
self.readyToRoll = false;
NSLog(#"%# is dead :(", self.address);
}
And the notification selector:
-(void)updateStatus:(NSNotification *) notification
{
NSDictionary* userInfo = notification.userInfo;
NSString* deviceAddress = (NSString*)userInfo[PARAM_DEVICE_ADDRESS];
if ([_address isEqualToString:deviceAddress]) {
self.readyToRoll = true;
[communicationChecker invalidate];
communicationChecker = nil;
communicationChecker = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(iAmDead:) userInfo:nil repeats:YES];
}
}
So how I thought this would work, is that every time that a notification comes for that given device, it would change its "readyToRoll" variable and reset the timer. The problem is that only one device declares that it's dead (when none of them report status), and it's the one that sent the last status report message. I really have no clue how to go about this. What causes this behaviour?
I have solved the problem by moving the NSTimer declaration from the .m file, to .h. By adding the NSTimer as a property (#property NSTimer* communicationChecker;) it is initiated for each device. Everything works as expected now.
I think that the NSTimer was initiating only once earlier, and was only restarted with different parameters. Now each device has its own timer.
Now this might be total wrong, but did you try to initialize your timers like this :
NSTimer *timer = [NSTimer timerWithTimeInterval:kUpdateTimeInterval
target:self
selector:#selector(doStuff)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
That way you ensure that they are added to the mainRunLoop, and not the "currentLoop" as done in the scheduledTimerWithTimeInterval function.
Try and let me know.
I need some UIViewControllers that receive a NSNotification from the app delegate. It's like a timer, but every UIViewController handle your way. My problem is: when I interact with user interface, the UIViewController doesn't receive the notification, causing problems.
Here is my code in AppDelegate:
-(void)updateCounter:(NSTimer *)theTimer{
[[NSNotificationCenter defaultCenter] postNotificationName:TimeTickNotification object:nil];
}
//*called by some trigger in the app
-(void) startTimer{
timer = [NSTimer
scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(updateCounter:)
userInfo:nil
repeats:YES];
}
I am handling the notifications in each UIViewController like this:
-(void) updateGlobalTime:(NSNotification *) notification{
totalTime = [NSNumber numberWithFloat:([ficha.tempoTotal floatValue] + STEP)];
}
-(void) viewWillAppear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateGlobalTime:)
name:TimeTickNotification
object:nil];
}
What should I do to interact with UI and update it at same time? Maybe the NSNotification is not being thrown while user interacts with UI.
You need to make sure you're updating any UI on the main thread. If you want to update the UI to have the new totalTime in a label or something, make sure the setText: function is running on the main thread. You can accomplish that with GCD, like this:
-(void) updateGlobalTime:(NSNotification *) notification{
totalTime = [NSNumber numberWithFloat:([ficha.tempoTotal floatValue] + STEP)];
// Update label on main thread
dispatch_async(dispatch_get_main_queue(), ^{
[label setText:totalTime];
});
}
The solution was to use NSRunLoop, as following:
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
timer = [NSTimer
scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(updateCounter:)
userInfo:nil
repeats:YES];
[runloop addTimer:timer forMode:NSRunLoopCommonModes];
[runloop addTimer:timer forMode:UITrackingRunLoopMode];
I want to update an UISlider according to a notification generated by another singleton class.
sondDuration=audioPlayer.currentItem.asset.duration;
songDurationinSeconds=CMTimeGetSeconds(sondDuration);
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateTime:) userInfo:nil repeats:YES];
This is my notofication generation.
According to this how I can update my UISlider in another ViewController Please help me.
You can use Delegate Method For that
Like the given Below:
http://www.roostersoftstudios.com/2011/04/12/simple-delegate-tutorial-for-ios-development/
I think you have to add a NSNotification in your viewcontroller which updates the UISlider
In your viewcontroller
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveSliderUpdate:)
name:#"UpdateSlider"
object:nil];
- (void) receiveSliderUpdate:(NSNotification *) notification
{
// [notification name] should always be #"UpdateSlider"
// unless you use this method for observation of other notifications
// as well.
if ([[notification name] isEqualToString:#"UpdateSlider"])
// do something with your slider
}
In your controller add the code to notify your view controller
[[NSNotificationCenter defaultCenter]
postNotificationName:#"UpdateSlider"
object:self];