I'm missing something about how UIActivityIndicatorView and NSTimer work together.
I've added this UIActivityIndicatorView in Interface Builder with the following settings:
The UIWebView is instantiated as self.webV and the UIActivityIndicatorView as self.indicator.
I have the following code in the implementation file:
-(void)viewDidLoad
{
[super viewDidLoad];
//Create UIWebView.
if (!self.webV)
{
self.webV = [[UIWebView alloc] init];
}
self.webV.delegate = self;
//Load web page.
NSString *baseURLString = #"myURL.com";
NSString *urlString = [baseURLString stringByAppendingPathComponent:#"myURL.com"];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0/2.0 target:self selector:#selector(timerLoad) userInfo:nil repeats:YES];
[self connectWithURL:urlString andBaseURLString:baseURLString];
}
-(void)timerLoad
{
if (!self.webV.loading)
{
[self.indicator stopAnimating];
}
else
{
[self.indicator startAnimating];
}
}
But when the UIWebView loads, no activity indicator shows up. What am I doing wrong or leaving out?
Thanks for the help, folks.
I'm really not sure on what the behaviour of the UIActivityIndicatorView is supposed to be if you repeatedly call start/stop on it. I am reasonably sure it isn't meant to be used that way :)
So, even though your question is specific to NSTimer and UIActivityIndicatorView, it may be helpful to understand that you should approach your solution differently.
Instead of using a timer that repeatedly calls [self.indicator startAnimating] every half-second, you should use the webview delegate methods to toggle the UIActivityIndicatorView on and off.
-(void)viewDidLoad
{
[super viewDidLoad];
//Create UIWebView.
if (!self.webV)
{
self.webV = [[UIWebView alloc] init];
}
self.webV.delegate = self;
//Load web page.
NSString *baseURLString = #"myURL.com";
NSString *urlString = [baseURLString stringByAppendingPathComponent:#"myURL.com"];
//self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0/2.0 target:self selector:#selector(timerLoad) userInfo:nil repeats:YES];
[self connectWithURL:urlString andBaseURLString:baseURLString];
}
- (void)webViewDidStartLoad:(UIWebView *)webView{
//start animating
[self.indicator startAnimating];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView{
//stop animating
[self.indicator stopAnimating];
}
There could be several reasons. It could load so fast that the loading is already done, or it never loaded at all because something is wrong with the URL.
This isn't the cause of your problem, but you never invalidate your timer, which you should.
I was also going to make the point that you should use delegate methods instead of a timer, but pdriegen beat me to it.
Related
I have a generated Subview that can be moved around. Every time it moves I check if it's passed 300 on the X-axis. My problem is, that when it passes the point and you don't stop moving it, the NSTimer gets started so often, that the program crashes.
NSArray *subviews = [self.view subviews];
for (UIView *subview in subviews) {
if (subview.frame.origin.x > 300) {
NSMutableArray *data = [[NSMutableArray alloc] initWithCapacity:1];
[data addObject:subview];
[NSTimer scheduledTimerWithTimeInterval:3.00 target:self selector:#selector(callFunction:) userInfo:data repeats:NO];
}
}
You should keep a reference to NSTimer instances and when you don't want it to be fired anymore you can call -[NSTimer invalidate]
Update besides, is it your intentions to schedule timers in a loop?
Add a property where you save a reference to the timer. So you can always check if NSTimer is already started and you stop it by
[NSTimer invalidate];
you add a property #property(nonatomic, strong) NSTimer *timerand then you can check like:
if(!self.timer){
self.timer = [NSTimer scheduledTimerWithTimeInterval:3.00 target:self selector:#selector(callFunction:) userInfo:data repeats:NO];
}
Try This :
[NSTimer invalidate];
NSTimer = nil;
As long as the application is running i need to change the status icon once in 2 sec., i think in my case i have to do it in separate thread.
This is my scenario.
I have 2 NSObject initialised in my MainMenu.nib. One of them is main controller AppDelegate and other one ChangeStatusBar to change the icon.
I have an IBOutlet in my AppDeligate pointing to ChangeStatusBar object. From AppDeligate i am calling one method of ChangeStatusBar which uses performSelectorInBackground with run loop at achieve this. Which is not working. My method to change icon is getting called but image is not changing during AppDeligate wait.
My Appdeligate can go on unconditional wait, even in such case i want StatusItem image changing. Ex: say you have scanf in AppDeligate or you are calling an API that can return after sum time. Thanks in Advance. Here is my code.
// AppDelegate.m
-(void)applicationDidFinishLaunching:(NSNotification*)notification
{
[statusItem initStatusItem:true]; //StatusItem is the IBOutlet to ChangeStatusBar object
}
//ChangeStatusBar.m
-(void)initStatusItem:(BOOL) flag
{
statusItem = [[[NSStatusBar systemStatusBar]
statusItemWithLength:NSVariableStatusItemLength]retain] ;
[statusItem setMenu:statusMenu];
[statusItem setImage:[NSImage imageNamed:#"lk0.png"]];
[self performSelectorInBackground:#selector(timerStart) withObject:nil];
}
-(void) timerStart
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
[[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(changeImages)
userInfo:nil repeats:YES] retain];
[runLoop run];
[pool release];
}
///Change image here
-(void)changeImages
{
[statusItem setImage:[NSImage imageNamed:[NSString stringWithFormat:#"lk%d.png",NoImages]]];`
}
I have the code below displaying an ActivityIndicator before calling a GCD process. The background process throws a Notification when it's complete or encounters an error. I am calling the stopAnimating method in the error handler but the spinner keeps spinning. Why?
UIActivityIndicatorView *mIndicator;
#interface VC_Main ()
#end
- (void)viewDidLoad
{
[super viewDidLoad];
[NSLog(#"viewDidLoad");
// create indicator for download activity
mIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[mIndicator setCenter:CGPointMake([self getScreen].x /2.0, [self getScreen].y / 2.0)]; // landscape mode
[self.view addSubview:mIndicator];
// fire off a single interval
[NSTimer scheduledTimerWithTimeInterval:0.0
target:self
selector:#selector(timerTask:)
userInfo:nil
repeats:NO];
...
}
- (void) timerTask:(NSTimer *) timer
{
NSLog(#"DEBUG: timertask timeout");
[mIndicator startAnimating];
...
}
// if there is an error parsing xml downloaded from server, it notifies here
- (void) xmlError:(NSNotification *)note
{
NSLog(#"error parsing xml");
[mIndicator stopAnimating]; // this doesn't work
// fire off a refresh using retry timeout
[NSTimer scheduledTimerWithTimeInterval:TIMEOUT_RETRY_MINS
target:self
selector:#selector(timerTask:)
userInfo:nil
repeats:NO];
NSLog(#"will retry in %d", TIMEOUT_RETRY_MINS);
}
As with every UIKit call, you need to do it on the main thread.
Just do:
dispatch_async(dispatch_get_main_queue(), ^{
[mIndicator stopAnimating];
});
and it should work
Perhaps you retry too soon?
scheduledTimerWithTimeInterval:TIMEOUT_RETRY_MINS
It's supposed to be seconds, not minutes.
I am programming for the iPhone. I haven't programmed for twenty years so am rather new to the whole objective programming thing. I have an UIAlertView that pops up when a sound is played with AVAudioPlayer. The user can dismiss the UIAlertView by clicking the button or wait until the sound ends (dismissed by an NSTimer call).
However, if the UIAlertView was dismissed by button before the NSTimer call, the program crashes. How do I check if the UIAlertView is displayed?
I tried the condition currentAlert.visible==YES but that crashes also if the view was already dismissed. What is the value of an UIAlertView object once it is dismissed?
Here's the code:
-(void) dismissAlert
{
if(currentAlert.visible==YES){
[currentAlert dismissWithClickedButtonIndex:0 animated:YES];
}
-(void) playSound:(NSString *)filename
volume:(float)volume
ofType:(NSString *)type
subtitle:(NSString *)text
speed:(float)speed
loops:(NSInteger)loops
{
//playSound
NSString *path = [[NSBundle mainBundle] pathForResource:filename ofType:type];
theAudio = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path]
error:nil];
theAudio.delegate = self;
theAudio.volume=volume;
theAudio.enableRate=YES;
theAudio.rate=speed;
theAudio.numberOfLoops=loops;
[theAudio prepareToPlay];
[theAudio play];
//display alert
if (text!=nil) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil
message:text
delegate:self
cancelButtonTitle:#"Close"
otherButtonTitles:nil];
currentAlert=alert;
[currentAlert show];
duration= theAudio.duration/speed;
[NSTimer scheduledTimerWithTimeInterval:duration
target:self
selector:#selector(dismissAlert)
userInfo:nil
repeats:NO];
[alert release];
}
}
Thanks.
Implement this method, which will be called when the user taps one of the buttons in the UIAlertView.
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
// user tapped a button, don't dismiss alert programatically (i.e. invalidate timer)
}
Docs: http://developer.apple.com/library/ios/#documentation/uikit/reference/UIAlertViewDelegate_Protocol/UIAlertViewDelegate/UIAlertViewDelegate.html#//apple_ref/occ/intfm/UIAlertViewDelegate/alertView:didDismissWithButtonIndex:
When a user taps a button the method alertView:didDismissWithButtonIndex: is called on the UIAlertView's delegate so that's where you should do your work
First you may need to keep a reference to the timer so create a new ivar
// .h
#property (nonatomic, retain) NSTimer *alertViewTimer;
//.m
#synthesize alertViewTimer = _alertViewTimer;
- (void)dealloc;
{
[_alertViewTimer release];
//.. Release other ivars
[super dealloc];
}
- (void)playSound:(NSString *)filename
volume:(float)volume
ofType:(NSString *)type
subtitle:(NSString *)text
speed:(float)speed
loops:(NSInteger)loops
{
// .. the rest of your method
self.alertViewTimer = [NSTimer scheduledTimerWithTimeInterval:duration
target:self
selector:#selector(dismissAlert)
userInfo:nil
repeats:NO];
// .. the rest of your method
}
Then in the delegate method implement the dismissing and invalidation of the timer:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
[alertViewTimer invalidate];
self.alertViewTimer = nil;
// .. Do whatever else you want to do.
}
and you can leave your dismissAlert method a little simpler
- (void)dismissAlert
{
[self.currentAlert dismissWithClickedButtonIndex:0 animated:YES];
}
Solution 2
Another potential way to do this would be to replace this:
[NSTimer scheduledTimerWithTimeInterval:duration
target:self
selector:#selector(dismissAlert)
userInfo:nil
repeats:NO];
with:
[self performSelector:#selector(dismissAlert) withObject:nil afterDelay:duration];
and then implement
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:#selector(dismissAlert)
object:nil];
// .. Do whatever else you want to do.
}
I'm trying to add a spinning activity indicator (UIActivityIndicatorView) to my app while it parses data from the internet. I have an IBOutlet (spinner) connected to a UIActivityIndicatorView in IB. Initially I had it set up like this:
-
(void) function {
self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle: UIActivityIndicatorViewStyleWhite];
self.spinner.hidesWhenStopped = YES;
[spinner startAnimating];
//parse data from internet
[spinner stopAnimating];}
But the spinner wouldn't spin. I read that it had something to do with everything being on the same thread. So I tried this:
- (void) newFunction {
self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle: UIActivityIndicatorViewStyleWhite];
self.spinner.hidesWhenStopped = YES;
[spinner startAnimating];
[NSThread detachNewThreadSelector: #selector(function) toTarget: self withObject: nil];
[spinner stopAnimating];}
But still no luck. Any ideas? Thanks.
Your newFunction: method should look like this:
- (void) newFunction {
self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
self.spinner.hidesWhenStopped = YES;
[NSThread detachNewThreadSelector: #selector(function) toTarget: self withObject: nil];
}
And your function method should look like this:
- (void) function {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self.spinner performSelectorOnMainThread:#selector(startAnimating) withObject:nil waitUntilDone:NO];
//...
[self.spinner performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:NO];
[pool drain];
}
you should not intitialize indicator again .please replace your code with this.
-(void) function {
[spinner startAnimating];
[self performSelector:#selector(newfunction) withObject:nil afterDelay:3.0];
}
- (void) newfunction {
[spinner stopAnimating];
}
Thanks.
Just see that the "//parse data from internet " is synchronous or asynchronous. Asynchronous would mean that a separate thread would start from that point on, and the current function execution will continue without delay.
In your second example, you are explicitly making separate thread, which means that #selector(function) will happen on a separate thread, and the next statement [spinner stopAnimating] is executed immediately. So, it seems like spinner is not spinning at all.
Moreover, make sure you start and stop the activity indicator on main thread only.