I want to build object and then open a controller with it. Building can take up to 5 seconds and I want to show a message while it processing.
I have the following implementation of didSelectRowAtIndexPath:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
messageView.hidden = NO;
// Some methods
Controller *ctrl = [Controller new];
[self.navigationController pushViewController:ctrl animated:YES];
}
Everything is good but there is a problem: messageView appears ONLY when push animation starts. What can I do to fix that?
Similar to Jonathan's answer, delay the push a little to give the messageView time to appear.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
messageView.hidden = NO;
int64_t oneMillisecond = NSEC_PER_MSEC;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, oneMillisecond), dispatch_get_main_queue(), ^(void){
// Some methods
Controller *ctrl = [Controller new];
[self.navigationController pushViewController:ctrl animated:YES];
});
}
It's not displaying because you're blocking the main thread while building the object.
The user interface will not update until you return control to the run loop.
The solution is to build your object on a background thread, the easiest way of doing this is with libdispatch, like so:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
messageView.hidden = NO;
// you may want to disable user interaction while background operations happen
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Perform your lengthy operations here
Controller *ctrl = [[Controller alloc] init];
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController pushViewController:ctrl animated:YES];
}
});
}
If you want a UIAlertView you can use this code:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Title..." message:#"More?" delegate:nil cancelButtonTitle:nil otherButtonTitles:nil];
[alert show];
After your done you can call this to dismiss:
[alert dismissWithClickedButtonIndex:0 animated:YES];
Can you try with this:
[UIView animateWithDuration:0.5f delay:0.0f options:UIViewAnimationCurveLinear animations:^(void)
{
messageView.hidden = NO;
}
completion:^(BOOL finished)
{
Controller *ctrl = [Controller new];
[self.navigationController pushViewController:ctrl animated:YES];
}];
The view probably isn't redrawn during your didSelectRowAtIndexPath call.
So... I would try running the long running method in a block. Then block the main thread with you messageView animation and have your block post a notification or something to shut it down.
You might want to have some kind of condition for the messageView to shut itself down after a certain time.
Related
In one stack I am displaying the MBProgressHUD and if by using the other stack when some calculation called I want MBProgressHUD to remove from the view but it is not been removed from the hud ..check what mistake I am doing..
first stack called LoginViewController.m
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
}
-(void)myTask {
// Do something usefull in here instead of sleeping ...
[MBProgressHUD hideHUDForView:self.view animated:YES];
[self.hud hide:YES];
self.hud=nil;
[self.hud removeFromSuperview];
//[self.hud showWhileExecuting:#selector(myTask1) onTarget:self withObject:nil animated:YES];
}
now theViewController get calls but view will be same Previous and
after some calculation and I want that in ViewController I want to remove theHUD from the view by calling the method in the LoginViewController..check code
- (void)didReceiveResponseFromServer:(NSString *)responseData
{
login=[[LoginViewController alloc]init];
[self.login myTask];
}
Set UP MBProgressHUD
- (void) setupHUD
{
//setup progress hud
self.HUD = [[MBProgressHUD alloc] initWithFrame:self.window.bounds];
[self.SpurView addSubview:self.HUD]; // add it as here.
self.HUD.dimBackground = YES;
self.HUD.minSize = CGSizeMake(150.f, 150.f);
self.HUD.delegate = self;
self.HUD.labelText = #"Loading...";
}
Then use for hide [self.HUD hide:YES]; as describe in your code .
like me ,
I tyr [MBProgressHUD hideHUDForView:bAnimatedView animated:YES]
but it will no work at times when I quick push in and back out .
So I add something to check the view of MBProgressHUD.
MBProgressHUD *HUD = [MBProgressHUD HUDForView:bAnimatedView];
if (HUD!= nil) {
[HUD removeFromSuperview];
HUD=nil;
}
I have to download an image from the server but I do not wish to create NSURLConnection, I know that UIKit is not thread safe so I tried this, just need a confirmation whether if this is safe or can it result in a crash (as of now it is working fine.)I tried the following
Look at the case 2 of the switch.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.row) {
case 0:
{
CreateNewSurveyViewController *vc=[[CreateNewSurveyViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
[vc release];
break;
}
case 1:{
MySurveyViewController *mySurveyViewController=[[MySurveyViewController alloc] init];
[self.navigationController pushViewController:mySurveyViewController animated:YES];
[mySurveyViewController release];
break;
}
case 2:{
self.progressHud.hidden = NO;
[self performSelectorInBackground:#selector(loadProfileImage) withObject:nil];
break;
}
default:
break;
}
}
-(void)loadProfileImage {
NSData* profileImageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:self.userAccount.profileImageURL]];
[self performSelectorOnMainThread:#selector(launchSettingsView:) withObject:profileImageData waitUntilDone:YES];
}
-(void)launchSettingsView:(NSData*)profileImageData {
self.userAccount.userImage = [UIImage imageWithData:profileImageData];
self.progressHud.hidden = YES;
SettingsViewController* settingsViewController=[[SettingsViewController alloc] init];
settingsViewController.userAccount = self.userAccount;
settingsViewController.delegate = self;
[self.navigationController pushViewController:settingsViewController animated:YES];
[settingsViewController release];
}
This looks safe to me. You do all your networking on a background thread, and then only touch the UI in the main thread method you call back to. Usually people will use GCD or NSOperationQueue but this should work too.
As long as the actual UI update work is done on the main thread, you will be fine.
I am using the master-detail example on my app.
And I added the MBProgressHUD to show a loading screen while the detail get loaded.
The thing is that I dont know what am I doing wrong with the threads but I have ended up with 2 ways of doing it:
1 - If I do not throw the dispatch_async(), the HUD is showed with delay;
2 - If I perform the segue inside the dispatch_async(), it takes more time than necessary to load stuff.
Heres da code for example 1:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[HUD show:YES];
[self performSegueWithIdentifier:#"detail" sender:nil];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Heres da code for example 2:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[HUD show:YES];
dispatch_queue_t taskQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(taskQ, ^{
[self performSegueWithIdentifier:#"detail" sender:nil];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
});
}
Any leads?
This is a solution that worked for me:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
HUD = [MBProgressHUD showHUDAddedTo:((AppDelegate*)[[UIApplication sharedApplication] delegate]).window.rootViewController.view animated:YES];
HUD.labelText = #"Loading";
HUD.labelFont = [UIFont systemFontOfSize:18];
HUD.delegate = self;
[HUD showWhileExecuting:#selector(openYourNewView) onTarget:self withObject:nil animated:YES];
});
}
-(void)openYourNewView {
[self performSegueWithIdentifier:#"YourViewIdentifier" sender:self];
}
However, I still have some weird rotation on previous view, and I don't know why.
I‘ve managed a way around this somehow!
Create a normal method where you will call the progressHUD and call the second
Create the second method where you do the time consuming stuff (loading views)
Perform that method on the main thread
Sample:
-(void)callHUD {
[progressHUD show];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self performSelectorOnMainThread:#selector(loadView) withObject:nil waitUntilDone:YES];
dispatch_async(dispatch_get_main_queue(), ^{
[progressHUD dismiss];
});
});
}
-(void)loadView {
//Perform your segue or transition which needs to load
}
Hope that will help others looking for the answer. ;) Cheers
My issue is that the back button will not restore its visibility if my web request does not finish before or soon after ViewWillAppear has fired.
I have a navigation based iPhone 4.0 application used a simple Root and Detail view setup.
I am working with data that is returned from a webservice so when I push my detail view in its ViewDidLoad function I call my web service method in a separate thread and the Iphone lifecycle does its thing on the main thread. I must disable/hide the back button until the web request has finished (or failed) so I call self.navigationItem.hidesBackButton = YES; in ViewDidLoad and self.navigationItem.hidesBackButton = NO; in the delegate function which fires once my web request has finished or failed.
I already tried the following:
[self.navigationItem performSelectorOnMainThread:#selector(setHidesBackButton:) withObject:NO waitUntilDone:NO];
[self.navigationItem setHidesBackButton:NO];
[self.view setNeedsDisplay];
[self.navigationController.view setNeedsDisplay];
UINavigationItem *nav = self.navigationItem;
nav.hidesBackButton = NO;
Root View Controller Push Code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
ArticleViewController *articleViewController = [[ArticleViewController alloc] initWithNibName:#"ArticleViewController" bundle:nil];
NewsArticle *newsArticle = [newsItems objectAtIndex:indexPath.row];
articleViewController.articleID = newsArticle.newsID;
[self.navigationController pushViewController:articleViewController animated:YES];
[newsArticle release];
[articleViewController release];
}
Details View Controller Code:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.hidesBackButton = YES;
id scrollView = [[[self webContent] subviews] objectAtIndex:0];
if([scrollView respondsToSelector:#selector(setBackgroundColor:)] )
{
[scrollView performSelector:#selector(setBackgroundColor:)
withObject:[UIColor blackColor]];
}
[self getNewsArticle];
}
//Fires when the web request has finished
- (void) finish:(NewsArticle *)newsArticleFromSvc {
self.navigationItem.hidesBackButton = NO;
self.newsArticle = newsArticleFromSvc;
[self bindNewsArtice];
}
Any help is GREATLY appreciated I can hardly ##$&^ believe that hiding a button in a UI could cause me this much wasted time.
Try use this method of UINavigationItem :
- (void)setHidesBackButton:(BOOL)hidesBackButton animated:(BOOL)animated
I wasn't able to solve this problem. Instead I tweaked my App Logic to make hiding he back button not necessary.
I have an App using UITableViews and fetching data from a server. I am attempting to put a UIActivityIndicatorView on the Parent UITableView, so it spins while the Child UITableView is loading. I have the UIActivityIndicatorView all hookedup through Interface Builder, etc.
-(void)spinTheSpinner {
NSLog(#"Spin The Spinner");
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[spinner startAnimating];
[NSThread sleepForTimeInterval:9];
[self performSelectorOnMainThread:#selector(doneSpinning) withObject:nil waitUntilDone:NO];
[pool release];
}
-(void)doneSpinning {
NSLog(#"done spinning");
[spinner stopAnimating];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[NSThread detachNewThreadSelector:#selector(spinTheSpinner) toTarget:self withObject:nil];
NSMutableString *tempText = [[NSMutableString alloc] initWithString:[categoryNumber objectAtIndex:indexPath.row]];
Threads *viewController = [[Threads alloc] initWithNibName:#"newTable" bundle:nil tagval:tempText SessionID:PHPSESSID];
[self.navigationController pushViewController:viewController animated:YES];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[viewController release];
}
So, when I push the Child UITableView onto the screen, the UIActivityIndicatorView (spinner) just sits there on the Parent UITableView. If I go to the Child UITableView and then quickly go back to the Parent View, I can catch the UIActivitIndicatorView in the act of spinning. The NSLogs are showing at the correct times - when I first tap the cell in the Parent UITableView, the "Spin The Spinner" appears in the Log. 9 seconds later, I get "done spinning" in the Log, but the spinner never spins unless I pop back to the Parent UITableView in time.
Ideas of why this is not working properly?
Perhaps the sleepForTimeInterval is blocking the animation.
Try removing the sleepForTimeInterval and replace performSelectorOnMainThread with a call to performSelector:withObject:afterDelay:.
let the ui display it outside thread:
[NSObject dispatchUI:^{
[spinner startAnimating];
}];