I am developing an app where the user will get to confirm some action via UIAlertView, if he confirms, I call a method that handles the operation, then I prepare to pop the view I am in to go back to another view after the method has been called.
I want to show UIActivityIndicatorView if the user presses confirm for as long as it takes to execute the method and go to that other view. I used startAnimating and stopAnimating in the proper location, but i never get to see the UI UIActivityIndicatorView shown, not for a sec.
I guess its related to some UI issues due to UIAlertView, not sure if I am correct though. I just need a clue on how to use UIActivityIndicatorView properly for a method execution time.
My code:
- (void)viewDidLoad
{
[super viewDidLoad];
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
self.activityIndicator.alpha = 1.0;
self.activityIndicator.hidesWhenStopped = YES;
self.activityIndicator.center = self.view.center;
[self.view addSubview:self.activityIndicator];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if(buttonIndex == 1) {
[self.activityIndicator startAnimating];
ContactsTableViewController *contactTableView = [self getContactsTVC];
[contactTableView applyActionOnCells];
// doing some setup before poping off to the root view controller of my nav controller
[self.activityIndicator stopAnimating];
// then go to rootViewController
[self.navigationController popToRootViewControllerAnimated:YES];
}
}
I'm not 100% certain, but try to comment out the stopAnimating call and see if it shows up.
If that helps, applyActionOnCells probably blocks your main thread (where all UI stuff also happens) and the indicator never has a chance to show up before you hide it again.
In that case, try do the applyActionOnCells call in the background:
if(buttonIndex == 1) {
[self.activityIndicator startAnimating];
__block typeof(self) bself = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
ContactsTableViewController *contactTableView = [bself getContactsTVC];
[contactTableView applyActionOnCells];
dispatch_async(dispatch_get_main_queue(), ^{
[bself.activityIndicator stopAnimating];
// then go to rootViewController
[bself.navigationController popToRootViewControllerAnimated:YES];
});
});
}
Edit: see also an earlier question.
Related
So i am showing a model controller on top of a view controller. And i have texts in the model controller, but somehow the texts are not visible. I tried everything but somehow labels are not visible. But of you stay on the page for like 30 -40 sec the text shows up. Also this model controller is called from main view controller after a successful service(REST) call. If i call the model without making the service call then labels are visible in simulator/iPad both. But if i call it after service call inside success block then labels are not visible. I tried adding the text programmatically but still same issue. I tried debugging using Color blended layers, but the label is not at all visible in the view somehow. :(
[self.serviceManager getCustDetails:account successBlock:^(NSDictionary * successDict) {
[self hideLoadingAnimation];
NSDictionary *custData = [[successDict objectForKey:#"txnData"] objectForKey:#"custData"];
self.showCurrYear = [iraContribData objectForKey:#"showCurrYear"];
if ([self.showCurrYear isEqual: #"true"]){
[self performSegueWithIdentifier:#"CSegue" sender:self];
}
} failureBlock:^(NSDictionary * failureDict) {
[self hideLoadingAnimation];
NSLog(#"Failiure Dict %#",failureDict);
}];
And this prepareForSegue method, -
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"CSegue"]) {
CustViewController *cVC = segue.destinationViewController;
cVC.delegate = self;
[cVC setModalPresentationStyle:UIModalPresentationFormSheet];
cVC.preferredContentSize = CGSizeMake(800,750);
}
}
Below is my screen in storyboard, but in simulator the label is not visible, only continue and close button is visible.
Please help!, any suggestions are most welcome. Thanks!
It is possible that the delay is due to a user interface update not made on the main thread.
Try to make sure that your code is executed on the main thread using dispatch_async like this :
[self.serviceManager getCustDetails:account successBlock:^(NSDictionary * successDict) {
dispatch_async(dispatch_get_main_queue(), ^{
[self hideLoadingAnimation];
NSDictionary *custData = [[successDict objectForKey:#"txnData"] objectForKey:#"custData"];
self.showCurrYear = [iraContribData objectForKey:#"showCurrYear"];
if ([self.showCurrYear isEqualToString:#"true"]){
[self performSegueWithIdentifier:#"CSegue" sender:self];
}
});
} failureBlock:^(NSDictionary * failureDict) {
dispatch_async(dispatch_get_main_queue(), ^{
[self hideLoadingAnimation];
NSLog(#"Failiure Dict %#",failureDict);
});
}];
I have two view controllers; in the second view, a bunch of data are processed which takes pretty much time, while in the first view, there is a button navigating to the second. I want to display an activity indicator for the process in the second view right after the button clicked. But initialising UIActivityIndicatorView in the second view doesn't seem to work. Nothing showed up when the button was clicked, and the app was stuck in the first view when data being processed.
Below are the code I wrote in viewDidLoad in the second view controller.
_activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[_activityIndicator setCenter:CGPointMake(SCREEN_WIDTH/2, SCREEN_HEIGHT/2)];
[self.view addSubview:_activityIndicator];
...............
[_activityIndicator startAnimating];
...............
// data processing
[_activityIndicator stopAnimating];
Anyone know how to solve this?
========EDIT=========
Thank you so much for the advices. Now I've tried using NSThread,but the spinner showed up pretty late. Here are the code I wrote in the first view controller.
- (void)viewDidLoad
{
[super viewDidLoad];
// activity indicator
_activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[_activityIndicator setCenter:CGPointMake(SCREEN_WIDTH/4, SCREEN_HEIGHT/4)];
[self.view addSubview:_activityIndicator];
}
- (IBAction)startButtonClicked:(id)sender
{
[NSThread detachNewThreadSelector:#selector(threadStartAnimating:) toTarget:self withObject:nil];
}
-(void)threadStartAnimating:(id)data
{
NSLog(#"start");
[_activityIndicator startAnimating];
[self performSelectorOnMainThread:#selector(threadStopAnimating:) withObject:nil waitUntilDone:YES];
}
-(void)threadStopAnimating:(id)data
{
NSLog(#"stop");
[_activityIndicator stopAnimating];
}
The spinner appeared around 2 sec after NSLog(#"start"); being executed and showed up in a very short period. I linked - (IBAction)startButtonClicked:(id)sender with the button that navigated to the second view.
Is there any better way to put [_activityIndicator startAnimating];?
Maybe you can try to solve this issue with Grand Central Dispatch (threads).
In you second VC, try this code:
- (void) viewDidLoad{
[super viewDidLoad];
// Main thread
_activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[_activityIndicator setCenter:CGPointMake(SCREEN_WIDTH/4, SCREEN_HEIGHT/4)];
[self.view addSubview:_activityIndicator];
[_activityIndicator startAnimating];
// create a queue
dispatch_queue_t queue = dispatch_queue_create("data_process", 0);
// send a block to the queue - Not in Main thread
dispatch_async(queue, ^{
// data processing
.................
// Interaction with User Interface - Main thread
dispatch_async(dispatch_get_main_queue(), ^{
[_activityIndicator stopAnimating];
_activityIndicator.hidden = YES;
});
});
}
I hope this helps.
I am running an update on an Sqlite3 database in the background when the user presses a force update button.
I want to disable the button as to not lock the database and keep the user from pressing it over and over again. Plus I want to show an Activity Indicator. However, the button is not disabling and the activity indicator does not show.
What am I doing wrong?
I hide the activity indicator when the view is loaded.
Built with storyboards:
View did load
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//other going on
[self updateUIInterface:false];
}
The method to update the UI
- (void) updateUIInterface : (BOOL) updating {
if (updating) {
//Disable buttons and show activity indicator
self.actLocalDB.hidden = NO;
[self.actLocalDB startAnimating];
self.btnSyncLocal.enabled = NO;
[self.btnSyncLocal setTitle:#"Updating.." forState:UIControlStateDisabled];
[self.btnSyncLocal setUserInteractionEnabled:NO];
} else {
// Enable buttons
self.actLocalDB.hidden = YES;
[self.actLocalDB stopAnimating];
self.btnSyncLocal.enabled = YES;
[self.btnSyncLocal setTitle:#"Sync Databases" forState:UIControlStateDisabled];
[self.btnSyncLocal setUserInteractionEnabled:YES];
}
}
My method to update the DB
- (IBAction)syncLocalDB:(id)sender {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"Begin Local DB Sync");
[self updateUIInterface:true];
//db stuff goes here
dispatch_async(dispatch_get_main_queue(), ^{
//update UI here
NSLog(#"Done updating local db");
[self updateUIInterface:false];
});
});
}
You can't make UI changes in background threads. All UI operations need to be performed on the main thread. Here is a nice blog post on the topic and a link to the docs.
Just call updateUIInterface Method before entering the GCD-Block.
- (IBAction)syncLocalDB:(id)sender {
[self updateUIInterface:true];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"Begin Local DB Sync");
//db stuff goes here
dispatch_async(dispatch_get_main_queue(), ^{
//update UI here
NSLog(#"Done updating local db");
[self updateUIInterface:false];
});
});
}
I have a uiView covering the screen with a label and uiindicator that runs when i am doing a network call.
Now here's the problem. I have initialized like this in viewdidload
[self.UiIndicator_view setHidden:YES];
[self.UiIndicator_label setHidden:YES];
[self.UiIndicator_indicator setHidden:YES];
On the network call i have called
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
[self ShowIndicator:#"Syncing Data"];
if([self CheckNetwork])
[HttpMethods GetHeaderDataForAppHttp];
dispatch_async(dispatch_get_main_queue(), ^
{
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:#"Network Error"
message:#"You have no network connection. Please connect to a network to sync data."
delegate:self
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alertView show];
[self StopIndicator];
});
});
and the methods are as follows
-(void)ShowIndicator:(NSString*)labelText
{
[self.UiIndicator_view setHidden:NO];
[self.UiIndicator_label setHidden:NO];
[self.UiIndicator_label setText:labelText];
[self.UiIndicator_indicator setHidden:NO];
[self.UiIndicator_indicator startAnimating];
}
-(void)StopIndicator
{
[self.UiIndicator_view setHidden:YES];
[self.UiIndicator_label setHidden:YES];
[self.UiIndicator_indicator setHidden:YES];
[self.UiIndicator_indicator stopAnimating];
}
Now the problem. The code runs perfectly fine when i start the app for first time. The call goes to dispatch_asyn, i see a UIView with label and indicator and after the call returns to main thread, the uiview and indicator disappears and i get the alertview if no data was fetched.
I have given the user an option to manually call the same function if data could not be fetched at first attempt. Here the issue arises, i can see in NSLOG that the method has been called, http request is fired, but i cannot see the uiview with indicator, even the elements and buttons that are supposed to be behind the uiview are not clickable (this means that there is a UIVIEW on top, but i cant see it). after some time i get the uialeart which means the call has completed.
Does setting uiview hidden = yes nullify it or something like that? i am not able to get this simple issue.
You are calling [self ShowIndicator:#"Syncing Data"]; on a background queue.
Only ever update the UI on the Main Queue.
More specifically:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
[self ShowIndicator:#"Syncing Data"]; <<<< THIS IS YOUR PROBLEM
I want to make all reading/writing database operations to background queue and update the current UI view when completed.
There is no problem if user stays in the view while I'm dealing with my database. However, if user left that view before database operations completed, it would crash. The psuedo code is as below:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
/* save data to database, needs some time */
dispatch_async(dispatch_get_main_queue(), ^{
// back to main queue, update UI if possible
// here may cause crash
[self.indicator stopAnimating];
[self.imageView ...];
});
});
Try checking if the view is still in the view hierarchy, and also stop the activity indicator from spinning in the viewDidDisappear method as well. You also might need a flag (isNeedingUpdate in the example below) to indicate whether the UI was updated or not, so you can do the appropriate actions if the user goes away before the update is complete and then comes back again.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
if (self.view.window) { // will be nil if the view is not in the window hierarchy
dispatch_async(dispatch_get_main_queue(), ^{
[self.indicator stopAnimating];
[self.imageView ...];
self.isNeedingUpdate = NO;
});
}else{
self.isNeedingUpdate = YES;
});
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (isNeedingUpdate) {
// do whatever you need here to update the view if the use had gone away before the update was complete.
}
}
-(void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.indicator stopAnimating];
}