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.
Related
I am trying to set a 'UIActivityIndicatorView' as an accessoryView in one of my 'UITableViewCell', in a such way as, the ActivityIndicator start animating when the user touch this cell. I add the following code at 'didSelectRowAtIndexPath' method:
UITableViewCell * cell = [tableView cellForRowAtIndexPath:indexPath];
UIActivityIndicatorView * activity = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
NSThread * newThread1 = [[NSThread alloc]initWithTarget:self selector:#selector(carregarNoticias) object:nil];
switch (indexPath.row) {
case 0:
cell.accessoryView = activity;
[activity startAnimating];
sleep(0.01);
[newThread1 start];
while (![newThread1 isFinished]) {
//waiting for thread
}
[activity stopAnimating];
[self.navigationController pushViewController:noticias animated:YES];
break;
I was expecting 'UIActivityIndicatorView' animating while the newthread1 is running. However, the animating only start when the newThread1 finish (and therefore I don't want the animation anymore). I added 0.01 seconds as sleep time to give time to the animation start. Nevertheless, this also did't solve.
Does anyone know what my error is? I appreciate any help!
I dont really know what you are trying to do adding delay like that.. try this line below, might be the one your look for.. and another thing.. please dont block the main thread like sleep(0.01); that, users wont like it..
cell.accessoryView = activity;
[activity startAnimating];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
// the system will wait until this
dispatch_async(dispatch_get_main_queue(), ^(void){
[newThread1 start];
});
// is finished, maybe you dont need the `[NSThread sleepForTimeInterval:3];`
// pick the one that suits your implementation..
[NSThread sleepForTimeInterval:3];
dispatch_async(dispatch_get_main_queue(), ^(void){
[activity stopAnimating];
[self.navigationController pushViewController:noticias animated:YES];
});
});
[NSThread sleepForTimeInterval:3] sleep the thread like the one you have sleep(0.01);
But using it inside
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){ });
makes it async. and wont block the main thread..
while the dispatch_async(dispatch_get_main_queue(), ^(void){ fires your code at the main thread..
Happy coding, cheers! :)
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.
So self.pic is the UIButton that I want to hide. But it never actually hides it for some reason.
Please Help
- (void)tick:(NSTimer*)time
{
self.pic.hidden = YES;
[NSThread sleepForTimeInterval:1];
self.pic.hidden = NO;
[self.pic setEnabled:NO];
if (self.checkPic == YES)
{
self.lives--;
self.livesLabel.text = [NSString stringWithFormat:#"Lives : %d", self.lives];
}
[self.pic setImage:[self backgroundImageForGame] forState:UIControlStateNormal];
[self check];
self.pic.enabled = YES;
}
Just to elaborate on what #rmaddy said in his comment, you shouldn't ever sleep the main (or UI) thread. When you do this, you're blocking the thread that is responsible for making the visual changes to the button, like whether or not it is hidden. The solution is to do the waiting asynchronously, and GCD has a built in function that makes this painless.
dispatch_after() allows you to create a block that will be executed after a specified delay, on a queue of your choosing. Since you want to update the UI, you'll want to come back to the main queue to make the changes to the on screen button. Here's an example:
self.pic.hidden = YES;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.pic.hidden = NO;
});
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.
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];
}