I'm trying to get a recent location using CLLocationManger. If I cannot get one within 30 seconds, I want an alert to pop up saying that "we're having problems right now" or something along those lines. In my CLLocationManager Delegate, I do this:
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
NSDate *eventDate = newLocation.timestamp;
NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
NSTimer *timer;
if (abs(howRecent) < 1.0) { // process time if time is less than 1.0 seconds old.
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateCount:) userInfo:nil repeats:NO];
return;
}
My updateCount method:
- (void)updateCount:(NSTimer *)aTimer {
count++;
NSLog(#"%i", count);
if (count == 30) {
[aTimer invalidate];
NSLog(#"Timer invalidated");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Server Down" message:#"Sorry, but we cannot find your location at this time. Please try again later." delegate:self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
[alert release];
}
}
But I don't see my alert get fired. I tried with a repeating timer, but after it hits 30 seconds, and the alert is shown, it repeats again. So I'm not quite sure what I'm doing wrong here. Thanks!
just a small update to updateCount method :
- (void)updateCount:(NSTimer *)aTimer {
count++;
NSLog(#"%i", count);
if (count == 30) {
[aTimer invalidate];
NSLog(#"Timer invalidated");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Server Down" message:#"Sorry, but we cannot find your location at this time. Please try again later." delegate:self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
[alert release];
[timer invalidate];
timer = nil;
}
}
Don't use NSTimer *timer; in locationManager method. instead directly call timer = [[NSTimer....]] method and I think because there is one argument in updateCount method i.e. NSTimer and we are using different NSTimer instance so here is basically problem. Try without using argument in updateCount Method and invalidate the timer that is declared in .h file.
I checked the documentation and found following paragraph in that.
However, for a repeating timer, you must invalidate the timer object yourself by calling its invalidate method. Calling this method requests the removal of the timer from the current run loop; as a result, you should always call the invalidate method from the same thread on which the timer was installed.
So, for invalidating timer try calling performSelector.
I am not sure weather it will help or not but if it works then please post here so that other too can take advantage.
Related
I make a class level method for Alert:
#interface TestAlert
#end
+ (void)showErrorAlert:(NSTimer *)message
{
.......
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:nil message:messageIn delegate:self cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[alert show];
}
and I want to call it directly in scheduledTimerWithTimeInterval like:
[NSTimer scheduledTimerWithTimeInterval:0.001 target:TestAlert selector:#selector( showErrorAlert:) userInfo:error repeats:NO];
There have grammar error of course.
I know I can put showErrorAlert to a method:
- (void)showError:(NSTimer *)timer
{
//NSLog(#"show error %#", error);
[TestAlert showErrorAlert:(NSString *)[timer userInfo]];
}
Then
[NSTimer scheduledTimerWithTimeInterval:0.001 target:self selector:#selector(showError:) userInfo:error1 repeats:NO];
But it will cause crash when showErrorAlert is called, because error message from showError method has been released.
Can i call showErrorAlert directly, If I can't, how should I avoid error message's release ?
Just use [TestAlert class] as a target instead of TestAlert.
How about trying this.
You can find all the types of provided performSelector: methods here:
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nsobject_Class/Reference/Reference.html
performSelector:
performSelector:withObject:
performSelector:withObject:withObject:
– performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:
performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:
performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:
performSelectorInBackground:withObject:
I have NSTimer in ViewController Class.. And all the methods for NStimer are in that class.. My NSTimer is working properly for play and pause... When i press home button on iPhone also the timer is working properly(it starts running from the time where the application enters into background when the application enters into foreground)... In my application i am rising an alert quickly when my application enters into foreground(UIAlertview in application didEnterForeGround). Here my NSTimer is running when the alert is on the screen(didn't give response to alert).. I want stop the NSTimer Upto the User responding to my alert... After that I want to start the NSTimer... How Can i do that...? Please help me... Thanks in Advance... I want call the NSTimer methods from Appdelegate.m file... Iam calling these methods properly... And the methods in ViewController are called.. But the action is Not Performing... For the Same method when call from viewController class it is working...
enter code here
ViewController.h
+(ExamViewController *)evcInstance;
ViewController.m
ExamViewController *evc = nil;
#implementation ExamViewController
+(ExamViewController *)evcInstance
{
if(!evc)
{
evc = [[ExamViewController alloc] init];
}
return evc;
}
- (void)onStartPressed
{
stopwatchtimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateTimer:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:stopwatchtimer forMode:NSRunLoopCommonModes];
resume.hidden = YES;
resume.enabled=NO;
pause.hidden = NO;
pause.enabled=YES;
}
- (void)onStopPressed
{
[stopwatchtimer invalidate];
stopwatchtimer = nil;
pause.hidden = YES;
pause.enabled=NO;
resume.hidden = NO;
resume.enabled=YES;
}
Appdelegate.m
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[[ExamViewController evcInstance] onStopPressed];
NSLog(#"applicationWillEnterForeground");
if(viewCalled ==YES)
{
UIAlertView *alertview=[[UIAlertView alloc]initWithTitle:#"" message:#"DO! You Want To continue" delegate:self cancelButtonTitle:#"YES" otherButtonTitles:#"NO", nil];
[alertview show];
[alertview release];
}
/*
Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
*/
}
a few things:
1) It looks like an incomplete singleton design. If you really want a singleton, refer to a post like this one.
2) No need to add stopwatchtimer to the current run loop. The scheduledTimerWithTimeInterval method schedules it for you.
3) I don't see where you declare stopwatch timer, but be sure to declare it as a retained property (or strong in ARC).
4) To stop the timer, just call [stopwatchtimer invalidate]; To restart it, re-instantiate and overwrite the old one using the same scheduledTimerWithTimeInterval class method that you called originally.
5) To do anything when an alert view completes, implement the delegate method:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
So you can invalidate the timer before presenting the alert, then rebuild it when you get notified the alert is finished.
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.
Still trying to update the message in an UIAlertview while the alert is active. I removed most of the first part of the question and publish more code in the update below.
UPDATE 3: Adding more code!
In the .h file I declare the following (among others):
#interface WebViewController : UIViewController <UIWebViewDelegate> {
IBOutlet UIWebView *webView;
UIAlertView *alert;
}
I #property and #synthesize the UIAlertview to.
Next I create the alert in an IBAction which is run by a button click:
-(IBAction)convert {
convertButton.enabled = NO;
mailButton.enabled = NO;
backButton.enabled = NO;
//show the alert window
alert = [[UIAlertView alloc] initWithTitle:#"Converting in progress\nPlease Wait..." message:#"\n\n\n" delegate:self cancelButtonTitle:nil otherButtonTitles: nil];
[alert show];
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
// Adjust the indicator so it is up a few pixels from the bottom of the alert
indicator.center = CGPointMake(alert.bounds.size.width / 2, alert.bounds.size.height - 50);
[indicator startAnimating];
[alert addSubview:indicator];
[indicator release];
[alert setMessage:#"getting roster"];
}
It then jumps to the following function:
- (void)didPresentAlertView:(UIAlertView *)progressAlert {
//A lot of code
[alert setMessage:#"Checking history"];
//more code
[alert setMessage:#"writing history"];
//even more code
[alert setMessage:#"converting roster"];
}
The didPresentAlertView method ends with an ASIHTTPRequest to post data to a webpage and when this request is finished the code finally jumps to the last method to exit the UIAlertView and closes everything up:
- (void)requestFinished:(ASIHTTPRequest *)request {
[timer invalidate];
[alert dismissWithClickedButtonIndex:0 animated:YES];
backButton.enabled = YES;
[alert release];
}
I also removed the autorelease from my UIAlertView init to make sure it exists during the rest of the process.
As it is now, the code only fires the very first setMessage -> 'getting roster' and the very last -> 'converting roster'. The setMessage requests in the middle do not get fired..
Really hope someone can help me here!
Thanks all!
Now I see the problem.
When you update the message property, it doesn't fire redrawing the view right away. The view is marked as 'needed to be drawn', and the actual drawing happens later, typically at the end of the current or next runloop in the main thread.
Therefore, while your didPresentAlertView method is running on the main thread, the alert view is not redrawn until the method is finished. This is why a computation-intensive job needs to run on a separate thread to increase the responsiveness of the UI, as the UI-related job is done on the main thread.
What you should do is run your //A lot of code //more code and //even more code on a separate thread, and update the message property on the main thread.
For example, your code may look similar to :
// this is inside didPresentAlertView method
NSOperationQueue* aQueue = [[NSOperationQueue alloc] init];
[aQueue addOperationWithBlock: ^{
// A lot of code
[alert performSelector:#selector(setMessage:) onThread:[NSThread mainThread]
withObject:#"Checking history" waitUntilDone:NO];
// more code
[alert performSelector:#selector(setMessage:) onThread:[NSThread mainThread]
withObject:#"writing history" waitUntilDone:NO];
// it goes on
}];
If you are working with iOS 4.0 and later, and want to use the GDC, be careful because it may detect independency of your computation and message updates and let them happen concurrently (which is not desired here).
I have been having problems creating a UISlider that can be used to modify a NSTimer I created. Essentially the slider is ment to modify the integer that the NSTimer is counting down from, but when I try to move the UISlider, the application crashes, I'm assuming this is because it is interfering with the countdown that is occurring, but I don't know what to do to fix this.
Here is the relevant code
- (void)viewDidLoad {
[label setFont:[UIFont fontWithName:#"DBLCDTempBlack" size:36.0]];
label.text = #"I0A0IN6";
mainInt = mySlider.value;
timer = [NSTimer scheduledTimerWithTimeInterval:(1.0/1.0) target:self selector:#selector (timerVoid) userInfo:nil repeats:YES];
[super viewDidLoad];
}
- (void)timerVoid {
mainInt += -1;
if (mainInt == 0) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Break Time!"
message:#"Time to take a break, please go to the exorcises page during your break inorder to maximise it"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles: nil];
[alert show];
[mainInt invalidate];
}
[label setFont:[UIFont fontWithName:#"DBLCDTempBlack" size:36.0]];
label.text=[NSString stringWithFormat:#"%d" , mainInt];
}
The slider is called mySlider, and it is modifying the interger "mainInt" (line 5).
Few things:
[super viewDidLoad]; line is better be first in viewDidLoad.
There is no need to set the font each time timerVoid is executed.
As I have mentioned in the comment, you should call invalidate on timer and not on mainInt.
The slider does not modify mainInt - you have set the value of mainInt to hold the initial value of your slider and it is not changed by itself when you change the value of the slider. In order to do that you should create an IBAction and connect it to slider's valueChanged event. Inside that IBAction you may do what ever you want with the new value of the slider - for example set the value of mainInt or reschedule your timer.
You may also avoid using the IBAction by using the mySlider.value directly everywhere you need.
I don't see any reason for crashing the app though...
Possible code:
- (void)viewDidLoad {
// This line should be first
[super viewDidLoad];
[label setFont:[UIFont fontWithName:#"DBLCDTempBlack" size:36.0]];
label.text = #"I0A0IN6";
// There is no need in mainInt
//mainInt = mySlider.value;
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerVoid) userInfo:nil repeats:YES];
}
- (void)timerVoid {
// This line is much more readable like this ("-=" instead of "+= -")
mySlider.value -= 1;
// Much better to use "<=" in this case to avoid bugs
if (mySlider.value <= 0) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Break Time!"
message:#"Time to take a break, please go to the exorcises page during your break inorder to maximise it"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles: nil];
[alert show];
[alert release]; // Haven't noticed the lack of release here earlier...
// timer and not mainInt
[timer invalidate];
}
// The next line is unnecessary
//[label setFont:[UIFont fontWithName:#"DBLCDTempBlack" size:36.0]];
label.text = [NSString stringWithFormat:#"%d", mySlider.value];
}
EDIT:
Added the [alert release]; line
Your app is crashing because you haven't linked the valueChanged event from your slider to any proper IBAction method.