i was undergoing stanford cs 193 p assignment5.The problem is that when i segue a data to destination view controller and use dispatch queue to fetch the data in viewDidLoad method.The dispatch_async does'nt execute in the destination view controller .Here is my code for View controller A and destination view controller.
view controller A code
-(void)viewDidLoad
{
[super viewDidLoad];
if(!self.places){
self.spinner.hidesWhenStopped = YES;
self.spinner.center = self.tableView.center;//
[self.view addSubview:self.spinner];
[self.spinner startAnimating];
dispatch_queue_t dispatchQueue = dispatch_queue_create("queue_top_places", NULL);
dispatch_async(dispatchQueue, ^{
self.places = [self getRecentPlacesFromFlicker];
// main queue to load table view data
dispatch_async(dispatch_get_main_queue(), ^{
// load table data
if(self.tableView.window){
[self.tableView reloadData];
[self.spinner stopAnimating];
}
});
});
dispatch_release(dispatchQueue);
}
// Uncomment the following line to preserve selection between presentations.
self.clearsSelectionOnViewWillAppear = YES;
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:#"Show Recent Photo List"]){
int currentRow = self.tableView.indexPathForSelectedRow.row;
// set up photo list controller model
[segue.destinationViewController setPhotosList:[self.places objectAtIndex:currentRow]];
}
}
Here is my code for destination view controller
- (void)viewDidLoad
{
[super viewDidLoad];
// get the current top place name and fetch photos at that place from flicker
if ([self.photosList isKindOfClass:[NSDictionary class]])
{
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
spinner.hidesWhenStopped = YES;
spinner.center = self.tableView.center;//
[self.view addSubview:spinner];
[spinner startAnimating];
dispatch_queue_t dispatchQueue1 = dispatch_queue_create("queue_top_50_photos", NULL);
dispatch_async(dispatchQueue1, ^{
self.photosList = [FlickrFetcher photosInPlace:self.photosList maxResults:50];
dispatch_async(dispatch_get_main_queue(), ^{
if (self.tableView.window ){
[self.tableView reloadData];
[spinner stopAnimating];
}
});
});
dispatch_release(dispatchQueue1);
}
// Uncomment the following line to preserve selection between presentations.
self.clearsSelectionOnViewWillAppear = YES;
self.title = #"50PhotoList";
}
The condition ([self.photosList isKindOfClass:[NSDictionary class]]) is satisfied checked with a debugger
I was just thinking about this, have you tried running this code in viewDidAppear instead of viewDidLoad? I'd give that a try.
Any reason why you create a new queue for each operation? What if you remove the dispatch_release instruction at the end? Maybe your async operation takes longer than the time it gets for your queue to get released.
Try to use a global queue instead of one that you create yourself, one you don't have to release when you're done.
Replace
dispatch_queue_t dispatchQueue1 = dispatch_queue_create("queue_top_50_photos", NULL);
with
dispatch_queue_t dispatchQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
and remove the dispatch_release(dispatchQueue1);
dispatch_async returns immediately and performs the task in background and executes rest of the code in the viewDidLoad or other viewDidAppear methods.
One solution would be to use dispatch_sync which waits for the task to execute and to nest it in dispatch_async like this
dispatch_queue_t dispatchQueue1 = dispatch_queue_create("queue_top_50_photos", NULL);
dispatch_async(dispatchQueue1, ^{
dispatch_queue_t dispatchSyncQueue = dispatch_queue_create("queue_top_50_photos", NULL);
dispatch_sync( dispatchSyncQueue,^){
self.photosList = [FlickrFetcher photosInPlace:self.photosList maxResults:50];
});
dispatch_sync(dispatch_get_main_queue(), ^{
if (self.tableView.window ){
[self.tableView reloadData];
[spinner stopAnimating];
}
});
dispatch_release(dispatchSyncQueue);
});
dispatch_release(dispatchQueue1);
Related
I have a UIViewController loading data from web, the process takes about 15 secs, So I put my long running process on 2nd thread, when the 2nd thread is completed, i will set the button enable or disable. but the button is not refreshing when the process is done.
#import "ViewController.h"
#import "FunctionNSObject.h"
#interface ViewController ()
#end
#implementation ViewController
NSMutableArray *schoolsAvaliable;
NSDictionary *dict;
NSString *schoolNameCh, *schoolNameEn;
int schoolYear, schoolID;
- (void)viewDidLoad
{
[super viewDidLoad];
}
-(void)viewWillAppear:(BOOL)animated
{
//initial set the button disable
self.button.enabled = NO;
//2nd thread
dispatch_queue_t downloadQueue = dispatch_queue_create("loadSchool", NULL);
dispatch_async(downloadQueue, ^{
//get avalible school info from JSON
schoolsAvaliable = [FunctionNSObject loadDataFromWeb:#"http://some web service"];
//get school year
schoolYear = [FunctionNSObject getSchoolYear];
if (schoolsAvaliable.count != 0)
{
//select the first row from array
dict = schoolsAvaliable[0];
//get the value from dictionary of that row
schoolID = (int)[[dict objectForKey:#"SchoolId"] integerValue];
schoolNameCh = [dict objectForKey:#"SchoolName"];
schoolNameEn = [dict objectForKey:#"SchoolNameEn"];
self.button.enabled = YES;
[self.button setNeedsDisplay];
}
else
{
self.button.enabled = NO;
[self.button setNeedsDisplay];
}
//2nd thread end then
dispatch_async(dispatch_get_main_queue(), ^{
//[self.pickerSchool reloadAllComponents];
self.labelSchool.text = [NSString stringWithFormat:#"%d - %d",schoolYear,schoolYear+1];
NSLog(#"%d",self.button.enabled);
});
});
}
#end
You call related to UI methods on non-main thread. Usually it results to unpredictable behaviour.
Try to call methods related to UI on main thread like this:
dispatch_async(dispatch_get_main_queue(), ^{
self.button.enabled = YES;
});
As #David noticed you needn't call [set.button setNeedsDisplay] because call of setEnabled: method results to call of this method.
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?
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.
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. :)