A tablview cell is being setup using the following code...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableviewcell *cell = [tableView dequeueReusableCellWithIdentifier:#"mytableviewcell" forIndexPath:indexPath];
cell.titleLabel.text = [self.data[indexPath.row] objectForKey:#"node_title"];
cell.taxonomy1Label.text = [self.data[indexPath.row] objectForKey:#"group"];
#try {
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[self.data[indexPath.row] objectForKey:#"image"]]];
cell.thumbnailImageView.image = [UIImage imageWithData:imageData];
}
#catch (NSException * e) {
NSLog(#"Exception: %#", e);
}
return cell;
}
The try/catch are just because it may or may not have an image but it was happening even before I put that in. It seems like there is some sort of issue when it goes to dequeue the cell. Any ideas?
If you are familiar with using third party libraries or CocoaPods, but this is a common problem, and I recommend the use of https://github.com/rs/SDWebImage or https://github.com/AFNetworking/AFNetworking, which have UIImageView category methods to handle loading images from a URL in the background, and not in a main thread.
For example using SDWebImage:
[cell.thumbnailImageView sd_setImageWithURL:yourImageURL];
This method will fetch the image in the background, not blocking the main thread and will not make your UITableView jitter.
As already mentioned this call blocks the main thread:
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[self.data[indexPath.row] objectForKey:#"image"]]];
What wasn't already mentioned is the right way to handle this kind of work.
You can't just wrap the above in a GCD call and expect everything to be OK.
You need to lazy load the images and populate them on the tableView when appropriate.
The most elegant way to handle this is to write your own NSOperation.
Here is a tutorial, any further questions let me know.
You are obstructing the main thread by trying to fetch image. Put your image call in a separate thread.
Related
I am making a program in which I am fetching a image from URL and displaying on the custom cell and rest of the table data on other cells.
Here is the code:
image=[[UIImage alloc]init];
if(indexPath.row==0)
{
LabelTableViewCell *cell=[detailtable dequeueReusableCellWithIdentifier:#"imagecell" forIndexPath:indexPath];
NSOperationQueue *myqueue=[[NSOperationQueue alloc] init];
NSBlockOperation *downloadOperation = [NSBlockOperation blockOperationWithBlock:^{
[cell.actindi startAnimating];
image=[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[frontimage objectAtIndex:0]]]];
}];
downloadOperation.completionBlock=^{
cell.imageprimary.image=image;
[cell.actindi stopAnimating];
};
[myqueue addOperation:downloadOperation];
return cell;
}
else
{
LabelTableViewCell *cell = [detailtable dequeueReusableCellWithIdentifier:#"tabledetail" forIndexPath:indexPath];
cell.labelspec.text=[firstArray objectAtIndex:indexPath.row];
cell.txtfield.text=[secondArray1 objectAtIndex:indexPath.row];
return cell;
}
return 0;
Problem I am facing:
Whenever I scroll up it reloads the top cell.
It dont reload the image on first cell till I click on that cell.
if(!cell) and if (cell==nil) not working.
Reloading the table puts in an infinite loop till I scroll down.
So I want to permanently allocate the memory to the first cell as it is only one in number.
I have tried putting nil in dequeueReusableCellWithIdentifier and its not working.
I have gone about 20-30 articles to do that but nothing has worked for me I don't know why.
So please give me any specific solution at the present condition.
I am new in the iOS developing don't know too much about it . This is my first program that I am trying to make.
You can try it different way:
To download and show image Just use library - https://github.com/rs/SDWebImage
Library will download image and cache for you.
Currently I am loading all of my UITableViewControllers with images and text. I'm not sure if there is a way of shortening my loading times. I'm thinking that GCD might be the best route to go, however, I'm not too sure that I'm using this correctly:
dispatch_async(dispatch_get_main_queue(), ^{
[some method];
});
This is being loaded in the ViewDidLoad, and I'm unsure if this is the correct place to use GCD. Also, is this correct way of asynchronously loading information?
use this, it downloads multiple files in different threads.And its asynchronous.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//load image here
});
If you want to load all images ,even the images which might be very down in tableView (say image at 20th row).Then viewDidLoad is perfect approach, or if you want to only load images that are currently to be shown, then download images in cellForRow:atIndexPath method.
OR,you can use AFNetworking to download multiple files much efficiently.
Hope this helps
For loading images faster in table view cells you can use SDWebImage.
It is very simple to use. Just import the SDWebImage folder into your Xcode project. It contains a category on UIImageVIew. So on any imageView object just call the method setImageWithURL as illustrated below:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:MyIdentifier] autorelease];
}
// Here we use the new provided setImageWithURL: method to load the web image
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
cell.textLabel.text = #"My Text";
return cell;
}
I have an app where I load a lot of large images. When I lazy-load them, and even after the image has been loaded, the cell does not load them until I take my finger off the screen. I am calling my downloadImageForVisiblePaths function in the UIScrollViewDelegate methods scrollViewDidEndDragging and in scrollViewDidEndDecelerating apart from this, I am also setting the image in the UITableView's cellForRowAtIndexPath method like so:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
// Code to load reusable custom cell
CustomObject *object = (CustomObject*) [self.tableArray objectAtIndex: indexPath];
if(!object.mainImage){
[self downloadImageForIndexPath:indexPath];
cell.mainImageView.image = [UIImage imageNamed:#"placeholder"];
}else{
cell.mainImageView.image = object.mainImage;
}
return cell;
}
Where the downloadImageForIndexPath looks like this:
-(void) downloadImageForIndexPath:(NSIndexPath*) indexPath{
UIImage *loadedImage = [[UIImage alloc] init];
// take url and download image with dispatch_async
// Once the load is done, the following is done
CustomCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
cell.mainImageView.image = loadedImage;
CustomObject *object = (CustomObject*) [self.tableArray objectAtIndex: indexPath];
object.mainImage = loadedImage;
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableVIew reloadData];
});
}
I can't see where I am going wrong. I need the images to load even when the finger is on the screen. This behaviour is similar to how the images load on apps like Google+, Instagram or Facebook.
Any pointers will be much appreciated.
It's hard to tell since you didn't include all the code for downloadImageForIndexPath, but it looks like you are assigning an image to a cell from a background thread (you shouldn't touch UI controls from background threads). Also, if you'r updating cell directly, you don't need to call reloadData.
I would also suggest using SDWebImage for displaying remote images in a tableview.
Every time I scroll the TableView, my images gets messed up, mainly the first row. I realy don`t kwon what to do.
Here is the code:
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
BarCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
[cell.activityFotoBar startAnimating];
cell.activityFotoBar.hidesWhenStopped = YES;
if(!cell){
cell = [[BarCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
NSMutableDictionary *infoBar = [self.bares objectAtIndex:indexPath.row];
NSString *nomeImagem = [infoBar objectForKey:#"foto"];
NSURL *url = [NSURL URLWithString:nomeImagem];
NSURLRequest *requestImagem = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:requestImagem queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if(connectionError == nil){
cell.imageViewImagemBar.image = [UIImage imageWithData:data];
[cell.activityFotoBar stopAnimating];
}
}];
cell.labelNomeBar.text = [infoBar objectForKey:#"nome"];
cell.labelEnderecoBar.text = [infoBar objectForKey:#"endereco"];
cell.labelAvaliacaoBar.text = [NSString stringWithFormat:#"Votos: %#", [infoBar objectForKey:#"votos"]];
return cell;
}
Thanks in advance!
The problem happens because the asynchronous image request finishes after your cell scrolls off the screen and gets reused. Downloads complete "out of order", contributing to a visual confusion. Essentially, some of the cells put up for reuse by scrolling, are still "hot", in the sense that their image load is in progress. Reusing such cell creates a race between the old and the new image downloads.
You should change the strategy that you use to load the images: rather than sending a request and "forgetting" it, consider using connectionWithRequest:delegate: method, storing the connection in the cell, and calling cancel on it when prepareForReuse method is called. This way your reused cells would be "cold".
You can use SDWebcache library. It contains a UIImageView category class which can load images from urls. I find it works well with tableviews
The code should "almost" work. You just need to fix one issue to get it working (though not optimally):
When the asynchronous request sendAsynchronousRequest:queue:completionHandler: has been finished, the completion handler will be called. Then, you need to retrieve the cell again from the table view specifying the indexPath at the time the request has been started.
You just need to capture the indexPath within the block in order to get a reference that stays valid until after the block finishes.
The UITableView's method to retrieve the cell is
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath
If the return value (a cell) is nil the cell at index path indexPath is not visible.
So, the completion block will look as follows:
if (error == nil && ... ) {
BarCell* cell = [self.tableView cellForRowAtIndexPath:indexPath]
if (cell) {
cell.imageViewImagemBar.image = [UIImage imageWithData:data];
[cell.activityFotoBar stopAnimating];
}
}
Which also safely handles the case where the cell is nil.
Note: while this "works" it's still a naïve approach. A more sophisticated approach would cache the (decoded) images and possibly has some "look ahead and eager eviction strategy" for better user experience and lower memory foot-print.
Note that imageWithData: may still be costly since it may require to decode, decompress and resize the image before it can be rendered. Basically, this can be performed beforehand, with an offscreen context.
I am using a tableview which loads images from the documents directory, creates a thumbnail and shows it in the tableview. However, I have a problem: it becomes slow and crashes as the pictures are large, taken using the camera.
I have explored several solution including GCD to do the work in a background thread but the result is the same thing. So, I thought to look into SDWebImage but I don't know if it will also work for local files, not web images in this case. Can someone advise me please? If not, how is this problem solved? Is there an API that can help to resolve this issue?
That question is not easy to answer as the Question is asked fairly broad but I will do my best.
First, I usually dispatch a Background Thread if I have expensive processing to do as to not block the Main Thread, which is fairly important.
I don't really know why you are not using the normal UIImageView for what you are doing but try to implement following :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"YourCell";
MyCellClass *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[MyCellClass alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
/*
Whatever Code you want
*/
NSArray* params =#[cell.myImageView, #"http://myfancyimages.com/image.png"];
[self performSelectorInBackground:#selector(loadEventImageWithParameters:) withObject:params];
return cell;
}
And now add the function :
- (void) loadEventImageWithParameters:(id) parameters {
NSArray* params = [[NSArray alloc] initWithArray:(NSArray*)parameters];
NSURL *url = [NSURL URLWithString:[params objectAtIndex:0]];
UIImage *image = [UIImage imageWithData: [NSData dataWithContentsOfURL:url]];
UIImageView* theImageView = (UIImageView*) [params objectAtIndex:0];
[theImageView setImage:image];
}
If you got a lot of Pictures to load you are well advised to queue your Processes so you don;t "steal" all resources with Grand Central Dispatch.
Have a read through this excellent post http://www.raywenderlich.com/4295/multithreading-and-grand-central-dispatch-on-ios-for-beginners-tutorial for further details.
Hope that helped