Pause NSTimer when leaving that view and continue counting when come back? - ios

I'm new in xcode and objective-c and trying to developing an app. I would like to set up a timer in some of pages for calculating how much time user spend in that page. In the app, I have 5 theme pages, each pages contains a table view to another 3 sub-pages. I would like to add a counter to these 3 pages (5*3) but not including theme pages themselves. The page shift is controlled by navigation bar. I have put some codes as follows in .m file of viewcontroller.
- (void)viewDidLoad
{
[super viewDidLoad];
//timer
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self
selector:#selector(handleTimer)
userInfo:nil
repeats:YES];
}
-(void)handleTimer
{
MainInt += 1;
self.TimeLabel.text = [NSString stringWithFormat:#"%d",MainInt];
}
and some code in .h file (Brett is one of the 3 sub-pages.)
#interface Brett : UIviewController
{
NSTimer *timer;
int MainInt;
}
# property (weak, nonatomic) IBOutlet UILable *TimLable;
Every time when I leave the page and go back again the counter is always count from 0. Anyone can help to solve this problem??

Every time it Count start form 0, I guess because of you are creating Brett object every time when you push it to navigation.
Create Global Timer in appDelegate class and use it,
in viewWillAppear start timer and in viewWillDisappear pause that timer.

Alternative to the other answer, you can just make the MainInt a static variable
static int MainInt;

What I understood from your question is to simply keep track of the amount of time a user spend in sub pages. If my understanding is correct, then you may try following.
Create and start timer as like now you are doing.
On viewWillDisAppear method of your controller, just update the global variable or NSUserDefaults value like this:
//Get the previous time available in userdefaults
int counter = [[NSUserDefaults standardUserDefaults] integerForKey:#"ScreenACounter"];
//Update the key with existing counter value + current timer value.
[[NSUserDefaults standardUserDefaults] setInteger:counter + currentTimer.Value forKey:#"HighScore"];
Hope this helps.

Related

Xcode - Can not update UILabel in NSTimer on tick

I have a label that I want to update with the number of seconds that the user has left to complete a certain task. I decided to use an NSTimer which ticks every second, and calls the function updateTime(), which will decrement the property containing the # of seconds left, and then update the label. However, the label is not being updated. I get no errors, nor output. Anything I try to change with the label within updateTime() does not work: mylabel.text, mylabel.frame, mylabel.font, etc... Anywhere else though, I'm able to modify the label normally. Here is my code:
// ViewController.h
#interface ViewController : UIViewController
{
NSTimer* timer;
int secs;
UILabel* timerText;
}
#end
// ViewController.m
- (void)updateTime
{
secs--;
timerText = (UILabel*)[self.view viewWithTag:4]; // i have also tried putting this line in viewDidLoad. i also tried removing the property in the .h file and just doing UILabel* timerText = ....
NSString* updatedTime = [NSString stringWithFormat:#"%d", secs];
timerText.text = updatedTime;
}
- (IBAction)startButton_pressed:(id)sender {
timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(updateTime)
userInfo:nil
repeats:YES];
}
EDIT: I am getting a "-[UIView setText:]: unrecognized selector sent to instance" error now. NSLog(#"%#", timerText) outputs ">" so it is not null. Under this, I get "-[UIView setText:]: unrecognized selector sent to instance 0x9767b60
" What is going on? What should I do about this? I tested similar code on a fresh project and it worked, no idea what's going on here. Could having two view controllers in my project possibly be causing this?
Thanks for all your replies everyone. I fixed it by creating a second ViewController class and putting all the initializations in its viewDidLoad.

Call custom Class' obj of type NStimer into viewController

In the sample tutorial I found, I noticed a timer made using NStimer class: it was implemented directly into viewController.m
I tried to make it as "separate" obj class in it's own timer.m and relative header.
This is what i got
#import "Timer.h"
#implementation Timer
-(void) startTimer{
seconds = 31;
myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(subtractTime:) userInfo:nil repeats:YES];
NSLog(#"%i",seconds);
}
-(void)subtractTime: (Timer*)myTimer{
seconds--;
NSLog(#"seconds %i",seconds);
if (seconds == 0) {
[myTimer invalidate];
}
}
#end
According to the output in NSLog, the countdown itself works perfectly; my issues start when i try to display it into a label using the form label.text = into the viewController.m
(here's just the method implementation part)
(Xcode gives me no error bout #implementation part both in Timer.h and viewController.h, also project build runs ok but the countdown into the label is locked to 0)
-(void)setupGame{
count = 0;
scoreLabel.text = [NSString stringWithFormat:#"Score:\n%i",count];//these strings are to make a score label increasing of 1 every time a button's pressed
Timer *newTimer = [[Timer alloc]init]; //i create a new obj of my Timer class
[newTimer startTimer];
[newTimer subtractTime:(Timer*) myTimer]; //i set the methods i created
timerLabel.text = [NSString stringWithFormat:#"%i",seconds]; //??? i don't know which var put here, actually, i cannot get here the refreshing seconds to make timerLabel changing
NSLog(#"newTimer %#",myTimer);//just a try to see this output, it's not what i need of course
}
What am i doing wrong?
There are a couple of issues here. Firstly make Timer an instance variable of the view controller as when you do this:
-(void)setupGame{
// blah
Timer *newTimer = [[Timer alloc]init];
// blah
}
The Timer object will be destroyed when this method returns, as the object goes out of scope.
The second problem is that you need to update the UI control when the time changes, so perhaps pass a reference to the UI control (UILabel or whatever it is), and store this as an instance variable of the Timer class, so the timer object can update it itself.
In this particular case I don't see the value of a separate Timer object as it would be easier to implement what you want just within the view controller class.
As Trojanfoe says, you are over-complicating this. Make your view controller create the timer and be done with it.
If you are bound and determined to create a custom class to manage a timer then you have a bunch of work to do.
I would NOT pass the label to the Timer object, as that breaks the encapsulation of the view controller. You should always treat a view controller's views as private to that view controller.
Instead, here's what I would do:
You need to add a Timer instance variable to your view controller, as Trojanfoe says.
You need to define a TimerProtocol. In that protocol, define a "timerFired" method, that would include the remaining time value as a parameter. You probably also want a "timerFinished" method
When you create a Timer object, set yourself up as the delegate of that timer.
Rewrite your Timer object's subtractTime method to send a message to the delegate with the remaining time value. Then in the view controller's timerFired method, update the label.
In your view controller's timerFinished method, nil out the instance var to the Timer object so it will be deallocated, since you then done with it.
Again, this is an over-complicated solution that does not add any value. The only reason to do it this way is as a learning exercise, but it is not a good design decision.

how to stop timers in tabbar's three viewcontrollers from single viewcontroller in iphone?

Hi i am developing an iphone applicaiton, in my application i have a scenario like a UItabbar controller having two UIviewcontrollers. in each view controller i have seperate nstimer.
I want to know that how to stop all timers from one of the view controller.
My timer code as follows
-(void)startTimer
{
if (_timer == nil)
{
_timer = [NSTimer scheduledTimerWithTimeInterval:10.0f
target:self
selector:#selector(setProgress)
userInfo:nil
repeats:YES];
}
}
- (void)stopTimer
{
if (_timer != nil)
{
[_timer invalidate];
_timer = nil;
}
}
-(void)setProgress
{
[self stopTimer];
[self initmethod];
}
if i navigate to any other page from one of the uiviewcontroller i just stoped and invoks by callback method. if i navigate to other page from a one of the uiviewcontroller means i want to stop all timers in all three uiviewcontroller.
Subclass uitabbarcontroller and add a method 'stopAllTimers'. That method will iterate through its array of viewControllers, sending each a 'stopTimer' message.
of cause, you could use NSNotification's in all the VC's
but if your repeating timers are mostly needed for updates of the VC's, the better way would be to save "last seen" time for each of them (in viewDidDisappear) and check if that time saved is distant enough in viewWillAppear method
There is not a direct way to do this.
But, you may consider using a shared bool variable ,say shouldStopTimer, which can be accessed in all viewControllers.
Add a check in your "setProgress" method if shouldStopTimer is NO.
Invalidate the timer if you find shouldStopTimer as YES in you "setProgress" method.
Else
You can use NSNotification to invalidate the timers in their respective viewControllers.

invalidating and creating NSTimer

Everytime I am finished with my NSTimer, I want to invalidate it and create a new interval but it keeps the old interval as well as new interval. I want to invalidate NSTimes once I click the offButton. The timer stops printing "Working" but when I call my method with a different interval, it prints "Working" for both intervals.
My code is something like this:
-(void) fireTimer{
NSString *textValue = [sliderLabel text];
float value = [textValue floatValue];
[NSTimer scheduledTimerWithTimeInterval:value target:self selector:#selector(vibrate:) userInfo:nil repeats:YES];
}
- (void) vibrate:(NSTimer*)timer {
if(_offButton.selected){
[timer invalidate];
timer=nil;
}
NSLog(#"Working");
}
You aren't following the MVC design pattern by getting your values directly from the UITextField. Those values should be stored in a model object, with the MVC pattern being used to get any new values from the text field into the model. Your current implementation is very delicate and will break in the slightest breeze. It also requires this code to have access to the UI elements, which is very inflexible; it will be better to give it access to just the model object.
Store the NSTimer * as an instance variable, and note that if you are using ARC then the NSTimer retains the target (!!) so make this instance variable __weak to break the retain-cycle.
If you want your timer to repeat then there is no need to reset it at all; this only needs to be done if the user changes the time (see point 1!).
Move the if (button_selected) do_vibrate; code into the timer fired method.
The invalidation code that you use itself is correct. But it would be easier for you to keep a reference to your timer as an ivar or property.
In that case you would definetly avoid making multiple instances of a timer.

How do I invalidate timer when I don't create NSTimer object

I don't want to create NSTimer object. How do I invalidate timer? I want to invalidate timer in viewWillDisappear.
-(void) viewDidLoad
{
[super viewDidLoad];
[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(onTimer:) userInfo:nil repeats:YES];
}
A
you have to hold on to the timer you create:
#interface MONObject ()
#property (nonatomic, retain) NSTimer * timerIvar;
#end
#implementation MONObject
...
- (void)viewDidLoad
{
[super viewDidLoad];
self.timerIvar = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(onTimer:) userInfo:nil repeats:YES];
}
- (void)invalidateTimer
{
[self.timerIvar invalidate];
self.timerIvar = nil;
}
- (void)viewWillDisappear:(BOOL)animated
{
...
[self invalidateTimer];
}
B
another option would be to invalidate the timer that is passed in the callback, but that won't occur within viewDidUnload:. therefore, it doesn't quite apply in this scenario:
- (void)onTimer:(NSTimer *)pTimer
{
[pTimer invalidate];
}
If you want to be able to cancel the timer, you have to refer to the timer you’re cancelling, and that means you have to keep the pointer to the timer around, see justin’s answer.
Keeping a reference to the timer is the right way to do it, but for the sake of completeness you may also use the -performSelector:withObject:afterDelay: method as a poor man’s timer. That call may be invalidated using +cancelPreviousPerformRequestsWithTarget:. Sample code:
- (void) viewDidLoad
{
[super viewDidLoad];
[self performSelector:#selector(timerTick) withObject:nil afterDelay:10];
}
And then:
- (void) viewWillDisappear
{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[super viewWillDisappear];
}
But this is not the right way to do it, because there might be other perform-selector requests pending on your object that you would cancel. It’s best to keep your timer around, that way you know exactly what you’re cancelling.
By the way, it’s also probably a bad idea to run a timer in -viewDidLoad. View loading may happen anytime, without any relation to view being displayed.
Maybe this method can help you:
[self performSelector:#selector(onTimer:) withObject:nil afterDelay:10];
If you don't want to hold on to your timer, the NSTimer object will be passed to the timer method (in your case onTimer:), so in that method you could check whether the timer is still needed and invalidate it. However, you will run into trouble if the view comes back before you invalidated the timer, and you create a new one.
By far the best way is to store the timer into an instance variable. It works, no clever tricks, and you'll know six months later what you did. I'd probably write a
#property (readwrite, nonatomic) BOOL hasTimer;
getter returns YES iff the timer is not nil, setter invalidates the timer or creates a new one.

Resources