When I call startAnimating on a UIActivityIndicatorView, it doesn't start. Why is this?
[This is a blog-style self-answered question. The solution below works for me, but, maybe there are others that are better?]
If you write code like this:
- (void) doStuff
{
[activityIndicator startAnimating];
...lots of computation...
[activityIndicator stopAnimating];
}
You aren't giving the UI time to actually start and stop the activity indicator, because all of your computation is on the main thread. One solution is to call startAnimating in a separate thread:
- (void) threadStartAnimating:(id)data {
[activityIndicator startAnimating];
}
- (void)doStuff
{
[NSThread detachNewThreadSelector:#selector(threadStartAnimating:) toTarget:self withObject:nil];
...lots of computation...
[activityIndicator stopAnimating];
}
Or, you could put your computation on a separate thread, and wait for it to finish before calling stopAnimation.
I usually do:
[activityIndicator startAnimating];
[self performSelector:#selector(lotsOfComputation) withObject:nil afterDelay:0.01];
...
- (void)lotsOfComputation {
...
[activityIndicator stopAnimating];
}
This question is quite useful. But one thing that is missing in the answer post is , every thing that takes long time need to be perform in separate thread not the UIActivityIndicatorView. This way it won't stop responding to UI interface.
- (void) doLotsOFWork:(id)data {
// do the work here.
}
-(void)doStuff{
[activityIndicator startAnimating];
[NSThread detachNewThreadSelector:#selector(doLotsOFWork:) toTarget:self withObject:nil];
[activityIndicator stopAnimating];
}
All UI elements require to be on main thread
[self performSelectorOnMainThread:#selector(startIndicator) withObject:nil waitUntilDone:NO];
then:
-(void)startIndicator{
[activityIndicator startAnimating];
}
If needed, swift 3 version:
func doSomething() {
activityIndicator.startAnimating()
DispatchQueue.global(qos: .background).async {
//do some processing intensive stuff
DispatchQueue.main.async {
self.activityIndicator.stopAnimating()
}
}
}
Ok, sorry seems like I went through my code being blind.
I've ended the Indicator like this:
[activityIndicator removeFromSuperview];
activityIndicator = nil;
So after one run, the activityIndicator has been removed completely.
Related
I have a method myButtonAction which performs some heavy calculations, which I need to run on a background thread, while I am loading a view indicating "Task progress" in the main thread. As soon as the, background thread completes executing the method, I need to remove the "task progress" view and load another view in the main thread.
[self performSelectorInBackground:#selector(myButtonAction) withObject:nil];
[self performSelectorOnMainThread:#selector(LoadView) withObject:nil waitUntilDone:YES];
The problem I am facing is that, before myButtonAction completes execution, the LoadView completes its execution. How can I make sure that LoadView starts execution only after myButtonAction completes its execution.
Note: myButtonAction has its method definition in another class.
Use Grand Central Dispatch:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self myButtonAction];
dispatch_async(dispatch_get_main_queue(), ^{
[self LoadView];
});
});
Or, if you want to stay with performSelector methods:
[self performSelectorInBackground:#selector(loadViewAfterMyButtonAction) withObject:nil];
where
- (void)loadViewAfterMyButtonAction
{
[self myButtonAction];
[self performSelectorOnMainThread:#selector(LoadView) withObject:nil waitUntilDone:YES];
}
You need to do following -
[self performSelectorInBackground:#selector(myButtonAction) withObject:nil];
- (void)myButtonAction {
//Perform all the background task
//Now switch to main thread with all the updated data
[self performSelectorOnMainThread:#selector(LoadView) withObject:nil waitUntilDone:YES];
}
EDIT -
Then you can try -
[self performSelectorInBackground:#selector(buttonActionInBackground) withObject:nil];
- (void)buttonActionInBackground {
[self myButtonAction];
//Now switch to main thread with all the updated data
[self performSelectorOnMainThread:#selector(LoadView) withObject:nil waitUntilDone:YES];
}
Now you don't need to change myButtonAction.
I take it that this code gets called at the end of myButtonAction:
[self performSelectorOnMainThread:#selector(LoadView) withObject:nil waitUntilDone:YES];
now no wonder that LoadView finishes before myButtonAction finishes, because you said it to wait until it's done with "waitUntilDone:YES". Call it at the end with waitUntilDone:NO.
For simple solutions to this kind of problems take a look at putting selector calls into the main run loop using [self performSelector:#selector(sel) withObject:obj afterDelay:0.0] or NSTimer - for example if you want to wait until UI updates are finished.
I use a Semaphore Design Pattern for these purposes.
I have this problem where I am adding an UIActivityIndicatorView to a UIScrollView; Everything is working fine, except it does not start spinning unless the UIScrollView is scrolled.
Can anyone help me with this issue?
Thank you.
here is some code:
UIActivityIndicatorView *loaderActivity = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
loaderActivity.center = CGPointMake(87/2,y+56/2);
loaderActivity.tag=tag;
[mainScrollView addSubview:loaderActivity];
[loaderActivity startAnimating];
[loaderActivity release];
You need to call startAnimating on the activity indicator to have it animate. Alternatively in interface builder you can tick the "animating" tickbox.
The fact that it's not animating until you scroll in the scroll view is a symptom that your call to startAnimating is happening in a background thread. UIKit calls should be made in the main thread.
You can verify that it's happening in a background thread by adding code like this:
if ([NSThread isMainThread]) {
NSLog(#"Running on main thread.");
} else {
NSLog(#"Running on background thread.");
}
You'll want to make sure all of the code you showed in your question is running on the main thread. To do this, you can change your code to look something like this:
// this code would be wherever your existing code was
[self performSelectorOnMainThread:#selector(addActivityIndicatorToView:) withObject:mainScrollView waitUntilDone:YES];
// this would be a new method in the same class that your existing code is in
- (void) addActivityIndicatorToView:(UIView*) view {
UIActivityIndicatorView *loaderActivity = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
loaderActivity.center = CGPointMake(87/2,y+56/2);
loaderActivity.tag=tag;
[view addSubview:loaderActivity];
[loaderActivity startAnimating];
[loaderActivity release];
}
activityIndicator = [[UIActivityIndicatorView alloc]
initWithFrame:CGRectMake(87/2,y+56/2);
[activityIndicator setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleWhite];
activityIndicator.tag=tag;
[mainScrollView addSubview:loaderActivity];
[activityIndicator startAnimating];
[activityIndicator release];
Had an issue where I made the startAnimating call in the viewDidLoad and didn't work. I moved the call in the viewWillAppear and it worked!
Try running the animator in a different thread.
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:#selector(showProgress) object:nil];
[thread start];
//do whatever your want here
//call this when you want it stop animating
[activityIndicator stopAnimating];
[thread release];
- (void)showProgress{
[activityIndicator startAnimating];
}
I want to do some action on a thread using the following API, so strange selector poiOneBoxSearch hasn't been invoked, why? Any mistake on the code? Thanks.
- (void)poiOneBoxSearch{
[self poiOneBoxSearcWithQueryString:#"coffee" isFinished:YES];
}
- (void)test1{
NSThread* thread = [[NSThread alloc] init];
[self performSelector:#selector(poiOneBoxSearch)
onThread:thread
withObject:nil
waitUntilDone:YES];
[thread release];
}
If You want use performSelector Method You should Read below Link
,I Think You missed SOmething
Please Goes Through This Link
If Not you may Use Below Code.
Try This
- (void)test1{
[NSThread detachNewThreadSelector:#selector(poiOneBoxSearch) toTarget:self withObject:nil];
}
Try this:
[self performSelectorInBackground:#selector(poiOneBoxSearch) withObject:nil waitUntilDone:YES];
[self performSelectorInBackground:#selector(poiOneBoxSearch) withObject:nil];
- (void) poiOneBoxSearch{
#autoreleasepool {
[self poiOneBoxSearcWithQueryString:#"coffee" isFinished:YES];
} }
The most important thing that you have to keep in mind is that since this method creates a thread on the given selector, the selector must have an autorelease pool just like any other thread in a reference-counted memory environment.
So when I click on a callout accessory in my mapView, nothing happens for several seconds because it is making a url request and parsing it, so I wanted to show the activity indicator so the user doesn't think it's frozen. Here's the code:
- (void)mapView:(MKMapView *)mv annotationView:(MKAnnotationView *)pin calloutAccessoryControlTapped:(UIControl *)control {
// start activity indicator
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSLog(#"tapped");
ArtPiece *artPiece = (ArtPiece *)pin.annotation;
//when annotation is tapped switches page to the art description page
artDescription *artD = [[artDescription alloc] initWithNibName:#"artDescription" bundle:nil];
artD.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
artD.startingLocation = mapView.userLocation.location.coordinate;
artD.selectedArtPiece = artPiece;
NSLog(#"0");
[self presentModalViewController:artD animated:YES];
NSLog(#"1");
[artD loadArt:artPiece];
NSLog(#"2");
// stop activity indicator
//[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[artD release];
}
Strangely (to me anyway, maybe I'm missing something obvious as I'm pretty inexperienced), the activity indicator does not show until after the method is done, and the modal view starts animating into view. I put the NSLogs in to see what was taking time. I had about a 2 second pause between "0" and "1" and another couple seconds between "1" and "2". Then the indicator finally showed, so I am sure it is waiting until the end of the method for some reason. Any ideas why?
The change to the UI, displaying the activity indicator, does not take effect until control has returned to the application's main run loop. This does not occur until after your method has ended and the stack has unwound. You need to show the activity indicator, then dump the activity you are waiting for onto a background thread:
[self performSelectorInBackground:#selector(doThingINeedToWaitFor:)
withObject:anObject];
(Note that Apple recommends that you move away from using threads explicitly; performSelectorInBackground:withObject: is the simplest method to get some code run off the main thread. More complex options are available for other situations. See the Concurrency Programming Guide.)
The important gotcha is that UI updates still need to be handled on the main thread, so in that method, when the work is done, you need to call back to stop the activity indicator:
- (void) doThingINeedToWaitFor: (id)anObject {
// Creating an autorelease pool should be the first thing
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Do your work...
// ...
// Update the UI back on the main thread
[self performSelectorOnMainThread:#selector(allDoneWaiting:)
withObject:nil
waitUntilDone:YES];
// Clear out the pool as the final action on the thread
[pool drain];
}
In your callback method, you hide the activity indicator again and do any other post-processing that's necessary.
You cannot start and stop the activity indicator in the same function.
See the answer I provided for this question: How show activity-indicator when press button for upload next view or webview?
Edit for clarity:
- (void) someFunction
{
[activityIndicator startAnimation];
// do computations ....
[activityIndicator stopAnimation];
}
The above code will not work because you do not give the UI time to update when you include the activityIndicator in your currently running function. So what I and many others do is break it up into a separate thread like so:
- (void) yourMainFunction {
activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[NSThread detachNewThreadSelector:#selector(threadStartAnimating) toTarget:self withObject:nil];
//Your computations
[activityIndicator stopAnimating];
}
- (void) threadStartAnimating {
[activityIndicator startAnimating];
}
Something is slowing down your spinner. I would recommend doing your heavy lifting in background, using a thread. Try this:
-(void)myMethod{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[NSThread detachNewThreadSelector:#selector(startWorkingThread) toTarget:self withObject:nil];
}
-(void)startWorkingThread{
//Heavy lifting
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
I assume that you have commented the:
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
For testing purposes...
I'm using two threads in an iPhone app for the first time and I've run into a problem. In this prototype, I have a view controller that loads a local web page. I want an activity indicator to show until the page has finished loading. With the code below, the activity indicator starts, the page loads properly, but the activity indicator does not stop or hide. It doesn't look like the "loading" function ever gets called.
What am I doing wrong?
- (void)viewDidLoad {
[self.view addSubview:activityIndicator];
[activityIndicator startAnimating];
[NSThread detachNewThreadSelector:#selector(getData) toTarget:self withObject:nil];
[super viewDidLoad];
}
- (void)getData {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[detailWebView loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"page1" ofType:#"html"]isDirectory:NO]]];
[NSTimer scheduledTimerWithTimeInterval: (1.0/2.0) target:self selector:#selector(loading) userInfo:nil repeats:YES];
[pool release];
}
- (void)loading {
if(!detailWebView.loading){
[activityIndicator stopAnimating];
[activityIndicator removeFromSuperview];
}
There's an easier way to do this without creating your own thread.
- (void)viewDidLoad {
[self.view addSubview:activityIndicator];
[activityIndicator startAnimating];
[detailWebView setDelegate:self];
[detailWebView loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"page1" ofType:#"html"]isDirectory:NO]]];
[super viewDidLoad];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[activityIndicator stopAnimating];
}
You 'd betetr stop the activity indicator in the webview delegate method :
-(void)webViewDidFinishLoad:(UIWebView*)webView
Have you added debug output to loading to whether it gets called ? The code sure looks like that, it should get called after 0.5 seconds and I'd guess that detailWebView is still loading then.
Also, GUI stuff should be run on the main thread, so you may need to do:
- (void)loading {
if(!detailWebView.loading){
[activityIndicator performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:YES];
[activityIndicator performSelectorOnMainThread:#selector(removeFromSuperView) withObject:nil waitUntilDone:YES];
}
}
You're using a webview, which already provides a mechanism to know when a load has finished, and already loads data asynchronously - the use of a custom thread really isn't needed.
Register your object as the UIWebView's delegate. Call loadRequest on the UIWebView and start animating the progress indicator. Stop animating the progress indicator in:
-(void)webViewDidFinishLoad:(UIWebView*)webView
This method is defined by the UIWebViewDelegate protocol - make sure your object implements this protocol. You implement this method:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[activityIndicator stopAnimating];
}
http://developer.apple.com/iphone/library/documentation/uikit/reference/UIWebViewDelegate_Protocol/Reference/Reference.html