iPhone activity indicator being delayed? - iphone

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...

Related

Dismiss MBProgressHUD when delegate method fires

First time working with a HUD and I'm confused.
I setup the HUD like this in my viewDidLoad:
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[[[WSXmppUserManager shared] xmppStreamManager] connect];
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
});
The HUD doesn't show. I think the reason is as follows. The xmpp connect method fires off a connection request to the xmpp server and then it's done. So there is no activity to wait for as is.
However, the connection isn't established until the server responds and the following delegate method is fired:
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
I want to wait for this and only then dismiss the HUD, but I'm at a loss as to how to do that. I'm missing something very simple.
You need to move this code
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
After your long running method has finished... that is, if this code is indeed returning immediately
[[WSXmppUserManager shared] xmppStreamManager] connect];
The hud is likely never going to display... as it gets told to display and then told to hide on the same run loop or perhaps one run loop right afterwards...
Why not put it at the end of this method if this indicates that a response has been received and the operation is completed?
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
HUD =[[MBProgressHUD alloc] initWithWindow:self.view];
[HUD setDelegate:self];
[self.view addSubview:HUD];
[HUD showWhileExecuting:#selector(connectToServer)
onTarget:self
withObject:nil
animated:YES];
In the connectToServer
-(void)connectToServer
{
[[[WSXmppUserManager shared] xmppStreamManager] connect];
}
As soon as the connectToServer method comepletes it task in the background , a delegate of MBProgressHUD called hudWasHidden: is automatically called
-(void)hudWasHidden:(MBProgressHUD *)hud
{
//Further work after the background task completed
}

Activity Indicator is not showing with SVProgressHUD

I am trying to implement the SVProgressHUD progress activity indicator. I copied the class from the [demo].1
My app loads up but the activity indicator doesn't show up. This is my first time trying to use one of these, so any help would be appreciated.
Here is the code:
#import "SVProgressHUD.h"
#implementation QuotesAppDelegate
- (void)startLoading
{
//call this in your app delegate instead of setting window.rootViewController to your main view controller
//you can show a UIActivityIndiocatorView here or something if you like
[SVProgressHUD show];
[self performSelectorInBackground:#selector(loadInBackground) withObject:nil];
}
- (void)loadInBackground
{
//do your loading here
//this is in the background, so don't try to access any UI elements
[self populateFromDatabase];
[SVProgressHUD dismiss];
[self performSelectorOnMainThread:#selector(finishedLoading) withObject:nil waitUntilDone:NO];
}
- (void)finishedLoading
{
//back on the main thread now, it's safe to show your view controller
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}
- (void)applicationDidFinishLaunching:(UIApplication *)application {
[self copyDatabaseIfNeeded];
[self startLoading];
}
For anyone else having a similar problem, this can also happen because you have a long loop or a piece of code that takes a long time to execute. If this happens, your progress bar wont be shown until after the loop, which kind of defeats the purpose.
To solve this issue you need to you this:
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
Basically your code would look something like this:
- (IBAction)submitPost:(id)sender {
//now we show the loading bar and submit the comment
[SVProgressHUD showWithStatus:#"Submitting post" maskType:SVProgressHUDMaskTypeGradient];
SEL aSelector = #selector(submitDataOfPost);
[self performSelectorInBackground:aSelector withObject:sender];
}
This will basically load the progress bar, and in a background thread, the method you want to execute will be called. This makes sure that the UI is updated (shows the progress hud) at the same time that your code is executed.
First of all you were not adding your SVProgressHUD to the view.
If your class inherited from UIViewController then [self.view addSubview:]; or if your class is simple UIView then [self addSubView:];
I do not understand your requirement but as far as i can understand through your code that you are showing [SVProgressHUD show]; in your startLoading method and then you are calling loadInBackground method in that method where you are hiding your hud using [SVProgressHUD dismiss];
I will suggest you to trace it by using breakpoint and figure it out.
I had the same problem. When I changed a version of SVProgressHUD to the later one the problem disappeared. My current version supports ARC.
=>
(IBAction)fetchData:(id)sender
{
[SVProgressHUD showWithStatus:#"Loading..." maskType:SVProgressHUDMaskTypeGradient];
[self performSelectorOnMainThread:#selector(getDataFromSomeWhere) withObject:nil waitUntilDone:NO];
}
=>
(void)getDataFromSomeWhere
{
//Do your data populating here. and dismiss the ProgressHud.
[SVProgressHUD dismiss];
}

NetworkActivityIndicator not working the same on iPhone and Simulator?

I am using the NetworkActivityIndicator to show that my App is doing some work. When I run the app in the simulator, it shows the way I want - basically spinning the entire time until the selected tab loads the data from the server - but when I put the app onto my phone, I only get a split-second of the spinner before it disappears. Usually only spins right before the view appears on the screen.
Ideas?
EDIT: The problem might have to do with the fact I am using a TabBar... In the simulator the ActivityIndicator will spin on Screen/Tab 1 while Screen/Tab 2 is loading. On the phone, I only see the ActivityIndicator for a split second after Screen 2 finally appears.
-(void)viewDidLoad {
// call to spinTheSpinner
[NSThread detachNewThreadSelector:#selector(spinTheSpinner) toTarget:self withObject:nil];
// method to Get the Data from the Server
[self getDataFromServer];
}
-(void)spinTheSpinner {
NSLog(#"Spin The Spinner");
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[self performSelectorOnMainThread:#selector(doneSpinning) withObject:nil waitUntilDone:YES];
[pool release];
}
-(void)doneSpinning {
NSLog(#"done spinning");
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
-(void)getDataFromServer {
// does a bunch of stuff to retrieve and display data
}
You turn on the spinner here…
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
But immediately you turn it off…
[self performSelectorOnMainThread:#selector(doneSpinning) …];
Of course it won't show. I'm surprised it shows in the simulator.
The -doneSpinning method should be called after -getDataFromServer is done, or just do
UIApplication* app = [UIApplication sharedApplication];
app.networkActivityIndicatorVisible = YES;
[self getDataFromServer]; // assumes it is blocking.
app.networkActivityIndicatorVisible = NO;

Activity indicator (spinner) with UIActivityIndicatorView

I have a tableView that loads an XML feed as follows:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if ([stories count] == 0) {
NSString *path = #"http://myurl.com/file.xml";
[self parseXMLFileAtURL:path];
}
}
I'd like to have the spinner to show on the top bar at the app launch and dissapear once the data is displayed on my tableView.
I thought that putting the beginning at viewDidAppear and the end at -(void)parserDidEndDocument:(NSXMLParser *)parser but it didn't work.
I'd appreciate a good explained solution on how to implement this solution.
Here's the problem: NSXMLParser is a synchronous API. That means that as soon as you call parse on your NSXMLParser, that thread is going to be totally stuck parsing xml, which means no UI updates.
Here's how I usually solve this:
- (void) startThingsUp {
//put the spinner onto the screen
//start the spinner animating
NSString *path = #"http://myurl.com/file.xml";
[self performSelectorInBackground:#selector(parseXMLFileAtURL:) withObject:path];
}
- (void) parseXMLFileAtURL:(NSString *)path {
//do stuff
[xmlParser parse];
[self performSelectorOnMainThread:#selector(doneParsing) withObject:nil waitUntilDone:NO];
}
- (void) doneParsing {
//stop the spinner
//remove it from the screen
}
I've used this method many times, and it works beautifully.
Starting a new thread can be overkilling and a source of complexity if you want to do things that are supposed to start on the main thread.
In my own code, I need to start a MailComposer by pushing a button but it can take some time to appear and I want to make sure the UIActivityIndicator is spinning meanwhile.
This is what I do :
-(void)submit_Clicked:(id)event
{
[self.spinner startAnimating];
[self performSelector:#selector(displayComposerSheet) withObject:nil afterDelay:0];
}
It will queue displayComposerSheet instead of executing it straight away. Enough for the spinner to start animating !
I typically implement an NSTimer that will call my spinner method, which I fire off right before I go into doing the heavy work (the work that will typically block the main thread).
The NSTimer fires and my spinner method is called. When the main work is finished, I disable the spinner.
Code for that is like:
IBOutlet UIActiviyIndicatorView *loginIndicator;
{
...
[loginIndicator startAnimating];
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(executeAuthenticationRequest)
userInfo:nil repeats:NO];
...
}
- (void) executeAuthenticationRequest
{
/* Simulate doing a network call. */
sleep(3);
[loginIndicator stopAnimating];
...
}
You can also do:
IBOutlet NSProgressIndicator *pIndicator;
Start:
[pIndicator startAnimation:self];
[pIndicator setHidden:NO];
And Stop:
[pIndicator stopAnimation:self];
[pIndicator setHidden:YES];
In Cocoa (and most other app frameworks) the user interface is updated by the main thread. When you manipulate views, they are typically not redrawn until control returns to the run loop and the screen is updated.
Because you are parsing the XML in the main thread, you are not allowing the screen to update, and that is why your activity indicator is not appearing.
You should be able to fix it by doing the following:
In viewDidAppear, show/animate the spinner and then call
[self performSelector:#selector(myXMLParsingMethod) withObject:nil afterDelay:0];
In myXMLParsingMethod, parse your XML, then hide/stop the spinner.
This way, control will return to the run loop before parsing begins, to allow the spinner to begin animating.

UITabBar appearance problem + NSThreads

I'm having a problem when trying to add a UITabBar as a subview of my AppDelegate's window.
The link above shows a screenshot of the messy state of the screen.
TabBarInAMessyState.png
The results are unpredictable. In this picture only the UITabBarItem's titles were affected, but sometimes the TabBar background is not shown (consequently we can see the window's background). Sometimes the NavigationBar is also affected (not show in this picture).
When I start the Application I first have to check if there's network connection, so It is called a method (verifyNetworkAvailability:) that will run in a thread different from the main thread. This is done in order not to freeze the application.
- (void)applicationDidFinishLaunching:(UIApplication *)application {
[window makeKeyAndVisible];
// check if there's network connection in another thread
[NSThread detachNewThreadSelector: #selector(verifyNetworkAvailability:) toTarget:self withObject:self];
}
- (void) verifyNetworkAvailability:(MyAppDelegate*) appDelegate {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Check if there's network connection..
// If so, call the verifyNetworkAvailabilityDidEnd method
[appDelegate verifyNetworkAvailabilityDidEnd];
[pool release];
}
- (void) verifyNetworkAvailabilityDidEnd {
[window addSubview:tabBarController.view];
}
I'd like to know if it is possible to add the tabBarController.view in this way (by a method call done in thread other than the main thread).
Thanks in advance
Try this
- (void) verifyNetworkAvailability:(MyAppDelegate*) appDelegate {
// other code here ...
[appDelegate performSelectorOnMainThread:#selector(verifyNetworkAvailabilityDidEnd) withObject:nil waitUntilDone:NO];
}
UIKit has some trouble when you try to access it from any thread but the main thread. Think about dispatching a notification to have your primary app thread to add the view rather than adding the view directly in your secondary thread.