Set Timer from another class to fire in another view - iphone

EDIT: Added the NSRunLoop from my code, as mentioned by Deepak below. This was originally in my code and forgot to add as it was commented out.
I have 2 classes: MainViewController, and ConfigViewController. The user switches to the ConfigView and uses a UIDatePicker / UIButton combo to set a Date/Time. Upon grabbing the correct time from the UIDatePicker object, I setup a NSTimer to fire as per the following code:
ConfigViewController.m
-(IBAction)setAlarmDate:(id)sender {
//Instantiate to get access to doAlarm:
MainViewController *mvc = [[MainViewController alloc] init];
dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateStyle:NSDateFormatterMediumStyle];
[dateFormat setTimeStyle:NSDateFormatterShortStyle];
NSString *target = [NSString stringWithFormat:#"%#",[dateFormat stringFromDate:datePicker.date]];
alarmDate = [datePicker date];
mvc.fireTimer = [[NSTimer alloc]
initWithFireDate:alarmDate interval:1 target:mvc
selector:#selector(doAlarm:) userInfo:nil repeats:YES];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:mvc.timer forMode:NSDefaultRunLoopMode];
[self dismissModalViewControllerAnimated:NO];
}
The doAlarm: method is as follows:
MainViewController.m
- (void)doAlarm:(NSTimer *)timer {
NSLog(#"Called doAlarm:");
UIImage *ac = [UIImage imageNamed:#"alarmclock.png"];
[self.alarmview setImage:ac];
[self.alarmview setHidden:NO];
[self.view addSubview:alarmview];
[self.view bringSubviewToFront:alarmview];
}
However, when I set the alarm date, the timer fails to fire. I think the following problems are afoot:
1) I am instantiating a new instance of the MainViewController class, setting the Timer going and then passing control back to the "original" instance of MainViewController when I dismiss the MVC. At this point, probably the "new" instance of MainViewController is nothing but a dangling pointer, and is never referenced again anyway, hence no segfault.
2) doAlarm: references self.view, which is supposed to be the MainViewController.view, but as it's instantiated in the scope of ConfigViewController, the alarm image would never be seen anyway...
I imagine my theories are a bit unfounded, but with my current level of knowledge, they make sense to me.
Any light you can shed on the above would be smashing.
Many thanks!
swisscheese.

If you go to the documentation for initWithFireDate:interval:target:selector:userinfo:repeats:, you will notice that while the timer is initialized it is not scheduled yet. You will need to add it a NSRunLoop to make it work. Do this after you have initialized an alarm as above.
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
You can also consider using the scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: which does that for you.
There are of course few other things here.
New MainViewController object
You guessed it right. You are creating an unrelated object which will just leak. To solve this you can maintain a weak reference to the MainViewController object. You can do this as follows -
#class MainViewController; // Forward declaration
#interface ConfigViewController {
...
MainViewController *mainViewController;
}
...
#property (nonatomic, assign) MainViewController *mainViewController;
...
#end
in ConfigViewController.m:
#import "MainViewController.h"
#implementation ConfigViewController
#synthesize mainViewController;
...
- (IBAction)setAlarmDate:(id)sender {
dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateStyle:NSDateFormatterMediumStyle];
[dateFormat setTimeStyle:NSDateFormatterShortStyle];
alarmDate = [datePicker date];
mainViewController.fireTimer = [[NSTimer alloc]
initWithFireDate:alarmDate interval:1 target:mainViewController
selector:#selector(doAlarm:) userInfo:nil repeats:YES];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:mainViewController.timer forMode:NSDefaultRunLoopMode];
mainViewController = nil;
[self dismissModalViewControllerAnimated:NO];
}
...
#end
Timer
I am not sure if you need a repeating timer here. There is an alternative - performSelector:withObject:afterDelay:. This will invoke a method on an object this is called after the desired delay. Does what it calls itself. It will be something like this -
[mvcReference performSelector:#selector(doAlarm)
withObject:nil
afterDelay:[datePicker.date timeIntervalSinceNow]];
doAlarm:
I did not get what you wanted to know here but you are right. self is a reference to the MainViewController object. Unless you retained the view controller that you displayed modally, it would have been dismissed and deallocated by then.

Related

navigationController keeps running after pop

Please help me understand the mystery of navigationController. I have a HomeViewController which gets invoked from the didFinishLaunchingWithOptions. From HomeViewController user press a button and my code is
-(IBAction)showMap:(id)sender
{
MapViewController *mapViewController = Nil;
mapViewController = [[MapViewController alloc] initWithNibName:#"MapView-iPad" bundle:nil];
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.navigationController pushViewController:mapViewController animated:YES];
}
When a user wants to go back from MapViewController, I use the code
-(IBAction)goBackToHome:(id)sender
{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate.navigationController popViewControllerAnimated:YES];
}
I was under impression that once I am out of MapViewController, all my resources get freed associated with MapViewController. To verify that I put this code inside initWithNibName in MapViewController.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
[NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(printMessage) userInfo:nil repeats:YES];
}
return self;
}
-(void) printMessage
{
NSLog(#"I am inside Map View Controller");
}
To my surprise, even after popping out the MapViewController, the printMessage still continues. Please help me understand what is going on and why MapViewController is still running. Is there any way I can verify the MapViewController is freed up ?
Take a look at the documentation for NSTimer. In particular, the scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: method. The documentation notes that
target
The object to which to send the message specified by aSelector when the timer fires. The target object is retained by the timer and released when the timer is invalidated.
This means that your timer is causing the MapViewController to be retained. Without that timer holding onto a reference to the controller, it should indeed be deallocated when you pop it from the navigation controller.
MapViewController is still being retained: The timer retains the ViewController, and the timer is retained by the run loop it is scheduled on. To get your MapViewController to be released you have to remove the timer from the run loop by using [yourTimer invalidate]. One way to do this is to keep a weak reference to your timer in your MapViewController:
#property (weak) NSTimer *timer;
When you create the timer, assign it to the property:
self.timer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(printMessage) userInfo:nil repeats:YES];
Now you can invalidate the timer when you no longer need it, e.g. in viewWillDisappear:
- (void)viewWillDisappear {
[self.timer invalidate];
[super viewWillDisappear];
}

NSTimer not stopping?

I'm trying to to stop an NSTimer with the following code:
- (void)viewDidLoad
{
[super viewDidLoad];
timer3 = [NSTimer timerWithTimeInterval:5.0 target:self selector:#selector(start) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer3 forMode:NSDefaultRunLoopMode];
}
-(void)invalidate
{
[timer3 invalidate];
timer3 = nil;
}
and I call -(void)invalidate from another class like this:
-(void)timer
{
ClassOfMyTimer *class = [[ClassOfMyTimer alloc] init];
[class invalidate];
}
but the timer doesn't stop. Does anyone know what I'm doing wrong?
You need to call your invalidate method on the same instance of your class that created the timer. In your timer method you create a new instance of your class which could have its own timer and invalidate that.
I'm kind of confused by what you're trying to do here, but I'd guess that you're not maintaining a reference to timer3.
Have you created a property in the .h file for the timer:
#property (strong) NSTimer *timer3;
And then added a synthesize statement in the .m file:
#synthesize timer3;
Then, in viewDidLoad:, you can maintain a reference to the timer you're creating via:
self.timer3 = [[[NSTimer timerWithTimeInterval:5.0 target:self selector:#selector(start) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.timer3 forMode:NSDefaultRunLoopMode];
And, to invalidate the timer later:
[self.timer3 invalidate]
self.timer3 = nil
On preview, Sven also has a valid solution to an issue that might be impacting you..

NSTimer scheduledTimerWithTimeInterval: repeats:NO selector method calls multiple times

I am using NSThread along with NSTimer.
My code is like this
-(void) checkForRecentAlarm
{
if ([self.alarmThread isFinished])
{
[self.alarmThread cancel];
}
self.alarmThread = [[NSThread alloc] initWithTarget:self selector:#selector(startTimerForRecentAlarm) object:nil];
[self.alarmThread start];
//[NSThread detachNewThreadSelector:#selector(startTimerForRecentAlarm) toTarget:self withObject:nil];
}
-(void)startTimerForRecentAlarm
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
self.recentAlarmTime = [NSDate date];
self.dbObject = [[RADataBaseModelManager alloc] init];
self.recentAlarmTime = [self.dbObject getMostRecentAlarmTimeFromDB];
if (self.recentAlarmTime) {
NSTimeInterval timeIntervalToAlarm = [self.recentAlarmTime timeIntervalSinceNow];
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
//Fire timer every second to updated countdown and date/time
self.RATimer = [NSTimer scheduledTimerWithTimeInterval:timeIntervalToAlarm target:self selector:#selector(timerFireMethod:) userInfo:nil repeats:NO];
[runLoop run];
}
[pool release];
}
- (void)timerFireMethod:(NSTimer*)theTimer
{
[self.RATimer invalidate];
[theTimer invalidate];
self.RATimer = NULL;
theTimer = NULL;
[self playAlarm];
UIAlertView *alarmAlert = [[UIAlertView alloc] initWithTitle:#"Alarm" message:#"" delegate:self cancelButtonTitle:#"Close" otherButtonTitles:#"Snooze", nil];
[alarmAlert show];
[alarmAlert release];
alarmAlert = nil;
}
Now the problem is, my alertbox comes twice for one call in the
startTimerForRecentAlarm
method. So that the alert comes consequently twice and my view get stuck.
What will be problem here?
I am trying to implement an alarm with multiple alarm option using a single NSTimer.
Please help.
When I debug this, I can find many simultaneous threads are running on same code(UIAlertView).
I can't see any obvious reason why that would be called twice, but it does seem like an overly complex way of doing what you need to do.
Have you thought about using local notifications?
If you don't want to do that, you could refactor your code so it works like this:
1. Add a new event
2. If there's no timer or the time to the event is shorter than the time on the timer, then set the timer for this event.
3. When a timer fires, check for the next event and set a timer for that event (if there is one).
This does seem really complex. My general observation is that if you get two timer firings it's because you have two timers for some reason.
If you have multiple threads doing UIAlertView, you have another problem, because you can only (reliably) do UI from the main thread.

switching from view 1 to view 2 without pressing any buttons

How to switch from view1 to view 2 without having any button pressed. In my views i have is uiimageview and uitextview
With the NSTimer i m trying to do this
in the viewdidload method by using the following code:
In the firstviewcontroller.h file
#interface FirstViewController : UIViewController
{
NSTimer *SwitchingTimer;
}
In the firstviewcontroller.m file
- (void)viewDidLoad
{
[super viewDidLoad];
SwitchingTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(SwitchView) userInfo:nil repeats:YES];
}
-(void)SwitchViews:(id)sender
In the secondviewcontroller.m file
-(void) SwitchView
{
SecondViewController *SecondView = [[SecondViewController alloc]
initWithNibName:#"SecondViewController" bundle:nil];
SecondView.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:SecondView animated:YES];
[SwitchingTimer invalidate];
self.SwitchingTimer = nil;
}
but nothing is happening. Can someone please tell me what i m missing in my code. Will appreciate help.
Thanks in advance.
There are a few issues in your code that are worth mentioning though I am not sure if those will provide you a solution.
Why do you want to repeat the timer every 2 seconds. I think you just want to switch to next view only once and if so then dont repeat the timer. So no need to invalidate the timer.
Your code for the SwitchView method is leaking memory. Please make sure that the SecondView is released after presenting the modal view(in case you are not using ARC).
Please follow the standard naming conventions. For eg: methods and variables should start with lowercase.
Regarding your issue please make sure that the nib name is correct and you are getting a valid object for the second view controller. You can check by using NSLog. Also ensure that the method Switchview is called. Try putting a break point and verify that it is called.
Another Option
If you just want to switch the view only once you can go for another option which does not make use of the NSTimer. For this, you can use performSelector:withObject:afterDelay:. This is just another option for the scenario I mentioned above.
You need to add it to the run loop:
- (void)viewDidLoad
{
[super viewDidLoad];
SwitchingTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(SwitchView) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer: SwitchingTimer forMode:NSDefaultRunLoopMode];
}
Also, you could rename -(void)SwitchView to -(void)switchViewTimerDidFire:(NSTimer *)timer. From the Documentation:
[...] The selector must have the following signature:
- (void)timerFireMethod:(NSTimer*)theTimer
The timer passes itself as the argument to this method.

To which source file does NSRunLoop/NSTimer belong?

I've been trying to figure out how to setup an NSTimer to allow me to print the current time in a UILabel within a View, and have it update every second (no finer resolution required - just a simple clock).
At first, I wasn't using a NSRunLoop, but if I try and include one, the execution just "spins" inside the loop, blocking further execution. I have posted my code below.
-(id) printCurrentTime {
now = [NSDate date];
dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setTimeStyle:NSDateFormatterMediumStyle];
NSString *nowstr = [dateFormat stringFromDate:now];
[dateFormat release];
NSLog(#"Current time is: %#",nowstr);
return nowstr;
}
And in the ViewController source file, I execute as per:
TimeStuff *T = [[TimeStuff alloc] init];
NSString *thetime = [T printCurrentTime];
[timelabel setText:thetime];
[T release];
[self.view addSubview:timelabel];
NSTimer *timmeh = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(printCurrentTime) userInfo:nil repeats:YES];
[[[NSRunLoop currentRunLoop] addTimer:timmeh forMode:NSDefaultRunLoopMode] run];
The "TimeStuff" class is effectively an empty class, save for the printCurrentTime function.
Questions:
1) Should I be including the RunLoop in the AppDelegate class? I am having trouble visualising how this all should hang together, as in - what are the steps to achieving a Loop based on Timer to update a text label with the up-to-second time. Pretty stumped.
2) In the event that I should use a NSThread, should that also be in it's own class / the Delegate class.
3) Is the ViewController class totally out of bounds for looping/timers, and simply the "eye candy" class, with callbacks to functions in the Delegate class?
Thank you for your time and patience.
You don't need to deal with run loops at all.
This line :
NSTimer *timmeh = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(printCurrentTime) userInfo:nil repeats:YES];
will create a timer and attach it to the current thread's run loop for you. You don't need the [NSRunLoop addTimer:forMode:] call at all - you can delete that line.
PS You certainly don't need to go as far as NSThreads!
EDIT Regarding your comment :
You will need to make an instance of your TimeStuff class for the timer to use if that's where your printCurrentTime method is. i.e.
#interface MyViewController : UIViewcontroller {
TimeStuff *timeStuff
}
and in your viewDidLoad method:
- (void)viewDidLoad {
[super viewDidLoad];
...
// Create our timestuff if we don't have one already
if (nil == timeStuff)
timeStuff = [[TimeStuff alloc] init];
// Start the timer
[NSTimer scheduledTimerWithTimeInterval:1.0 target:timeStuff selector:#selector(printCurrentTime) userInfo:nil repeats:YES];
and not forgetting dealloc
- (void)dealloc {
[timeStuff release];
...
[super dealloc];
}
Passing in the timeStuff as the target for the timer tells it where to look for the printCurrentTime method!
Hope that helps,
PS All the line #class TimeStuff does is tell the compiler that there is a class called TimeStuff. It has no idea that you want to use it for your timer's selector!