MBProgressHUD fades before finishing - iphone

I am having a small issue with MBProgressHUD in my app.
Basically I am using it in the simplest way I can find to do it:
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[self performSelector:#selector(save) withObject:nil afterDelay:0.001];
[self refreshView];
The method refreshView has this line at the end of it's execution:
[MBProgressHUD hideHUDForView:self.view animated:YES];
The MBProgress shows up exactly how it should, but for the last half(ish) of it's duration fades to a barely visible state, making it appear as though the methods have finished executing before they actually finish.
Any ideas on what might be going wrong here?
Thanks a lot!

The concept you need to understand is that of the run loop. The run loop is the internal loop in your program that handles incoming events, timers, networking, etc. (It is provided and run by the system so you don't actually see this loop in your code.)
With this line:
[self performSelector:#selector(save) withObject:nil afterDelay:0.001];
you schedule the -save action to run after 1 ms. (or later, if the main thread is busy doing something else)
After scheduling this -save (so not after running it) you call your refreshView which hides the HUD.
So what actually happens is that you show and hide the HUD in one go. So you see something like the combined animation of showing and hiding it. Independently, your -save method is called.
Contrary to what UIAdam suggests, you do need the performSelector:afterDelay: because you need to give the run loop some time to breathe and display your eye candy on the screen. Changes to the user interface do not happen immediately, but only as the program returns to the run loop.
The fix is therefore to take this line:
[self refreshView];
and move it to the end of your -save method.

It's hard to give a precise solution without seeing more of your code. But basically the problem probably has something to do with the fact that you are calling [self refreshView] and therefore [MBProgressHUD hideHUDForView:self.view animated:YES] before your save method is even called. Why don't you hide the HUD at the end of the save method, or call save directly rather than after a delay. I'm not sure why you have to perform the save after a delay.

Related

is this code using UIActivityIndicatorView flawed?

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.

UIAlertView starts to show, screen dims, but it doesn't pop up until it's too late!

I have a button which when pressed makes calls that load information from a series of URLs (about 5 seconds loading time). Right before actually making those calls I want to add a "loading" alert. When I use a UIAlertView the screen dims like it's about to pop up, but it doesn't until the data is loaded - too late! I have no idea what's going on, it's like the calls I'm making to load the data are immediately taking preference over showing the new view, even though they're made right after the calls adding the new view (or showing the alert).
This is a summarized version of the code:
-(void) refresh{
UIAlertView *av = ...
[av show]; //this should pop up before dat begins to load
[myDataSource loadData]; //this contains a series of [NSData initWithURL] calls
[self.tableView reloadData];
//here I would hide the AlertView, but if I do I see it for just s split second
//when the tableView has already reloaded
}
Thanks in advance for any insight!
***EDIT
To anyone who uses performSelectorInBackground beware of the added complexities of creating what is effectively a threaded program. For example, leaks might appear as the new thread doesn't have an autorelease pool - you have to add one, etc.
when you try to retrieve data from the internet (I guess you are using something like [NSString stringWithContentsOfURL: ...]) the main thread is waiting for those data and for this reason the application cannot redraw the interface.
You can try to use:
[mySourceData performSelectorInBackground:#selector(loadData) withObject:nil];
Be careful if you are using coredata to lock the NSManagedObjectContext before doing any operation on it and unlock it when you finish.
If you have a bunch of operations to perform, in addition to performSelectorInBackground: like cescofry wrote, you can use NSOperation and NSOperationQueue.
You probably don't want an UIAlertView for this anyway. Take a look at MBProgressHUD on GitHub.

How to update a UILabel frequently?

I am currently working on a project where I request and parse multiple html sites in a controller. To give some feedback to the user I have created a second view, which gets displayed during processing of the data. It displays a status label and a progressbar. In my controller I have several points where I update my labels text. Unfortunately this works only sometimes. I guess thats because the label gets redrawn only once in a while and I try to do it probably more often than once a second.
It does not work to force a redraw with [label setNeedsDisplay];
I also made a thread which updates the labels text with the text of a global variable and also calls setNeedsDisplay on the label every second. But the results in the same. I only see certain changes, but not all.
Everything is setup properly and the label never is nil. When I log my updateMethod everything seems allright. It just does not get displayed!
Cheers
Here is the code of my threads
- (void)startUpdateStatusThread{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self performSelectorOnMainThread:#selector(updateFrequently) withObject:nil waitUntilDone:NO];
[pool release];
}
- (void)updateFrequently{
NSLog(#"updateFrequently: %#", currentStatus);
test++;
[self.statusLabel setText:[NSString stringWithFormat:#"%# - %i", currentStatus, test]];
[self.statusLabel setNeedsDisplay];
[NSTimer scheduledTimerWithTimeInterval:0.0001 target:self selector:#selector(updateFrequently) userInfo:nil repeats:NO];
}
Am I right if I assume that you call your label's text-property as well as setNeedsDisplay from within your "Thread" that is parsing your websites?
Note that: changes to UIViews (or subclasses of UIView like your label) must be performed on the main thread.
What I recommend you to do is write a helper method that updates your label and calls setNeedDisplay, the from within your Parser-Thread call
[mainViewController performSelectorOnMainThread:#selector(yourUpdateLabelHelper:) withObject:nil waitUntilDone:NO];
that should get your job done.
You mention threads. Be aware that UIKit controls, such as UILabel, can only be updated from the main thread. Make sure you are only attempting to set the label's text from the main thread and then verify if you are still having issues.
EDIT due to question edit:
First, -setNeedsDisplay only tells the view it needs to redisplay the next time the screen is refreshed, it does not force a display at that time.
Second, the iPhone screen draws at about 60 hz. This means there is an update every 0.016666 or so seconds. You're trying to update much faster than that, so only about every 160th of your updates will actually be displayed.
Third, and probably most important, you don't seem to be making any threads, unless you call -startUpdateStatusThread from a background thread. Either way, once you hit -updateFrequently everything is back on the main thread. With the frequency you are scheduling that timer, you are probably overloading the CPU and making it so that the system does not have time to draw the label. So even though you are setting new text, you aren't giving it a chance to render. You need to reduce the frequency of that timer, and you probably need to rethink whatever it is you're trying to do.
I think that the creation of the timer should be in separate function:
Allow the repetition and also store the timer in a member variable you can close on dealloc.
[NSTimer scheduledTimerWithTimeInterval:0.0001 target:self selector:#selector(updateFrequently) userInfo:nil repeats:YES];
}
The update function should operate as a callback function.
Regards
Assayag

Why does addSubview load the view asynchronously

I have a UIView that I want to load when the user clicks a button. There happens to be some data processing that happens as well after I call addSubview that involves parsing an XML file retrieved from the web. The problem is the view doesn't show up until after the data processing even if addSuview is called first. I think I'm missing something here, can anyone help?
Code: I have a "Loading..." view I'm adding as a custom modal (meaning I'm not using the modalViewController). This action is linked to a button in the navigationController.
- (IBAction)parseXml:(id)sender {
LoadingModalViewController *loadingModal = [[LoadingModalViewController alloc] initWithNibName:#"LoadingModalViewController" bundle:nil];
[navigationController.view addSubview:loadingModal.view];
[xmlParser parse];
}
Howdy! If you're looking for an easy work around:
[self showLoadingScreen]
[self performSelector:#selector(methodToDoWork) withObject:nil afterDelay:0.3];
However you're better off making methodToDoWork asynchronous if you can.
If you are doing your processing on the main thread, it will block the main thread until its done, which means your UI will become unresponsive and not update until the main thread resumes.
You need to perform your XML processing on a background thread using something like NSOperation or an existing asynchronous API and update your view when you have finished.
Its hard to be of more help and get a better idea of whats going wrong without seeing your code unfortunately.

NSRunloops and forcing event processing

I have a few tabs in my iPhone application which take a few seconds to load (pulling large amounts of data from a local sqlite database). When the users touch the tabs it appears as if the application is doing absolutely nothing. I've attempted to put a window showing a spinner up, however it is never shown due to the fact that the processing immediately follows.
I know i have a couple of different options for loading the data asynchronously, however i wanted to poll the community and see if there were any potential problems with just forcing another cycle of the NSRunloop to show the window.
Here's what my code looks like...
[[ActivityIndicator sharedActivityIndicator] show];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]];
On a scale of 1 to 10 how bad would you rate this hack?
I don't know where I'd rate it, but I know I wouldn't want to do it that way. Messing with the system default runloop just seems like a bad idea.
There are a couple of what I think are good approaches. The simplest is to put the additional processing in a separate private method, and then do this:
[[ActivityIndicator sharedActivityIndicator] show];
[self performSelector:#selector(processingMethod) withObject:nil afterDelay:0];
That will cause processingMethod to be called at the end of the run loop, after your indicator is showing. Should work fine.
The one caveat is that if your indicator is animated, depending on how it's set up it may not be animated while processingMethod is running. In that case you'd want to run processingMethod in a background thread, which might be a little more complicated, or might be as simple as doing this instead:
[self performSelectorInBackground:#selector(processingMethod) withObject:nil];
The potential complication there is that at the end of processingMethod, when you're going to display the results of your processing, you may have to call a method back onto the main thread.
My experience is that the event handling code on iPhone is not reentrant. So if you're running the runloop in default mode be prepared for various crashes.
I've found others are having issue too:
http://lists.apple.com/archives/xcode-users/2009/Apr/msg00313.html
http://www.iphonedevsdk.com/forum/iphone-sdk-development-advanced-discussion/16246-trying-make-modal-dialog-using-nested-nsrunloop.html