I am using GCD to download the header image for the UITableView.
When I use dispatch_async, the image does not show up at all, and when I use dispatch_sync, it still a synchronous download. How do I fix this ?
eventDetailsTable = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height) style:UITableViewStyleGrouped];
eventDetailsTable.dataSource = self;
eventDetailsTable.delegate = self;
[self.view addSubview:eventDetailsTable];
NSString *headerImageUrl = [NSString stringWithFormat:#"%#%#", [currentEvent objectForKey:#"baseurl"], [currentEvent objectForKey:#"sessionimage"]];
NSURL *headerImageURL = [NSURL URLWithString:headerImageUrl];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSData *imageData = [[NSData alloc] initWithContentsOfURL:headerImageURL];
UIImage *headerImage = [UIImage imageWithData:imageData];
UIImageView *headerImageView = [[UIImageView alloc] initWithImage:headerImage];
eventDetailsTable.tableHeaderView = headerImageView;
});
When you update UI, you must do it on the main thread. So here is solution:
dispatch_async(global_queue, ^{
//Do your work
dispatch_async(dispatch_get_main_queue(), ^{
//Update UI
});
});
Related
I have two arrays, one with usernames and the other with played games, I want to match the profile picture that is in the username array to the yourName in played games however I cannot seem to get it to work.
for(id object in self.playedGames){
PFObject *obb = object;
for(int l = 0; l <self.Userarray1.count;l++){
if([[obb objectForKey:#"yourName"]isEqual:[[self.Userarray1 valueForKey:#"username"]objectAtIndex:l]] ){
PFFile *imageFile = [[self.Userarray1 valueForKey:#"profilePic"]objectAtIndex:l];
if(imageFile !=nil){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
NSURL* imageFileUrl = [[NSURL alloc] initWithString:imageFile.url];
NSData *imageData = [NSData dataWithContentsOfURL:imageFileUrl];
UIImage *newImageset = [UIImage imageWithData:imageData];
dispatch_sync(dispatch_get_main_queue(), ^(void) {
UIImageView *imgVew = [[UIImageView alloc] initWithFrame:CGRectMake(4, 5, 50, 50)];
imgVew.image = newImageset;
imgVew.opaque = YES;
[ImageArray addObject:imgVew];
NSLog(#"imageArray count = %i",ImageArray.count);
if(ImageArray.count == self.playedGames.count){
[self getStuff];
}
});
});
}
else{
UIImage *NoPP = [UIImage imageNamed:#"friends_tab.png"];
NoPP = [self imageScaledToSize:CGSizeMake(50, 50)withImage:NoPP];
UIImageView *NoPPView = [[UIImageView alloc]initWithFrame:CGRectMake(4, 5, 50, 50)];
NoPPView.image = NoPP;
NoPPView.opaque = YES;
[ImageArray addObject:NoPPView];
}
}
}
}
Realized that i was doing asynchronous work and synchronous work so the ImageArray was being added at different times so I fixed it with this solution
if(imageFile !=nil){
UIImageView *imgVew = [[UIImageView alloc]initWithFrame:CGRectMake(4, 5, 50, 50)];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
NSURL* imageFileUrl = [[NSURL alloc] initWithString:imageFile.url];
NSData *imageData = [NSData dataWithContentsOfURL:imageFileUrl];
UIImage *newImageset = [UIImage imageWithData:imageData];
dispatch_sync(dispatch_get_main_queue(), ^(void) {
imgVew.image = newImageset;
imgVew.opaque = YES;
});
});
[ImageArray addObject:imgVew];
NSLog(#"imageArray count = %i",ImageArray.count);
if(ImageArray.count == self.playedGames.count){
[self getStuff];
}
}
it also made iterating through that function faster as well
I want to load some "image" (In remote server) in a UIScrollView with NSOperatoinQueue. Because If I load it with normal NSURL, NSData or with NSMutableURLRequest it takes too much time to load for all the images. After that I show those images in UIButton. Here is my code:
- (void)viewDidLoad
{
[super viewDidLoad];
[self startAnimation:nil];
self.imageDownloadingQueue = [[NSOperationQueue alloc] init];
self.imageDownloadingQueue.maxConcurrentOperationCount = 4; // many servers limit how many concurrent requests they'll accept from a device, so make sure to set this accordingly
self.imageCache = [[NSCache alloc] init];
[self performSelector:#selector(loadData) withObject:nil afterDelay:0.5];
}
-(void) loadData
{
adParser = [[AdParser alloc] loadXMLByURL:getXMLURL];
adsListArray = [adParser ads];
displayArray = [[NSMutableArray alloc] init];
for (AdInfo *adInfo1 in adsListArray)
{
AdInfo *adInfo2 = [[AdInfo alloc] init];
[adInfo2 setBannerIconURL:adInfo1.bannerIconURL];
[adInfo2 setBannerIconLink:adInfo1.bannerIconLink];
[displayArray addObject:adInfo2];
}
[self loadScrollView];
[activityIndicator stopAnimating];
}
-(void) loadScrollView
{
[self.scrollView setScrollEnabled:YES];
[self.scrollView setContentSize:CGSizeMake([displayArray count] * ScrollerWidth, ScrollerHight)];
for (int i = 0; i < [displayArray count]; i++)
{
adButtonOutLet = [[UIButton alloc] initWithFrame:CGRectMake(i*320, 0, ButtonWidth, ButtonHight)];
currentAd = [displayArray objectAtIndex:i];
NSString *imageUrlString = [currentAd bannerIconURL];
UIImage *cachedImage = [self.imageCache objectForKey:imageUrlString];
if (cachedImage)
{
[adButtonOutLet setImage:cachedImage forState:UIControlStateNormal];
}
else
{
[self.imageDownloadingQueue addOperationWithBlock:^
{
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrlString]];
UIImage *image = nil;
image = [UIImage imageWithData:imageData];
// add the image to your cache
[self.imageCache setObject:image forKey:imageUrlString];
// finally, update the user interface in the main queue
[[NSOperationQueue mainQueue] addOperationWithBlock:^
{
[adButtonOutLet setImage:image forState:UIControlStateNormal];
}];
}];
}
adButtonOutLet.userInteractionEnabled= YES;
[adButtonOutLet setTag:i];
[adButtonOutLet addTarget:self action:#selector(goToURL:) forControlEvents:UIControlEventTouchUpInside];
[self.scrollView addSubview:adButtonOutLet];
}
}
Can anyone tell me what's wrong with the above code? There is no problem of parsing or retrieving data from Remote server. I check it by NSLog. I think the NSOperationQueue have some problem, which I can't manage properly. Thanks in advance. If you needed more information, I will attach here.
Have a nice day.
Not sure if this is your problem or your solution, its hard to tell without testing myself.
Taken from RayWenderlich
addOperationWithBlock: if you have a simple operation that does not
need to be subclassed, you can create an operation using the block
API. If you want to reference any object from outside in the block,
remember that you should pass in a weak reference. Also, if you want
to do something that is related to the UI in the block, you must do it
on the main thread:
// Create a weak reference
__weak MyViewController *weakSelf = self;
// Add an operation as a block to a queue
[myQueue addOperationWithBlock: ^ {
NSURL *aURL = [NSURL URLWithString:#"http://www.somewhere.com/image.png"];
NSError *error = nil;
NSData *data = [NSData dataWithContentsOfURL:aURL options:nil error:&error];
UIImage *image = nil;
If (data)
image = [UIImage imageWithData:data];
// Update UI on the main thread.
[[NSOperationQueue mainQueue] addOperationWithBlock: ^ {
weakSelf.imageView.image = image;
}];
}];
I am trying to create an app like this:
(source: topmobiletrends.com)
where each of the views show an image for every in a photo in a JSON array.
I have successfully set and downloaded the array of images and set them in a UIImageView with with NSURLRequest and SDWebImage.
I created a UIView with an image view inside to test the code in my ViewController(Storyboard), everything works fine. However, when I try to create a UIView to loop programmatically, it shows up blank, with no ImageView. I'm not sure what I'm doing wrong.
Here is my code for my ViewController.m
for (NSObject *photo in photos) {
UIView *dView = [[UIView alloc]initWithFrame:CGRectMake(160,250,258,229)];
dView.backgroundColor = [UIColor whiteColor];
dispatch_async(dispatch_get_main_queue(),^{
UIImage *image = [UIImage imageWithData:data];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
myImage.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageURL]];
//Get image from url
[self.imageView setImageWithURL:imageURL];
[self.myImage addSubview:imageView];
priceLabel.text = [[[jsonDictionary objectForKey:#"site"] objectAtIndex:index] objectForKey:#"price"];
[imageView addSubview:dView];
});
}
}];
[task resume];
}
you are adding dView as subview for your image view may be it is [dView addSubview:imageView];
try this. the main issue is you are not setting frame for image view
for (NSObject *photo in photos) {
//UIView *dView = [[UIView alloc]initWithFrame:CGRectMake(160,250,258,229)];
//dView.backgroundColor = [UIColor whiteColor];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(160,250,258,229)];
[self.myImage addSubview:imageView];
priceLabel.text = [[[jsonDictionary objectForKey:#"site"] objectAtIndex:index] objectForKey:#"price"];
//[imageView addSubview:dView];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"URL"]];
dispatch_async(dispatch_get_main_queue(), ^{
if (imgData) {
[imageView setImage:[UIImage imageWithData:imgData]];
}
});
});
}
I'm a noobie in the Objective-C language, and I have a little problem.
In fact, I have 2 TableViews, and when I go from one to the other I parse some XML from the internet. The parsing is doing well, but I wanted to add an UIActivityIndicatorView between those 2 views to tell to the user that something is loading.
So, to do that, I wanted to do the parsing in another thread and show the UIActivityIndicatorView in the main thread. So here's my code :
- (void)viewDidLoad
{
[super viewDidLoad];
UIActivityIndicatorView *activityIndicator;
activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityIndicator.frame = CGRectMake(0.0, 0.0, 40.0, 40.0);
activityIndicator.center = self.view.center;
[self.view addSubview: activityIndicator];
activityIndicator.startAnimating;
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
dispatch_async(queue, ^{
NSError *error = nil;
// we will put parsed data in an a array
titles = [[NSMutableArray alloc] init];
urls = [[NSMutableArray alloc] init];
CXMLDocument *rssParser = [[CXMLDocument alloc] initWithContentsOfURL:[NSURL URLWithString:_emissionSelectionnee] options:0 error:&error];
NSArray *nodes = NULL;
nodes = [rssParser nodesForXPath:#"//rss/channel/item/title" error:nil];
for (CXMLElement *title in nodes) {
[titles addObject:[title stringValue]];
}
nodes = NULL;
nodes = [rssParser nodesForXPath:#"//rss/channel/item/enclosure" error:nil];
for (CXMLElement *url in nodes) {
[urls addObject:[[url attributeForName:#"url"] stringValue]];
}
dispatch_sync(dispatch_get_main_queue(), ^{
activityIndicator.stopAnimating;
});
}
}
So now, the UIActivityIndicator shows up, but the cells are empty.. When I do not use the dispatch_queue_t, it works well..
Does someone have an idea?
Thank you in advance!
You need to reload your Table view (in the same block where you hide the activity indicator):
[self.tableView reloadData]
I don't know how to explain but... If you understand me when I say a "loading circle" perfect, I just want to do this
// Start loading in the middle of the screen frozing all interaction
for (int c = 0; c < ([barcos count] - 1); c++)
{
NSArray *datos = [[barcos objectAtIndex:c] componentsSeparatedByString:#";"];
NSString *nombreImagen = [datos objectAtIndex:2];
NSURL *accesoFtp = [NSURL URLWithString:[NSString stringWithFormat:#"%#/%#",urlFtp,nombreImagen]];
NSData *imagen = [NSData dataWithContentsOfURL:accesoFtp];
[imagen writeToFile:[[NSString alloc] initWithString: [docsDir stringByAppendingPathComponent:[NSString stringWithFormat:#"Barcos/%#",nombreImagen]]] atomically:NO];
}
// Stop frozing all interaction and remove the loading circle
Probabbly I have to add a thread or something but I don't know how to do what I want exactly I hope you can help me, again. Thanks.
EDIT:
UIActivityIndicatorView *activityIndicator;
activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityIndicator.frame = CGRectMake(0.0, 0.0, 40.0, 40.0);
activityIndicator.center = self.view.center;
[self.view addSubview: activityIndicator];
activityIndicator.startAnimating;
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
dispatch_async(queue, ^{
for (int c = 0; c < ([barcos count] - 1); c++)
{
NSArray *datos = [[barcos objectAtIndex:c] componentsSeparatedByString:#";"];
NSString *nombreImagen = [datos objectAtIndex:2];
NSURL *accesoFtp = [NSURL URLWithString:[NSString stringWithFormat:#"%#/%#",urlFtp,nombreImagen]];
NSData *imagen = [NSData dataWithContentsOfURL:accesoFtp];
[imagen writeToFile:[[NSString alloc] initWithString: [docsDir stringByAppendingPathComponent:[NSString stringWithFormat:#"Barcos/%#",nombreImagen]]] atomically:NO];
}
dispatch_sync(dispatch_get_main_queue(), ^{
activityIndicator.stopAnimating;
});
});
Two things
1.- The activity indicator is too small but works, if I can to it bigger or same size but make darker the background would be better (Thanks!)
2.- I have a warning with startAnimating and stopAnimating "Property access result unused - getters should not be used for side effects"
Thanks =)
It can be done using MBProgressHUD
Also check this code:
//Show your activity indicator here
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
dispatch_async(queue, ^{
for (int c = 0; c < ([barcos count] - 1); c++)
{
NSArray *datos = [[barcos objectAtIndex:c] componentsSeparatedByString:#";"];
NSString *nombreImagen = [datos objectAtIndex:2];
NSURL *accesoFtp = [NSURL URLWithString:[NSString stringWithFormat:#"%#/%#",urlFtp,nombreImagen]];
NSData *imagen = [NSData dataWithContentsOfURL:accesoFtp];
[imagen writeToFile:[[NSString alloc] initWithString: [docsDir stringByAppendingPathComponent:[NSString stringWithFormat:#"Barcos/%#",nombreImagen]]] atomically:NO];
}
dispatch_sync(dispatch_get_main_queue(), ^{
//hide that activity indicator here
});
});
EDIT:
Never call methods like:
activityIndicator.startAnimating;
activityIndicator.stopAnimating;
These are used for calling setters and getters.
Change it to:
[activityIndicator startAnimating];
[activityIndicator stopAnimating];
You can use a UIActivityIndicatorView.
UIActivityIndicatorView *activityIndicator;
activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityIndicator.frame = CGRectMake(0.0, 0.0, 40.0, 40.0);
activityIndicator.center = self.view.center;
[self.view addSubview: activityIndicator];
Also, like shown by Midhun MP, you may have to use asynchronism to, for example, load data while your indicator is showing.
May be you can try Activity Indicator.
You can try using svprogresshud
You can alter the style of activityindicator,
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActionSheetStyleDefault];
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActionSheetStyleBlackOpaque];
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActionSheetStyleBlackTranslucent];
use any of these styles.
And This link can help to solve your second issue
xCode "Property access results unused - getters should not be used for side effects"
SOLUTION:
instead of using activityIndicator.startAnimating; and activityIndicator.stopAnimating
use [activityIndicator startAnimating]; and [activityIndicator stopAnimating];
For changing the size of the activity indicator:
activityIndicator.frame = CGRectMake(0.0, 0.0, 80.0, 80.0);
activityIndicator.transform = CGAffineTransformMakeScale(1.75, 1.75);
By the way, if you are populating a tableview, you may consider enabling the user interaction at the same time you launch the activity indicator:
[activityIndicator startAnimating];
tableView.userInteractionEnabled = NO;
and restoring it after:
[self.tableView reloadData];
[activityIndicator stopAnimating];
tableView.userInteractionEnabled = YES;