I perform this selector in my application:
- (void) doFilter:(UIButton*)button {
[activityIndicator startAnimating];
[self disableButtons];
// ...
// some actions
// ...
sleep(2);
[self enableButtons];
[activityIndicator stopAnimating];
}
when user clicks on button. activityIndicator is UIAtivityIndicatorView. But I don't see any activity indicators while this code is performing. How can I fix it?
Firstly, you should never ever use sleep on the main thread. It blocks your entire app and the user can't do anything for that time.
Secondly, the UI is not updated until your code returns control to the run loop. So whenever you call startAnimating and stopAnimating in the same method without returning to the run loop in between, you can be sure that nothing will happen in the UI (same with disableButtons and enableButtons).
Solution: call startAnimating and disableButtons. Then start the tasks you have to perform in the background so that the UI is not blocked. You can use NSOperation, performSelectorInBackground:..., Grand Central Dispatch etc. for that. Finally, when the long task is finished, have it call another method on the main thread which then calls stopAnimating and enableButtons.
Related
I have the following code, which is executed when a button is pressed:
[self performSelector:#selector(timeout:) withObject:nil afterDelay:30.0];
The issue comes in when I wanted to cancel this from a background thread:
[NSObject cancelPreviousPerformRequestsWithTarget:self];
I did this and it didn't cancel, it still calls timeout after 30 second. So my question, is there a way to cancel this from the background thread?
From the documentation, 'This method removes perform requests only in the current run loop, not all run loops.' That means that you have to call cancelPreviousPerformRequestsWithTarget on the main thread. Use performSelectorOnMainThread:withObject:waitUntilDone: from your thread to schedule a call to cancelPreviousPerformRequestsWithTarget on the main thread.
It's a roundabout way of doing things, but should work.
Edit to show example:
The easiest way is to use a helper method:
-(void)cancelTimeout
{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
}
Then on your background thread call this when you want to cancel the timeout:
[self performSelectorOnMainThread:#selector(cancelTimeout) withObject:nil waitUntilDone:NO];
You can use NSTimer to call method after some time and invalidate if you want
Is this code using UIActivityIndicatorView flawed? It appears that I don't actually get to see the indicator/spinner at all here, so is this because the view isn't drawn until the who viewDidLoad completes?
Is the only way around this to do the viewDidLoad custom work (e.g. data updates) on a separate thread? (I was hoping in this case for an easier single-thread operation). Is there a way to force the view to refresh after the "startAnimating" line perhaps prior to the data loading commencment?
Code from UITableViewController implementation:
- (void)viewDidLoad {
// Wait indicator - Start
self.waitView = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge] autorelease];
self.waitView.hidesWhenStopped = true;
[self.view addSubview: self.waitView];
// Load data into tableview
[NSThread sleepForTimeInterval: 5.0]; // Test code to simulate
[self.waitView stopAnimating];
}
You should also call startAnimating. Sleeping is not a good idea. I would prefer the performSelector-methods which starts a not recurring NSTimer under the hood.
Try this:
-(void) doStuff:(id)aSender
{
[self.waitView stopAnimating];
}
-(void)viewDidLoad
{
...
[self performSelector:#selector(doStuff:) withObject:self afterDelay:5.0];
}
in addtion: also set the frame- or bounds-property of the ActivityIndicatorView somewhere like sosborn said in his comment
Actually the answer from Thomas should work as it is, I will add a little explanation as to why not use sleep as you have done it.
All the UI processing on iPhone (and most of OSs as well) is being done in only one thread - the main thread, the thread that executes the so called run loop. If you stop that thread the UI will stop, nothing will be drawn.
Putting sleep into viewDidLoad, which runs in the main thread, will do just that - stop UI from doing anything. So because immediately after wakeup you've called [self.waitView stopAnimating] and the activityview should hide when not animating, you can't see it at all - you just didn't give it any time to show.
Thomas used a NSTimer to call stopAnimating after 5 seconds - now this lets the main thread to execute code before stopping animation and hiding waitView and this will work for your test.
Better yet you just let it animate without any timer and use a delegate patter to be informed by the tableView loading code after the data has been loaded, then stop animating. You don't know how long loading of data will last, so it's better to wait until it's finished than stop animating after any specific time.
Oh well, and the size and position, makes sense, but for testing it doesn't matter and is not the cause of not seeing it - if not specified it will be added at 0,0 and have a default size so you will see it anyway.
I have a method that does a time consuming operation, say something like ten consecutive calls to
[[NSString alloc] initWithContentsOfURL:u];
I want a UIActivityIndicatorView that was in a hidden state before the method call to show and animate, so I write:
activityIndicator.hidden = NO;
[activityIndicator startAnimating];
at the beginning of the method
but of course it won't work. The UIActivityIndicatorView will only animate once the method is over.
This is not acceptable. I must show the animation during the function call.
Anyone knows how to do it?
NSOperation maybe? (anyone has a sample thereof?)
I assume you are doing some expensive work in this method and while that work is being performed, you want the activity indicator to spin. Expensive work should NOT be done on the main thread (iOS might kill your app!). Put your expensive work on a separate thread with:
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
...and when the method (aSelector) is done, call:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
...and there you stop the activity indicator.
Never call any UI code from within a non-main thread!
My application has a button when clicked it is disabled, an activity indicator displayed and a background task is executed. When this task is completed a callback updates the interface by enabling the button and removing the activity indicator. The problem I having is the task is completing the callback function is executed but for a period of time the activity monitor remains on the screen, the button looks like it is disabled but it is possible to click it again. Can anyone tell me where I am going wrong?
Thanks very much!
Could it be that the callback-method is being executed in a separate thread?
I'm asking, because any calls that have impact on a view should be performed on the main thread.
The problem might be solved by doing the following:
create a Method that handles your UI-related code and gets called within your callback method
the UI-related code has to be performed on the main thread
It could look a little bit like this:
//gets called asynchronously when your operation has completed
-(void)myCallbackHandler {
[self performSelectorOnMainThread:#selector(updateUI) withObject:nil
waitUntilDone:NO];
}
-(void)updateUI {
[myActivityIndicatorView stopAnimating];
[myButton setEnabled:YES];
}
I have a problem that i want to call a function when one of my functions that is running into a seperate thread comes to an end:
[NSThread detachNewThreadSelector:#selector(fetchFeaturedFreeApps) toTarget:self withObject:nil];
here is my fetchFeaturedFreeApps function
-(void)fetchFeaturedFreeApps
{
////all my code
[self performSelector:#selector(closeView) withObject:nil afterDelay:4.0];
}
My problem is that the close view methods doesnt run after the 4 seconds.
Hoew if i call the fetchFeaturedFreeApps method with perform selector then my closeview metod is called properly.
Your valuable help is highly appreciated.
You want to run any UI or view update code in a selector that runs on the main thread, not in a background thread. Use -performSelectorOnMainThread:withObject:waitUntilDone: and place a timer in that selector to fire after four seconds, which closes your view.