iOS : using activity indicator in loading for second view controller - ios

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.

Related

UI is not updating on background thread

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];
});
});
}

UIAcitivity Indicator with UIAlertView

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.

UIActivityIndicatorView not loading as expect

I am trying to load a UIActivityIndicatorView however have some confusion about then it should load.
Should it start on line [activityIndicatorView startAnimating]; or when it gets to the end of the function.
- (void)LoadBuayView{
activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
activityIndicatorView.center = self.view.center;
activityIndicatorView.backgroundColor = [UIColor grayColor];
[activityIndicatorView hidesWhenStopped];
[self.view addSubview:activityIndicatorView];
[activityIndicatorView startAnimating];
}
- (IBAction) EditSave:(id)sender {
[self LoadBuayView];
for(int i =0; i<5; i++)
{
//Some very long takes time code
}
}
the activityIndicatorView will start animation in the next runloop of mainThread, so you can think it get to start at end of the function.
You can try following code to show the indicator view:
- (IBAction) EditSave:(id)sender {
[self LoadBuayView];
[self performSelector:#selector(doLongTimeTask) withObject:nil afterDelay:0.01];
}
- (void)doLongTimeTask{
for(int i =0; i<5; i++)
{
//Some very long takes time code
}
}
Any time consuming processes should not take place in the main queue, but rather you should employ asynchronous programming patterns as discussed in the Concurrency Programming Guide. Bottom line, you should never block the main queue. At best, blocking the main queue can result in a suboptimal UX, and at worst, your app can be killed by the iOS watch dog process.
Instead, dispatch time consuming code to a background queue (either a dispatch queue or an operation queue). You can either create your own custom background queue, or with GCD you can avail yourself of one of the existing background queues:
- (void)loadBuyView{
activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
activityIndicatorView.center = self.view.center;
activityIndicatorView.backgroundColor = [UIColor grayColor];
[activityIndicatorView hidesWhenStopped];
[self.view addSubview:activityIndicatorView];
[activityIndicatorView startAnimating];
}
- (IBAction) didTouchUpInsideSaveButton:(id)sender {
[self loadBuyView];
// always do slow processes on a background queue, not the main queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 5; i++)
{
// Some code that takes a very long time
}
// when done, dispatch the stopping of the activity indicator view back to the main queue;
// all UI updates should be performed on the main queue
dispatch_async(dispatch_get_main_queue(), ^{
[activityIndicatorView stopAnimating];
});
});
}
For more information about asynchronous programming patterns like the above, see WWDC 2012 video Asynchronous Design Patterns with Blocks, GCD, and XPC. For background in the various concurrent programming technologies, see the Concurrency Programming Guide.

Async UIView content not showing up when coming back to main thread

When loading my UIViewController, I basically put a spinner in the middle of the page until the content loads, then come back on the main thread to add the subviews :
- (void)viewDidLoad {
[super viewDidLoad];
UIActivityIndicatorView *aiv_loading = ... // etc
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Load content
NSString *s_checkout = [[BRNetwork sharedNetwork] getCheckoutInstructionsForLocation:self.locBooking.location];
UIView *v_invoice_content = [[BRNetwork sharedNetwork] invoiceViewForBooking:locBooking.objectId];
// Display the content
dispatch_sync(dispatch_get_main_queue(), ^{
if (s_checkout && v_invoice_content) {
[aiv_loading removeFromSuperview];
[self showContentWithText:s_checkout AndInvoice:v_invoice_content];
} else {
NSLog(#"No data received!"); // is thankfully not called
}
});
});
}
- (void) showContentWithText:(NSString *)s_checkout AndInvoice:(UIView *)v_invoice {
[self.view addSubview:[self checkoutTextWithText:s_checkout]]; // Most of the time displayed text
[self.view addSubview:[self completeCheckout]]; // always Displayed UIButton
[self.view addSubview:[self divider]]; // always displayed UIImageView
// Summary title
UILabel *l_summary = [[UILabel alloc] initWithFrame:CGRectMake(0, [self divider].frame.origin.y + 6 + 10, self.view.bounds.size.width, 20)];
l_summary.text = NSLocalizedString(#"Summary", nil);
[self.view addSubview:l_summary];
CGRect totalRect = CGRectMake([self divider].frame.origin.x, [self divider].frame.origin.y + 6 + 30, self.view.bounds.size.width - [self divider].frame.origin.x, 90);
_v_invoice = v_invoice;
_v_invoice.frame = totalRect;
[self.view addSubview:[self v_invoiceWithData:v_invoice]]; // THIS Pretty much never displayed
UITextView *l_invoice = [[UITextView alloc] initWithFrame:CGRectMake(0, _v_invoice.frame.origin.y + _v_invoice.frame.size.height + offset, 320.0, 50)];
l_invoice.text = NSLocalizedString(#"summary_emailed", nil);
[self.view addSubview:l_invoice]; // Always displayed
}
However, not all the content is displayed. The invoice is never there at first, but gets displayed after a couple of minutes. The other async-created string, s_content is sometimes not displayed.
This seems to be random with the content creation. The end result is pretty neat, but not reliable for a production version.
I used the undocumented [self.view recursiveDescription] to check if everything was there, and even if I don't see it, they are all there with what seems to be correct frames.
Any pointers?
- layoutSubviews did not help!
- putting a background color to the invoice view is showing the background color
I suspect this line is your problem:
UIView *v_invoice_content = [[BRNetwork sharedNetwork] invoiceViewForBooking:locBooking.objectId];
As you are calling this in a background dispatch queue. Any work involving UIKit should be done on the main queue/thread. Either move that into the main thread block, or if building the view is dependent on data from a network call, change your invoiceViewForBooking method to return the data first, and build your view in the main thread with that data.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Load content
NSString *s_checkout = [[BRNetwork sharedNetwork] getCheckoutInstructionsForLocation:self.locBooking.location];
id someData = [[BRNetwork sharedNetwork] invoiceDataForBooking:locBooking.objectId];
// Display the content
dispatch_async(dispatch_get_main_queue(), ^{
UIView *v_invoice_content = [invoiceViewWithData:someData];
});
});
I'd also suggest using dispatch_async instead of dispatch_sync on the main queue.
Solved! I ended up removing the dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
To only leave the async block on the main queue:
// Display the content from a new async block on the main queue.
dispatch_async(dispatch_get_main_queue(), ^{
[aiv_loading removeFromSuperview];
NSString *s_checkout = [[BRNetwork sharedNetwork] getCheckoutInstructionsForLocation:self.locBooking.location];
[self showContentWithText:s_checkout AndInvoice:[[BRNetwork sharedNetwork] invoiceViewForBooking:locBooking.objectId]];
});
which did the trick, viewcontroller's view can appear without waiting for the view to load the remote data!
You should put your code in viewDidAppear instead of viewDidLoad. Whatever is related to display (even launching async blocks) should always be in viewWillAppear or viewDidAppear.
Also, I recommend you to use dispatch_async(dispatch_get_main_queue(), ^{}) instead of your dispatch_sync since you still want your block to be performed async (but on main thread).
Dont forget to call super methods in your viewDidAppear.

Activity Indicator is not showed when loading large amount of data from sqlite database

In my app, i'm getting the data from database. The data that I get from database is very large like 20000 records. So, I want to show an activity indicator to indicate that data is loading. But activity indicator is not showed when I run the app.
I'm sending code for this. Please help me out.
activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
activityIndicator.frame = CGRectMake(100,100, 20, 20);
[self.view addSubview:activityIndicator];
[activityIndicator startAnimating];
// Code to get data from database
[activityIndicator stopAnimating];
First, you don't need to set the frame for the activiyIndicator. You can just set the centre...
activityIndicator.center = self.view.center; (or soemthing like this).
Second, how are you retrieving the data? Are you doing it all on the main thread? Or are you doing it asynchronously?
::EDIT::
Ah it appears you are doing this...
[actInd startAnimating];
[self getData]; //this is asynchronous
[actInd stopAnimating];
if this is the case then the stopAnimating will get called almost immediately.
What you need is something like this...
[actInd startAnimating];
[self getData]; //this is asynchronous
Then in getData...
- (void)getData
{
//get all your data and process it....
[actInd stopAnimating];
}
This way the stop animating will only get called after doing all the data work.
If that doesn't work try removing the stopAnimating altogether to check it is actually animating in the first place.
#Fogmeister is wright about setting the frame of activityIndicator.
and for showing the activityIndicator just create one function of the code shown in your question and call it using the performSelector:withObject:afterDelay: with delay of 0.001 and the activityIndicator will shown
Happy Coding :)
Do this:
activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
activityIndicator.center = self.view.center;
[self.view addSubview:activityIndicator];
[NSThread detachNewThreadSelector:#selector(startActivityIndicator) toTarget:self withObject:nil];
Add this method:
-(void)startActivityIndicator
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
[activityIndicator startAnimating];
[pool release];
}
Do changes:
- (void)getData
{
//get all your data from database.
[activityIndicator stopAnimating];
}

Resources