I am trying to make a timer app. I am fine with Play button but I couldnt get Pause button working. I have seen some tutorials on Timer Apps and most of them have only used: [timer invalidate] code for that method that solely stops the time that is currently being shown in the label (display). Even that code doesn't work for me so I tried doing this which makes kinda more sense but still, of no luck.
#implementation ViewController
int timerCounter=0;
NSTimer *timer;
NSString *label;
BOOL isPaused=NO;
-(IB Action) playButton:(id)sender{
[timer invalidate];
isPaused=NO;
timer=[NSTimer scheduledTimerWithTimeInterval:1 target:self selector: #selector(tick) userInfo:nil repeats:YES];
}
-(IBAction) pauseButton:(id)sender{
[timer invalidate];
isPaused=YES;
label=[NSString stringWithFormat:#"%d",timerCounter];
_labelTimer.text=label;
}
-(void) tick{
if (isPaused==NO){
timerCounter++;
}
label=[NSString stringWithFormat:#"%d",timerCounter];
_labelTimer.text=label;
}
The NSTimer API do not have any method for pausing. What is available is either fire or invalidate. About your code, You are using global variables - not a good practice, most probably the instance of timer you are calling is not the same, remove them and add a property in the class extension in .m instead:
#property (nonatomic, strong) NSTimer * timer;
you then address that property with self.timer.
If this does not help, check if the button call the method when you press it.
Related
is it a good idea to use performSelector:withObject:afterDelay for animation purpose when UIView or CALayers are just not available?
More specifically I am working with MKOverlayRenderer which can display something like an UIView does. I'd like to animate a movement of this MKOverlayRenderer (just think of a progress bar). In my implementation I just repeatedly send performSelector:withObject:afterDelay to update the position of the MKOverlayRenderer in very short time intervals.
It works quite well but I am concerned about the performance since the CPU usage rises from ~0% to 15% (if I set the delay to 0.5 seconds) and 90% (delay = 0.02).
Are there are any other options or can I just ignore the CPU peaks?
I'm not sure of anything specifically for MKOverlayRenderer, but you can create an NSTimer like so:
#property (nonatomic, strong) NSTimer *myTimer;
And then instantiate it:
self.myTimer = [NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(myMethod)
userinfo:nil
repeats:YES];
and then your method:
- (void)myMethod {
//do whatever MKOverlayRenderer animation you need
}
and then to stop the timer:
[self.myTimer invalidate];
self.myTimer = nil;
But just remember that the CPU overload is from the animation you're running, not the timer.
I have an iOS App that provides test for users with time limit.
The test will span across multiple view controllers, where these view controllers may re-open during the test flow. I think of the following flow:
In AppDelegate.h, add a NSTimer and time spent in float:
#property (strong, nonatomic) NSTimer *timer;
#property (nonatomic) float timeSpent;
- (void)startTimer;
- (void)stopTimer;
Without forgetting #synthesize the above, make a start & stop timer function in AppDelegate.m:
- (void)startTimer {
self.timer = [NSTimer scheduledTimerWithTimeInterval: 0.1f target: self
selector: #selector(updateTime) userInfo: nil repeats: YES];
}
- (void)stopTimer {
[self.timer invalidate];
}
In the periodically-called updateTime function, increment the value of timeSpent by 1.
Last, in each View Controller, obtain the timeSpent value and format it with the format I want, such as "01min 56s", or "01:56".
Is there a simpler way to do so?
Note:
No Internet connection available, and the test will last for around 10 minutes only; thus using Google Analytics is an overkill & not applicable in this case
You are correct that you need one NSTimer and variable to track the global time and..
What you are proposing, using the app delegate as a glorified singleton will work..
But please don't, this is not very good practice. This blog post has a nice brief description on why, in my opinion at least.
http://www.cocoawithlove.com/2008/11/singletons-appdelegates-and-top-level.html
Personally if it was me I would probably just use a dependency injection model and pass the NSTimer between the viewControllers. http://www.objc.io/issue-15/dependency-injection.html
In short, app delegate is probably the easiest and quickest way. But I would advise something a little more scalable if it is going to be anything other than a trivial app.
Hope this was useful :)
*Edit Sample code for singleton.
This should go at the top of singleton class to initialise it.
#interface
#property (nonatomic, strong) NSTimer *myTimer;
#end
#implementation MySingletonClass
+ (instancetype)shared
{
static MySingletonClass *_shared = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_shared = [[self alloc] init];
// any other initialisation you need
});
return _shared;
}
- (void)startTimer {
self.myTimer = [NSTimer scheduledTimerWithTimeInterval: 0.1f target: self
selector: #selector(updateTime) userInfo: nil repeats: YES];
}
- (void)stopTimer {
[self.myTimer invalidate];
}
then you can access it from any other classes in your program like this
#import "MySingletonClass.h"
//some method
- (void)myMethod
{
CGFloat currentTime = [MySingletonClass shared].globalTimeProperty;
// do something with the time
}
-(void)startTimer
{
[[MySingletonClass shared] startTimer];
}
-(void)updateTime
{
// do your update stuff here
}
Singleton header
#interface
#property (nonatomic, assign) CGFloat globalTimeProperty;
+ (instancetype)shared;
- (void)startTimer;
- (void)stopTimer;
#end
I may have missed some stuff but it should be enough to get you going.
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.
When I create a new timer with an IBAction and I double click the button, it creates two timers.
How can I write the code so there's only 1 timer and if I push the button it doesn't create a new timer?
Sorry for my bad English I'm 13 and from Germany.
Here is the code I use to create the timer:
- (IBAction)start:(id)sender;
{
progressBarUpdate = [NSTimer scheduledTimerWithTimeInterval:0.003 target:self selector:#selector(progressbarupdate) userInfo:nil repeats:YES];
//startet den timer
}
safe the timer as a member variable and check if already exists
#implementation MyViewController {
NSTimer *_timer;
}
...
- IBAction)start:(id)sender {
if(!_timer) {
_timer = [[NSTimer timerWith...] retain];//with arc, forget the retain
}
}
...
#end
Check progressBarUpdate.isValid before recreating the timer is an easy way to do this
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.