I have a method to retrieve data from an external url, load it into an array from JSON format, and populate a UITableView. It works fine, but there is no indication to the user that something is happening while the data is downloaded.
- (void)viewDidLoad
{
[super viewDidLoad];
[self retrieveDataC];
}
Here is the code that I tried for viewDidLoad which adds a spinner animation while downloading. I'm attempting to put retrieveDataC on a background thread and when it completes, I would like the view to continue executing as though I didn't implement the multi-threading in the example above.
- (void)viewDidLoad
{
[super viewDidLoad];
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.center = CGPointMake(160, 240);
[self.view addSubview:spinner];
[spinner startAnimating];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self retrieveDataC];
dispatch_async(dispatch_get_main_queue(), ^{
[spinner stopAnimating];
});
});
}
The loading spinner displays correctly for a brief moment, however after the process is done I'm left with a blank table as though I have not called [self retrieveDataC] to begin with. Any suggestions, advice? Am I setting up the background process correctly?
Thank you
EDIT:
Here's what ended up working -
- (void)viewDidLoad
{
[super viewDidLoad];
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
spinner.center = CGPointMake(160, 240);
[self.view addSubview:spinner];
[spinner startAnimating];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self retrieveDataC];
dispatch_async(dispatch_get_main_queue(), ^{
[spinner stopAnimating];
[self.collectionView reloadData];
});
});
}
Do you call [self.tableView reloadData] after you received the data?
Related
In my code, I initialize a UIActivityIndicator, and add it as a subview, but it never appears. What's going wrong?
-(void) update {
UIActivityIndicatorView *activityView=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityView.center=self.view.center;
[self.view addSubview:activityView];
[activityView startAnimating];
/** Stuff to access to server, this stuff works correctly.*/
[activityView stopAnimating];
}
As you are starting and stoping the activity as same runloop so it will not get change to update the UI.You are doing all server stuff on same synchronously than you are blocking the runLoop and do not giving time to activity indicator to start.You should start server stuff on next runLoop so activity indicator will get time to start.So use performSelector with delay 0.0 it will start the activity indicator and than perform your server stuff on immediate next runloop and than stop the activityInddicator
-(void) update {
UIActivityIndicatorView *activityView=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityView.center=self.view.center;
[self.view addSubview:activityView];
[activityView startAnimating];
//Use performSelector with 0.0 so activity indicator starts
[self performSelector:#selector(thisWillStartAtNextRunLoop) withObject:nil afterDelay:0.0];
}
-(void)thisWillStartAtNextRunLoop{
/** Stuff to access to server, this stuff works correctly.*/
[activityView stopAnimating];
}
Edit execute it on main thread as all UI updation must be on main thread
Try
-(void) update {
dispatch_async(dispatch_get_main_queue(), ^{
UIActivityIndicatorView *activityView=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityView.center=self.view.center;
[self.view addSubview:activityView];
[activityView startAnimating];
});
/** Stuff to access to server, this stuff works correctly.*/
}
I am having some trouble getting my UIActivityIndicatorView to start animating. Here is my setup:
In my viewDidLoad in my view controller I have:
- (void)viewDidLoad{
schoolList = NO;
_activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[_activityIndicator startAnimating];
[NSThread detachNewThreadSelector: #selector(getSchoolList) toTarget: self withObject: nil];
[self performSelector:#selector(updateUI) withObject:nil afterDelay:20.0];
[super viewDidLoad];
}
The selector getSchoolList communicates with a server to retrieve a list of schools in a given state. Then, the selector updateUI is called to populate my UIPickerView with the list. In my updateUI selector I have:
-(void)updateUI {
_schools = [_server returnData];
if(!(_schools == nil)) {
NSLog(#"update the UI");
}
else
NSLog(#"Error:Show re-load button");
[_activityIndicator stopAnimating];
}
When I run this code, my UIActivityIndicatorView shows up, but does not animate. Can someone explain the proper way to animate my UIActivityIndicatorView? Any help is much appreciated.
You need to add the UIActivityIndicatorView to your view in viewDidLoad like this:
- (void)viewDidLoad {
schoolList = NO;
_activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[self addSubview:_activityIndicator];
[_activityIndicator startAnimating];
[NSThread detachNewThreadSelector: #selector(getSchoolList) toTarget: self withObject: nil];
[self performSelector:#selector(updateUI) withObject:nil afterDelay:20.0];
[super viewDidLoad];
}
EDIT
If _activityIndicator is a properly connected IBOutlet to a UIActivityIndicatorView, you should only need to check the 'animating' box. There would be no need to alloc/init another UIActivityIndicatorView.
Breakpoint the update function, but I don't see where you add that as a view to the hierarchy. I think you're looking at a different indicator view in the program.
I have an app with a table view controller in which a user selects a US state, a web service is called and data is displayed for that state in the destination table view controller. Since the web service can take some time to complete I want an activity indicator. Since there will be no temporary data to display, I need this to be processed synchronously. So my task is pretty simple: start the activity indicator, call the web service, and after it completes, stop the activity indicator.
I am obviously doing something wrong and no activity indicator ever displays.
Here is the code from my destination table view controller's viewDidAppear method:
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[self.tableView bringSubviewToFront:spinner];
spinner.hidesWhenStopped = YES;
spinner.hidden = NO;
[spinner startAnimating];
stateGauges = [[GaugeList alloc] initWithStateIdentifier:stateIdentifier andType:nil];
[self.tableView reloadData];
[spinner stopAnimating];
}
Header:
#property (strong, nonatomic) UIActivityIndicatorView *spinner;
GaugeList is the object which makes the web service call.
Can someone tell me how to get an activity indicator view to appear? Thanks!
You forgot to add spinner on table view. Your code should look as follows:
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spiner.center = //set some center
[self.tableView addSubview: spinner];
[self.tableView bringSubviewToFront:spinner];
spinner.hidesWhenStopped = YES;
spinner.hidden = NO;
[spinner startAnimating];
stateGauges = [[GaugeList alloc] initWithStateIdentifier:stateIdentifier andType:nil];
[self.tableView reloadData];
[spinner stopAnimating];
}
Also you send requests to a web service in main thread. This is bad practice. I would suggest something like following:
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spiner.center = //set some center
[self.tableView addSubview: spinner];
[self.tableView bringSubviewToFront:spinner];
spinner.hidesWhenStopped = YES;
spinner.hidden = NO;
[spinner startAnimating];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
stateGauges = [[GaugeList alloc] initWithStateIdentifier:stateIdentifier andType:nil];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
[spinner stopAnimating];
});
});
}
At first you should add activity indicator to some view to show it. But you can not add it to UITableView, because UITableView is subclass of UIScrollView and you will see floating activity indicator. The best way in your case is to add activity indicator to navigation bar, etc. Or if you want to disable table view you should write something like this:
- (void)viewDidLoad {
[super viewDidLoad];
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
UIView *dummyView = [[UIView alloc] init];
dummyView.frame = self.tableView.bounds;
dummyView.alpha = 0.5f;
dummyView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
dummyView.userInteractionEnabled = YES;
dummyView.backgroundColor = [UIColor blackColor];
[dummyView addSubview:activityIndicator];
activityIndicator.center = dummyView.center;
[self.tableView addSubview:dummyView];
}
Try using self.spinner instead of using spinner.
In a method that can take up to several seconds i have:
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc]initWithFrame:CGRectMake(135,140,50,50)];
spinner.color = [UIColor blueColor];
[spinner startAnimating];
[_mapViewController.view addSubview:spinner];
// lots of code
[spinner removeFromSuperview];
The spinner doesn't show up. Probably since the screen doesn't get update at that time.
How can i get around this problem?
Use GCD:
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc]initWithFrame:CGRectMake(135,140,50,50)];
spinner.color = [UIColor blueColor];
[spinner startAnimating];
[_mapViewController.view addSubview:spinner];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// lots of code run in the background
dispatch_async(dispatch_get_main_queue(), ^{
// stop and remove the spinner on the main thread when done
[spinner removeFromSuperview];
});
});
When moving from one view to another, I want in my second view as long as the image is being downloaded to see a spinner (so that the second view will open instantly not take any time at all, and the user waits for the spinner to finish).
Here is my code:
In the second View, the one that opens:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
UIActivityIndicatorView *spinner=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.center=CGPointMake(160.0,240.0 );
spinner.hidesWhenStopped=YES;
[self.view addSubview:spinner];
[spinner startAnimating];
NSString *urlString=[NSString stringWithFormat:#"http://mysite.com/projects/test/test.jpg"];
NSLog(#"URL is:%#",urlString);
NSURL *url=[NSURL URLWithString:urlString];
[more_info_image setImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:url]]];
[spinner stopAnimating];
}
I do not see the spinner at all. i do not think that this is because my internet connection is so fat. On the other hand, I see a little delay when a button is pressed and waiting for the view to open - like that it is that time when it tries to download the image.
I want to open the view, have the spinner, download the image and then let the spinner go away.
Must I change anything?
You might not be seeing the UIActivityIndicatorView since you are using [NSData dataWithContentsOfURL:url] on the main thread. This is blocking the main thread from displaying the UIActivityIndicatorView until after the image is downloaded, and by that point you are removing it.
You might want to do something like:
but make sure you define spinner in your *.h file for this to work.
- (void)viewDidLoad
{
...
//This code will make downloadImage run in the background thread
[self performSelectorInBackground:#(downloadImage) withObject:nil]
...
}
- (void) downloadImage{
NSLog(#"URL is:%#",urlString);
NSURL *url=[NSURL URLWithString:urlString];
UIImage *downloadedImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
//This code will make setImage run in the main thread since it is changing UI
[more_info_image performSelectorOnMainThread:#selector(setImage:) withObject:downloadedImage waitUntilDone:NO];
//This code will make stopAnimating run in the main thread since it is changing UI
[spinner performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:NO];
}
The other way is to use Grand Central Dispatch and do something like:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
UIActivityIndicatorView *spinner=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.center=CGPointMake(160.0,240.0 );
spinner.hidesWhenStopped=YES;
[self.view addSubview:spinner];
[spinner startAnimating];
//This is the new GCD code
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
//This code will run on a background thread
NSString *urlString=[NSString stringWithFormat:#"http://mysite.com/projects/test/test.jpg"];
NSLog(#"URL is:%#",urlString);
NSURL *url=[NSURL URLWithString:urlString];
UIImage *downloadedImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
dispatch_sync(dispatch_get_main_queue(), ^{
//this code runs on the main thread since it is UI changes
[more_info_image setImage:downloadedImage];
[spinner stopAnimating];
});
});
}
As you are downloading your image in the viewDidLoad method so while the image is downloading it did not loads your view so that's why you are experiencing a delay try this
in your header file add an instance of UIActivityIndicator
UIActivityIndicator *spinner;
in your implementation file do something like this
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
spinner=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.center=CGPointMake(160.0,240.0 );
spinner.hidesWhenStopped=YES;
[self.view addSubview:spinner];
[spinner startAnimating];
[self performSelector:#selector(downloadYourImageMethod) withObject:nil afterDelay:1];
}
-(void)downloadYourImageMethod
{
NSString *urlString=[NSString stringWithFormat:#"http://mysite.com/projects/test/test.jpg"];
NSURL *url=[NSURL URLWithString:urlString];
[myIMageview setImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:url]]];
[spinner stopAnimating];
}
modify your code as
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
UIActivityIndicatorView *spinner=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.center=CGPointMake(160.0,240.0 );
spinner.hidesWhenStopped=YES;
[self.view addSubview:spinner];
[spinner startAnimating];
[self performSelector:#selector(downloadImage) withObject:nil afterDelay:0.1];
}
-(void)downloadImage
{
NSString *urlString=[NSString stringWithFormat:#"http://mysite.com/projects/test/test.jpg"];
NSLog(#"URL is:%#",urlString);
NSURL *url=[NSURL URLWithString:urlString];
[more_info_image setImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:url]]];
[spinner stopAnimating];
}
Sometimes, it all depends on how you are placing your views. The spinner should the very last thing that's added to the hierarchy in order to be the first in the order when your view loads. This was the issue in my case, I hope this help anyone else that bumps into this answer. :)