I'm trying to keep a timer running on another page when you switch to other pages and complete other tasks, in essence keeping a clock on how long it takes to do the tasks. Whenever I switch to another page, it resets the timer back to what it was started, and does the same with some switches on other pages that I'm trying to keep on. Any ideas?
Screenshot of storyboards:
Code so far:
//
// ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (IBAction)start{
ticker = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self ``selector:#selector(showActivity) userInfo:nil repeats:YES];
}
- (IBAction)reset{
[ticker invalidate];
time.text = #" 0:00";
}
- (void)showActivity{
int currentTime = [time.text intValue];
int newTime = currentTime + 1;
time.text = [NSString stringWithFormat:#"%d", newTime];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
// ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController{
IBOutlet UILabel *time;
NSTimer *ticker;
}
- (IBAction)start;
- (IBAction)reset;
- (void)showActivity;
#end
Your NSTimer is a member variable of your view controller class. I'm assuming that when you switch between views, you're destroying this view controller and instantiating an instance of a new one. That means this view controller is gone, as well as the timer; it's not that the timer is being reset, it's that your old timer has been destroyed an a new one is being created.
What you need is to store your NSTimer and its functionality in a place where it will not be destroyed every time you change your view controller. One solution is to create a Singleton class which handles the timer. (A Singleton class is a class that can only be created one time; only one instance of it can exist. You can read more about them here.)
Here is an example of how you can create a Singleton class in Objective-C. The header:
//ApplicationManager.h
#interface ApplicationManager : NSObject
+(ApplicationManager*) instance;
#end
And the implementation:
//ApplicationManager.m
#import "ApplicationManager.h"
#implementation ApplicationManager
static ApplicationManager* appMgr = nil;
+(ApplicationManager*) instance
{
#synchronized([ApplicationManager class])
{
if(!appMgr)
{
appMgr = [[self alloc] init];
}
return appMgr;
}
return nil;
}
+(id) alloc
{
#synchronized([ApplicationManager class])
{
NSAssert((appMgr == nil), #"Only one instance of singleton class may be instantiated.");
appMgr = [super alloc];
return appMgr;
}
}
-(id) init
{
if(!(self = [super init]))
{
[self release];
return nil;
}
return self;
}
#end
The first time you call the instance method, the instance of the ApplicationManager will be created. Each time you want to access it, call the instance method again; the ApplicationManager will be returned. Now you simply add your NSTimer (and any other object you wish to persist throughout your application) as member variables of the ApplicationManager class.
You then must import the ApplicationManager class into your view controller class, and your view controller methods will change to:
-(IBAction) start
{
[[ApplicationManager instance] setTicker:[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(showActivity) userInfo:nil repeats:YES]];
}
-(IBAction) reset
{
[[[ApplicationManager instance] ticker] invalidate];
time.text = #" 0:00";
}
-(void) showActivity
{
int currentTime = [time.text intValue];
int newTime = currentTime + 1;
time.text = [NSString stringWithFormat:#"%d", newTime];
}
If you want to make things nice and neat, you can also add this line to the top of your ApplicationManager class:
#define APPMGR [ApplicationManager instance]
Now instead of having to type [ApplicationManager instance] everywhere, you can simply refer to it as APPMGR instead. [APPMGR ticker] is a lot cleaner than [[ApplicationManager instance] ticker] :)
Related
I have a singleton class that I want to run in the background and check for photos to upload. The singleton is initialized from another viewcontroller via
[[EXOImageUploader sharedPhotoUploader] startPhotoUploadCheck];
If I NSLog everything, it appears the singleton is working. I can do other things in the singleton that aren't shown below just fine. The NSTimer just never fires. I have even commented out the line that checks to see if the timer isValid but that doesn't work either.
Any idea on why my Timer is working?
Here is the order that the NSLog's spit out.
sharedPhotoUploader init
doSetup
Timer interval: 1.000000
sharedPhotoUploader singleton
startPhotoUploadCheck
Here is the code.
.h
#interface EXOImageUploader : NSObject
#property (assign) NSTimeInterval timerCheckInterval;
+ (EXOImageUploader *) sharedPhotoUploader;
- (void) startPhotoUploadCheck;
- (void) stopPhotoUploadCheck;
.m
#interface EXOImageUploader ()
#property (nonatomic, strong) NSTimer* timerUpload;
#end
#implementation EXOImageUploader
static EXOImageUploader* _sharedPhotoUploader;
#pragma mark - SINGLETON SETUP
+ (EXOImageUploader *) sharedPhotoUploader {
static EXOImageUploader *sharedPhotoUploader = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedPhotoUploader = [[self alloc] init];
NSLog(#"sharedPhotoUploader singleton");
});
return sharedPhotoUploader;
}
- (id) init {
if (self = [super init]){
NSLog(#"sharedPhotoUploader init");
[self doSetup];
}
return self;
}
- (void) doSetup {
NSLog(#"doSetup");
if (!self.timerCheckInterval) {
self.timerCheckInterval = 1.0f;
}
NSLog(#"Timer interval: %f", self.timerCheckInterval);
}
#pragma mark Public Methods
- (void) startPhotoUploadCheck {
NSLog(#"startPhotoUploadCheck");
//Don't start a new one if this one is running
if (!_timerUpload) {
_timerUpload = [NSTimer timerWithTimeInterval:_timerCheckInterval target:self selector:#selector(checkForPhotosToUpload) userInfo:nil repeats:YES];
}
}
- (void) stopPhotoUploadCheck {
NSLog(#"stopPhotoUploadCheck");
[_timerUpload invalidate];
_timerUpload = nil;
}
Use scheduledTimerWithTimeInterval instead of timerWithTimeInterval.
The docs for timerWithTimeInterval say: "You must add the new timer to a run loop, using addTimer:forMode:".
With the "scheduled" version, that's already done for you.
You are trying to use your timer without initializing it, thus it will be nil and won't fire. Activate your timer like this:
[timerUpload scheduledTimerWithTimeInterval:2.0
target:self
selector:#selector(targetMethod:)
userInfo:nil
repeats:NO];
I have a singleton class, and I have a property declared in it:
#property (nonatomic, strong) NSString *currentTableName;
+ (SuperNoteManager*)sharedInstance;
.m file:
+ (SuperNoteManager*)sharedInstance
{
static SuperNoteManager *_sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[SuperNoteManager alloc] init];
});
return _sharedInstance;
}
When I run my app for the first time, there is no data in the data base,so it shows the EmptyViewController.
#property (nonatomic, strong) SuperNoteManager *myManager;
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
_myManager=[SuperNoteManager sharedInstance];
}
-(void)changeRootView{
UIStoryboard *storyboard=[UIStoryboard storyboardWithName:#"Main" bundle:nil];
HomeViewController *hVC=[storyboard instantiateViewControllerWithIdentifier:#"HomeViewController"];
UINavigationController *mNavVC=[storyboard instantiateViewControllerWithIdentifier:#"MainNavigationController"];
mNavVC.viewControllers=#[hVC];
[[UIApplication sharedApplication].keyWindow setRootViewController:mNavVC];
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
if ( [_myManager checkForDataInAllTables]) {
NSLog(#"All tables are empty");
}else{
//a note is saved, show home view controller
if (![_myManager isDatabaseEmpty]) {
[self changeRootView];
}
}
}
There is + button on NavigationBar on EmptyNotesViewController, and on tap '+',
NotesViewController is pushed from EmptyNotesViewController.
In the NotesViewController, after I write some notes, I save the notes in database:
NotesViewController:
#property (nonatomic,strong) SuperNoteManager *myManager;
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
_myManager.currentTableName=#"WorkTable";
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController) {
NSLog(#"going back");
[self insertTextintoDatabase]; //Text is inserted . I double checked
}
}
And then When I go back to my EmpytNotesViewController, I check for data, and if data is present, I change the rootViewController as it is not EmptyNotesView anymore.
So When I go back to my EmptyNotesViewController:
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
if ( [_myManager checkForDataInAllTables]) {
NSLog(#"All tables are empty");
}else{
//a note is saved, show home view controller
//Put a breakpoint here
if (![_myManager isDatabaseEmpty]) {
[self changeRootView];
}
}
}
Here at the breakpoint _myManager.currentTableName is nil. why?
I set it in the NotesController, and it became nil when it come back to the EmptyNotesController.
I thought once a value is set in singleton, it will persist as long as the app is closed/killed.
Note: I have declared the property of my Singleton class as strong and also all the properties in the singleton are declared as strong.
It appears like you never get a reference to the SuperNoteManager singleton in NotesViewController, like you did in your EmptyNotesController.
Therefore the currentTableName property never gets set in the first place.
You want to insert:
_myManager = [SuperNoteManager sharedInstance];
in your -viewDidAppear: before you set the currentTableName property.
I'm trying to create a simple countdown timer app for myself. So far I've figured out how to create the countdown timers with a stop/reset action on them for a single button I've got attached to the timer.
However, I would like to add multiple timers to the same page and I'm not really sure how to do about making extra calls for the timers. Each of the timers would have it's own number to count down from (7 minutes for one, 3 minutes for the other, etc). These are set intervals that the user is not able to change. Google hasn't really worked out for me on how to do this so I'm hoping someone can at least guide me in the right direction. Below is my code snippets:
ViewController.h
#interface ViewController : UIViewController {
IBOutlet UILabel *firstCountdownLabel;
NSTimer *firstCountdownTimer;
bool timerActive;
int secondsCount;
}
- (IBAction)start:(id)sender;
- (void)timerRun;
#end
ViewController.m
#interface ViewController ()
#end
#implementation ViewController
- (void) timerRun {
secondsCount = secondsCount - 1;
int minutes = secondsCount / 60;
int seconds = secondsCount - (minutes * 60);
NSString *timerOutput = [NSString stringWithFormat:#"%2d:%.2d", minutes, seconds];
firstCountdownLabel.text = timerOutput;
if (secondsCount == 0) {
[firstCountdownTimer invalidate];
firstCountdownTimer = nil;
}
}
//- (void) setTimer {
- (IBAction)start:(id)sender {
secondsCount = 420;
if (timerActive == NO) {
timerActive = YES;
self->firstCountdownTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerRun) userInfo:nil repeats:YES];
}
else {
timerActive=NO;
[self->firstCountdownTimer invalidate];
self->firstCountdownTimer = nil;
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
// [self setTimer];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Google doesn't help in showing you how to implement original application ideas.
If you want multiple timers, define multiple timer instance variables:
#interface ViewController : UIViewController
{
IBOutlet UILabel *timer1Label;
IBOutlet UILabel *timer2Label;
IBOutlet UILabel *timer3Label;
NSTimer *timer1;
NSTimer *timer2;
NSTimer *timer3;
int timer1Count;
int timer2Count;
int timer3Count;
bool timer1Active;
bool timer2Active;
bool timer3Active;
}
Then create a separate IBAction for each button that starts each of the timers:
- (IBAction)startTimer1:(id)sender
{
timer1Count = 420;
if (timer1Active == NO)
{
timer1Active = YES;
timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(timer1Run:)
userInfo:nil
repeats:YES];
}
else
{
timer1Active=NO;
[timer1 invalidate];
timer1 = nil;
}
}
- (void) timer1Run: (NSTimer*) timer
{
timer1Count -= 1;
int minutes = timer1Count / 60;
int seconds = timer1Count - (minutes * 60);
NSString *timerOutput = [NSString stringWithFormat:#"%2d:%.2d", minutes, seconds];
timer1Label = timerOutput;
if (timer1Count == 0) {
[timer2 invalidate];
timer2 = nil;
}
}
Duplicate the above code for each timer, using "timer2" and "timer3" in place of "timer1". Change the time counts for each one to the desired values. (I changed the names from "firstTimer" to "timer1" because it's easier to edit the code to support multiple timers that way.
I did not write 3 versions of each method for you because you need to figure this out rather than copy & pasting in code that you don't understand.
It would be possible, and require less code, to use the same IBAction method for all your start timer buttons, and have the code check the tag on the button to decide which timer to start.
The code might look like this:
- (IBAction)startTimer1:(id)sender
{
int tag = [sender tag];
switch (tag)
{
case 1: //timer 1
//Put code to start timer 1 here
break;
case 2: //timer 2
//put code to start timer 2 here
break;
}
}
But that might be a bit over your head at the moment.
By the way, forget you ever saw the "self->variable" syntax. it is slower and more error-prone than just referring to the instance variable directly. using object->variable syntax also allows you to access the instance variables of other objects, which is bad practice. You should always use properties to access the instance variables of objects other than yourself.
Also, the timer method should take a single parameter, a timer. I corrected the timer method in the above code.
Create a class as YourTimer with few properties like
NSString *timerLabel;
NSTimer *timer;
NSInteger timerCounter;
Now create an array of YourTimer objects. Then you can access it easily.
This will be modular, maintainable and reusable code, as may be later on you need one more identifier to be with all timers, hence wrap them in one class and use it.
I have an issue at the moment. I am making a game. When two imageViews collide the game will end. I am using CGRECTIntersects rect to detect if the two images collide.
The issue is that when i restart the game the collision will happen when in fact the two images have not actually touched one another?
Any suggestions would be much appreciated.
Thank you
-(void)collision {
if (CGRectIntersectsRect(object.frame, object2.frame)) {
object.hidden=YES;
object2.hidden=YES;
retry.hidden=NO;
[timer invalidate];
}
}
/- (void)viewDidLoad
{
retry.hidden=YES;
timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:#selector(object2) userInfo:nil repeats:YES];
objectSpeed1 = CGPointMake(3.0, 2.0);
[super viewDidLoad];
}
/- (IBAction)retry:(id)sender {
[self performSegueWithIdentifier:#"restart" sender:sender];
}
-(void)object2 {
object2.center = CGPointMake(object2.center.x + object2.x, object2.center.y + objectspeed1.y);
if (object2.center.x > 310 || object2.center.x < 10) {
objectspeed1.x = - objectspeed1.x;
}
if (object2.center.y > 558 || object2.center.y < 10) {
objectspeed1.y = - objectspeed1.y;
}
Just consider this case on the code. You can check if image views are on their start position or they came to this position from another. It's enough to keep BOOL property on viewcontroller's class like this:
// ViewController.m
#interface ViewController ()
#property (readonly) BOOL isStarted;
#end
#implementation
- (void)viewDidLoad {
[super viewDidLoad];
_isStarted = NO;
// Make some preparations for app's needs
_isStarted = YES;
}
#end
_isStarted is a flag which shows whether viewcontroller is ready to handle the data.
if(_isStarted) {
// Analyze image views' frame
}
else {
// Do nothing
}
I'm building an app with a timer in it, and I've created a custom class, Timer, which is a wrapper around the NSTimer class. It has a property called remainingTime which is periodically updated as the timer is running.
My view controller, TimerVC, instantiates a Timer object, and needs to update its view based on timer.remainingTime. How do I achieve this?
I believe I should be using a delegate here, but I don't know how it would work. Which class should implement the delegate method?
Or maybe my approach is all wrong?
Edit: The reason I'm not using NSTimer inside of TimerVC is because I want to abstract it for re-use, and also decouple it from the view.
Here is my code for my Timer class:
#import "Timer.h"
#define SECONDS_INTERVAL 1
#interface Timer()
#property (strong, nonatomic) NSTimer *timer;
#property NSInteger seconds;
#property NSInteger secondsRemaining;
#end
#implementation Timer
- (Timer *)initWithSeconds:(NSInteger)seconds {
self = [super init];
if (self) {
self.seconds = seconds;
self.secondsRemaining = self.seconds;
}
return self;
}
- (void)start {
self.timer = [NSTimer timerWithTimeInterval:SECONDS_INTERVAL target:self selector:#selector(updateSecondsRemaining) userInfo:nil repeats:YES];
}
- (void)pause {
}
- (void)stop {
[self.timer invalidate];
self.timer = nil;
}
- (void)reset {
[self stop];
self.secondsRemaining = self.seconds;
}
- (void)updateSecondsRemaining {
self.secondsRemaining = self.secondsRemaining - SECONDS_INTERVAL;
if (self.secondsRemaining == 0) {
[self timerFinished];
}
}
- (void)timerFinished {
[self reset];
}
Typically your view's view controller would implment that delegate.
Define the protocol in a .h file. Let's say its name is CustomTimerDelegate. Add an id <CustomTimerDelegate> timerDelegate; property to your custom timer. Set the delegate when the custom timer was created. The protocol contains one method at least that is invoked by the timer every time when the view controller should update its view. The view conroller implements the protocol. In its .h file you add <CustomTimerDelegate> to the #interface statement. In its .m file you implement the method that is called though the protocol.
Does that answer your question?
I don't see why you would need to write a custom timer for that.
In your TimerVC use this to create a new timer:
[NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(timerCallback:)
userInfo:nil
repeats:YES];
This will call the timerCallback: method every second. In this method you can update your remaining time property and you can also update your view.
Just completing Hermann Klecker answer.. please don't forget that on your timer class the property should be
#property(nonatomic, weak) id <CustomTimerDelegate> delegate
This is.. a weak property to avoid memory leaks when having your VC holding the timer strongly and your timer holding the delegate (in this case the VC) strongly... they would be retained in memory even if VC gets dumped (because timer would have a strong pointer to it) causing VC to not deallocate, thus not making the Timer deallocate thus not making the VC deallocate... and so forth...
with weak you avoid this...