Wrong data populated in tableview cell on dispatch queue - ios

I have an iOS app that has a UITableView with custom TableViewCells that contain a UIImageView. The image is loaded from a web service, so during the initial load, I display a "loading" image, and then use gcd to dispatch and get the image matching the data for that cell.
When I use a DISPATCH_QUEUE_PRIORITY_HIGH global queue to perform the image fetch, I sporadically get the wrong images loading in the tableview cells. If I use my own custom queue then the correct images get populated into the cells but the tableview performance is awful.
Here is the code...
// See if the icon is in the cache
if([self.photoCache objectForKey:[sample valueForKey:#"api_id"]]){
[[cell sampleIcon]setImage:[self.photoCache objectForKey:[sample valueForKey:#"api_id"]]];
}
else {
NSLog(#"Cache miss");
[cell.sampleIcon setImage:nil];
dispatch_queue_t cacheMissQueue = dispatch_queue_create("cacheMissQueue", NULL);
//dispatch_queue_t cacheMissQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(cacheMissQueue, ^{
if(sample.thumbnailFilename && sample.api_id){
NSData *thumbNailData = [[NSData alloc] initWithContentsOfFile:sample.thumbnailFilename];
UIImage *thumbNailImage = [[UIImage alloc]initWithData:thumbNailData];
if(thumbNailImage){
// Set the cell
dispatch_sync(dispatch_get_main_queue(), ^{
[[cell sampleIcon]setImage:thumbNailImage];
[cell setNeedsLayout];
});
// save it to cache for future references
NSLog(#"DEBUG: Saving to cache %# for sample %#",sample.thumbnailFilename,[sample objectID]);
[self.photoCache setObject:thumbNailImage forKey:sample.api_id];
}
}
});
dispatch_release(cacheMissQueue);
}

Watching the WWDC 2012 session #211 helped a lot and I changed the code from using GCD to NSOperationQueue and it solved the problem.
New code...
[[self imgQueue]addOperationWithBlock:^{
if(sample.thumbnailFilename && sample.api_id){
NSData *thumbNailData = [[NSData alloc] initWithContentsOfFile:sample.thumbnailFilename];
UIImage *thumbNailImage = [[UIImage alloc]initWithData:thumbNailData];
if(thumbNailImage){
// Set the cell
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
[[cell sampleIcon]setImage:thumbNailImage];
[cell setNeedsLayout];
}];
// save it to cache for future references
[self.photoCache setObject:thumbNailImage forKey:sample.api_id];
}
}
}];

When you finally get an image, you need an association between the indexPath of the cell and the image. Since this is on a backgound thread, what I suggest you do is post a nofification using a block to the mainQueue that such and such an image is available. On the main thread only, you ask the tableView for the array of visible cells, and if the cell you have an image for is showing, you can then set the image directly at that time (your on the main thread, you know the cell is there and showing, and its not going to change for this runloop iteration.) If the cell is not showing, no problem, next time that cell comes into scope you will have the image waiting for it. I am doing this now in my app, its been out many months, and its all working well and getting good reviews on responsiveness (just as your app will if you do this!)

Related

Getting same images in UITableView cell in Dispatch_queue

I am adding images from server. I am using NSMutableArray and custom UITableViewCell. Problem : When I run the project. UITableViewCell displaying same images. I think it refreshing cell. How can I fix that issue?
Below dispatch method I used,
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//this will start the image loading in bg
dispatch_async(concurrentQueue, ^{
NSError *nserror = nil;
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString: [imagesArray2 objectAtIndex:indexPath.row]]options:NSDataReadingUncached error:&nserror];
//this will set the image when loading is finished
dispatch_async(dispatch_get_main_queue(), ^{
if (nserror)
{
[cell.imgview setImage:[UIImage imageNamed:#"placeholderimage.png"]];
}
else
{
[cell.imgview setImage:[UIImage imageWithData:imageData]];
}
[mytablview reloadData];
});
});
Thanks for any help.
Just reset cell imageView, before loading new image.
[cell.imgview setImage:[UIImage imageNamed:#"placeholderimage.png"]];
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
NSError *nserror = nil;
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString: [imagesArray2 objectAtIndex:indexPath.row]]options:NSDataReadingUncached error:&nserror];
dispatch_async(dispatch_get_main_queue(), ^{
if (nserror) {
[cell.imgview setImage:[UIImage imageNamed:#"placeholderimage.png"]];
} else {
[cell.imgview setImage:[UIImage imageWithData:imageData]];
}
});
});
This doesn't really answer your question, but it may help you change the direction you're heading in.
When you finish loading the image, the cell may have already started loading a different image because UITableViews reuse their cells. The operation of asynchronously loading the image and setting it once it's done doesn't get cancelled and so you're going to get incorrect images popping up all over the shop, since there's no guarantee of which image loading finishes first.
Please see my comment regarding AFNetworking, which I highly recommend that you use, but if you go down a different path then you should be using some kind of NSOperation subclass for loading the images. That way you can cancel the operations when the cells are reused and you can then safely load a different image for that cell.
Save the url in the cell that will ultimately show the image. Then when you attempt to show the image after it arrives make sure the url in the cell matches the url of the image you are trying to show. If it doesn't match don't show it; this means the cell has been reused (due to scrolling or refresh) and no longer requires that image. No need to stop the loading of the image, just the display.

UISegmentedControl with two UICollectionViews to display images. MULTITHREADING

So my goal is to create something like this:
My problem is when I switch between segments, I would like to show a UIActivityViewIndicator spinning, as it takes some time to load the images (stored locally so in theory this should be much faster...). But the thread seems to be getting back before switching to the new cells' images.
What am I doing wrong?
Here is my code:
- (void)reloadCollection {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[_myCollection reloadData];
[_myCollection layoutIfNeeded];
dispatch_async(dispatch_get_main_queue(), ^{
_spinnerView.hidden = YES;
});
});
}
- (IBAction)segmentedControlSwitched:(id)sender{
_spinnerView.hidden = NO;
[self reloadCollection];
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
if ([_mySegmentedControl selectedSegmentIndex] == 0){
return [_photosTaken count];
}else
return [_photosTagged count];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *cell;
UIImageView *imageView;
if ([_mySegmentedControl selectedSegmentIndex] == 0){
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
imageView = [[UIImageView alloc] initWithImage:[_photosTaken objectAtIndex:indexPath.row]];
}
else
{
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cell1" forIndexPath:indexPath];
imageView = [[UIImageView alloc] initWithImage:[_photosTagged objectAtIndex:indexPath.row]];
}
imageView.frame = cell.frame;
[cell setBackgroundView:imageView];
return cell;
}
Thanks,
João Garcia.
You can't call UIKit operations on a background thread, unless the documentation explicitly states that you can.
In any case, your problem is not one that should be solved simply by throwing things on a background thread.
You say yourself, for some reason the loading thread is taking too long. Why not use the time profiler in instruments to find out why?
You'll probably find that the issue is that you are creating a brand new image view every time and adding it as the cells background view.
Creating, adding and removing views is an expensive operation that should not be performed while scrolling. Your custom collection view cells should already have an image view, which you've added in the storyboard or a custom init method, and created an outlet or property for. It's very unusual not to use a subclassed cell, since the base cell has nothing in the content view.
When populating the cell, just set the image view's image property to your stored image. This should be fast enough that you don't need a spinner.
From your comments, it sounds like you also have these problems:
you are using images that are far too large. For display in a list you must use appropriately sized thumbnail images
you are performance testing on the simulator. This is irrelevant to real-life app performance on a device. The memory situation is completely different and the speed of things can be faster or slower depending on the operation (usually, CPU-based is faster, but GPU-based is slower).
dispatch_get_global_queue get a concurrent dispatch queue in which multiple tasks are run parallel. It is a background queue, UI updating should be performed on mainQueue, so [_myCollection layoutIfNeeded] should be moved into dispatch_async(dispatch_get_main_queue(),
updating UI on concurrent background queue will cause weird behaviour such as pausing or freezing.
And you can try this.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// do time consuming things here
dispatch_sync(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
dispatch_sync(dispatch_get_main_queue(), ^{
[self.activityView stopAnimating]; // after collectionView is updated
});
});
Instead of dispatch_async, try using dispatch_sync for _spinnerView.hidden = YES; on the main queue.
It seems that the issue here was the size of the photos as #jrturton mentions in his answer.
I shortened the size (to about 10s of KBs) and it works smoothly.

How would I design an asynchronous image downloader for my UITableView that prioritizes downloads depending on location in table view?

In my app, I receive a list of image URLs to use as thumbnails in my table view. The tableview has a small amount of items, approximately 30, so I want to load all the thumbnails immediately (instead of when they become visible, as they undoubtedly will become visible and I want them fully loaded then).
I want to prioritize the image downloads by index path, so the first index path has priority over the second, which has priority over the third, etc.
However, if I suddenly jump to the end of the table view (which shows, say, index paths 24-29) I want the images for those index paths to become highest priority, so if they jump at the very beginning they won't have to wait for all the others to download first.
How would I go about designing something like this? If it's possible with SDWebImage that'd be great, as I'm comfortable with that, but if not I'm more than fine with creating something from scratch with NSURLSession. (I've looked at SDWebImage, and the SDWebImagePrefetcher looks promising, but doesn't allow priority changing from what I've seen.)
I recently had a similar problem, but didn't need to have a priority to load. I can't think of how to change the priority of what is loaded unless you do the loading of thumbnails before loading the tableview. Tableviews load cells as they are needed.
I can think of two options:
If you want all the data loaded and ready before the tableview is even loaded, preemptively load the data in an earlier view controller and save it to be opened in the view controller containing your table view. Then no requests will have to be made by your tableview and everything will appear seamlessly.
The tableview sends each thumbnail request asynchronously and updates the cell as the images arrive on the main thread. You can then cache or save the images after they arrive. But the images won't appear instantly, generally a split second later.
I did option two using NSCache. The value for key "avatar" is the URL of the thumbnail image. This code is located in my - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
if (![teamMember[#"avatar"] isEqualToString:#""]) {
// check for cached image, use if it exists
UIImage *cachedImage = [self.imageCache objectForKey:teamMember[#"avatar"]];
if (cachedImage) {
cell.memberImage.image = cachedImage;
}
//else retrieve the image from server
else {
NSURL *imageURL = [NSURL URLWithString:teamMember[#"avatar"]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
// if valid data, create UIImage
if (imageData) {
UIImage *image = [UIImage imageWithData:imageData];
// if valid image, update in tableview asynch
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
TeamCommitsCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
// if valid cell, display image and add to cache
if (updateCell) {
updateCell.memberImage.image = image;
[self.imageCache setObject:image forKey:teamMember[#"avatar"]];
}
});
}
}
});
}
}

tableView cell image which is asynchronous loaded flickers as you scroll fast [duplicate]

This question already has answers here:
Async image loading from url inside a UITableView cell - image changes to wrong image while scrolling
(13 answers)
Closed 9 years ago.
Im using a asynchronous block (Grand central dispatch) to load my cell images. However if you scroll fast they still appear but very fast until it has loaded the correct one. Im sure this is a common problem but I can not seem to find a away around it.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Load the image with an GCD block executed in another thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[[appDelegate offersFeeds] objectAtIndex:indexPath.row] imageurl]]];
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *offersImage = [UIImage imageWithData:data];
cell.imageView.image = offersImage;
});
});
cell.textLabel.text = [[[appDelegate offersFeeds] objectAtIndex:indexPath.row] title];
cell.detailTextLabel.text = [[[appDelegate offersFeeds] objectAtIndex:indexPath.row] subtitle];
return cell;
}
At the very least, you probably want to remove the image from the cell (in case it is a re-used cell) before your dispatch_async:
cell.imageView.image = [UIImage imageNamed:#"placeholder.png"];
Or
cell.imageView.image = nil;
You also want to make sure that the cell in question is still on screen before updating (by using the UITableView method, cellForRowAtIndexPath: which returns nil if the cell for that row is no longer visible, not to be confused with the UITableViewDataDelegate method tableView:cellForRowAtIndexPath:), e.g.:
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.imageView.image = [UIImage imageNamed:#"placeholder.png"];
// Load the image with an GCD block executed in another thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[[appDelegate offersFeeds] objectAtIndex:indexPath.row] imageurl]]];
if (data) {
UIImage *offersImage = [UIImage imageWithData:data];
if (offersImage) {
dispatch_async(dispatch_get_main_queue(), ^{
UITableViewCell *updateCell = [tableView cellForRowAtIndexPath:indexPath];
if (updateCell) {
updateCell.imageView.image = offersImage;
}
});
}
}
});
cell.textLabel.text = [[[appDelegate offersFeeds] objectAtIndex:indexPath.row] title];
cell.detailTextLabel.text = [[[appDelegate offersFeeds] objectAtIndex:indexPath.row] subtitle];
return cell;
Frankly, you should also be using a cache to avoid re-retrieving images unnecessarily (e.g. you scroll down a bit and scroll back up, you don't want to issue network requests for those prior cells' images). Even better, you should use one of the UIImageView categories out there (such as the one included in SDWebImage or AFNetworking). That achieves the asynchronous image loading, but also gracefully handles cacheing (don't reretrieve an image you just retrieved a few seconds ago), cancelation of images that haven't happened yet (e.g. if user quickly scrolls to row 100, you probably don't want to wait for the first 99 to retrieve before showing the user the image for the 100th row).
This is a problem with async image loading...
Let's say you have 5 visible rows at any given time.
If you are scrolling fast, and you scroll down for instance 10 rows, the tableView:cellForRowAtIndexPath will be called 10 times. The thing is that these calls are faster than the images are returned, and you have the 10 pending images from different URL-s.
When the images finally come back from the server, they will be returned on those cells that you put in the async loader. Since you are reusing only 5 cells, some of these images will be displayed twice on each cell, as they are downloaded from the server, and that is why you see flickering. Also, remember to call
cell.imageView.image = nil
before calling the async downloading method, as the previous image from the reused cell will remain and also cause a flickering effect when the new image is assigned.
The way around this is to store the latest URL of the image you have to display on each cell, and then when the image comes back from the server check that URL with the one you have in your request. If it is not the same, cache that image for later.
For caching requests, check out NSURLRequest and NSURLConnection classes.
I strongly suggest that you use AFNetworking for any server communication though.
Good luck!
The reason of your flicker is that your start the download for several images during the scrolling, every time you a cell is displayed on screen a new request is performed and it's possible that the old requests are not completed, every tile a request completes the image is set on the cell, so it's if you scroll fast you use let's say a cell 3 times = 3 requests that will be fired = 3 images will be set on that cell = flicker.
I had the same issue and here is my approach:
Create a custom cell with all the required views. Each cells has it's own download operation. In the cell's -prepareForReuse method. I would make the image nil and cancel the request.
In this way for each cell I have only one request operation = one image = no flicker.
Even using AFNetworking you can have the same issue if you won't cancel the image download.
The issue is that you download the image on the main thread. Dispatch a background queue for that:
dispatch_queue_t myQueue = dispatch_queue_create("myQueue", NULL);
// execute a task on that queue asynchronously
dispatch_async(myQueue, ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[[[appDelegate offersFeeds] objectAtIndex:indexPath.row] imageurl]]];
//UI updates should remain on the main thread
UIImage *offersImage = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *offersImage = [UIImage imageWithData:data];
cell.imageView.image = offersImage;
});
});

How to asynchronously load an image in an UIImageView?

I have an UIView with an UIImageView subview. I need to load an image in the UIImageView without blocking the UI. The blocking call seems to be: UIImage imageNamed:. Here is what I thought solved this problem:
-(void)updateImageViewContent {
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
UIImage * img = [UIImage imageNamed:#"background.jpg"];
dispatch_sync(dispatch_get_main_queue(), ^{
[[self imageView] setImage:img];
});
});
}
The image is small (150x100).
However the UI is still blocked when loading the image. What am I missing ?
Here is a small code sample that exhibits this behaviour:
Create a new class based on UIImageView, set its user interaction to YES, add two instances in a UIView, and implement its touchesBegan method like this:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.tag == 1) {
self.backgroundColor= [UIColor redColor];
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
[self setImage:[UIImage imageNamed:#"woodenTile.jpg"]];
});
[UIView animateWithDuration:0.25 animations:
^(){[self setFrame:CGRectInset(self.frame, 50, 50)];}];
}
}
Assign the tag 1 to one of these imageViews.
What happens exactly when you tap the two views almost simultaneously, starting with the view that loads an image? Does the UI get blocked because it's waiting for [self setImage:[UIImage imageNamed:#"woodenTile.jpg"]]; to return ? If so, how may I do this asynchronously ?
Here is a project on github with ipmcc code
Use a long press then drag to draw a rectangle around the black squares. As I understand his answer, in theory the white selection rectangle should not be blocked the first time the image is loaded, but it actually is.
Two images are included in the project (one small: woodenTile.jpg and one larger: bois.jpg). The result is the same with both.
Image format
I don't really understand how this is related to the problem I still have with the UI being blocked while the image is loaded for the first time, but PNG images decode without blocking the UI, while JPG images do block the UI.
Chronology of the events
The blocking of the UI begins here..
.. and ends here.
AFNetworking solution
NSURL * url = [ [NSBundle mainBundle]URLForResource:#"bois" withExtension:#"jpg"];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
[self.imageView setImageWithURLRequest:request
placeholderImage:[UIImage imageNamed:#"placeholder.png"]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
NSLog(#"success: %#", NSStringFromCGSize([image size]));
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"failure: %#", response);
}];
// this code works. Used to test that url is valid. But it's blocking the UI as expected.
if (false)
if (url) {
[self.imageView setImage: [UIImage imageWithData:[NSData dataWithContentsOfURL:url]]]; }
Most of the time, it logs: success: {512, 512}
It also occasionnaly logs: success: {0, 0}
And sometimes: failure: <NSURLResponse: 0x146c0000> { URL: file:///var/mobile/Appl...
But the image is never changed.
The problem is that UIImage doesn't actually read and decode the image until the first time it's actually used/drawn. To force this work to happen on a background thread, you have to use/draw the image on the background thread before doing the main thread -setImage:. This worked for me:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
UIImage * img = [UIImage imageNamed:#"background.jpg"];
// Make a trivial (1x1) graphics context, and draw the image into it
UIGraphicsBeginImageContext(CGSizeMake(1,1));
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), [img CGImage]);
UIGraphicsEndImageContext();
// Now the image will have been loaded and decoded and is ready to rock for the main thread
dispatch_sync(dispatch_get_main_queue(), ^{
[[self imageView] setImage: img];
});
});
EDIT: The UI isn't blocking. You've specifically set it up to use UILongPressGestureRecognizer which waits, by default, a half a second before doing anything. The main thread is still processing events, but nothing is going to happen until that GR times out. If you do this:
longpress.minimumPressDuration = 0.01;
...you'll notice that it gets a lot snappier. The image loading is not the problem here.
EDIT 2: I've looked at the code, as posted to github, running on an iPad 2, and I simply do not get the hiccup you're describing. In fact, it's quite smooth. Here's a screenshot from running the code in the CoreAnimation instrument:
As you can see on the top graph, the FPS goes right up to ~60FPS and stays there throughout the gesture. On the bottom graph, you can see the blip at about 16s which is where the image is first loaded, but you can see that there's not a drop in the frame rate. Just from visual inspection, I see the selection layer intersect, and there's a small, but observable delay between the first intersection and the appearance of the image. As far as I can tell, the background loading code is doing its job as expected.
I wish I could help you more, but I'm just not seeing the problem.
You can use AFNetworking library , in which by importing the category
"UIImageView+AFNetworking.m"
and by using the method as follows :
[YourImageView setImageWithURL:[NSURL URLWithString:#"http://image_to_download_from_serrver.jpg"]
placeholderImage:[UIImage imageNamed:#"static_local_image.png"]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
//ON success perform
}
failure:NULL];
hope this helps .
I had a very similar issue with my application where I had to download lot of images and along with that my UI was continuously updating. Below is the simple tutorial link which resolved my issue:
NSOperations & NSOperationQueues Tutorial
this is the good way:
-(void)updateImageViewContent {
dispatch_async(dispatch_get_main_queue(), ^{
UIImage * img = [UIImage imageNamed:#"background.jpg"];
[[self imageView] setImage:img];
});
}
Why don't you use third party library like AsyncImageView? Using this, all you have to do is declare your AsyncImageView object and pass the url or image you want to load. An activity indicator will display during the image loading and nothing will block the UI.
-(void)touchesBegan: is called in the main thread. By calling dispatch_async(dispatch_get_main_queue) you just put the block in the queue. This block will be processed by GCD when the queue will be ready (i.e. system is over with processing your touches). That's why you can't see your woodenTile loaded and assigned to self.image until you release your finger and let GCD process all the blocks that have been queued in the main queue.
Replacing :
dispatch_async(dispatch_get_main_queue(), ^{
[self setImage:[UIImage imageNamed:#"woodenTile.jpg"]];
});
by :
[self setImage:[UIImage imageNamed:#"woodenTile.jpg"]];
should solve your issue… at least for the code that exhibits it.
Consider using SDWebImage: it not only downloads and caches the image in the background, but also loads and renders it.
I've used it with good results in a tableview that had large images that were slow to load even after downloaded.
https://github.com/nicklockwood/FXImageView
This is an image view which can handle background loading.
Usage
FXImageView *imageView = [[FXImageView alloc] initWithFrame:CGRectMake(0, 0, 100.0f, 150.0f)];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.asynchronous = YES;
//show placeholder
imageView.processedImage = [UIImage imageNamed:#"placeholder.png"];
//set image with URL. FXImageView will then download and process the image
[imageView setImageWithContentsOfURL:url];
To get an URL for your file you might find the following interesting:
Getting bundle file references / paths at app launch
When you are using AFNetwork in an application, you do not need to use any block for load image because AFNetwork provides solution for it. As below:
#import "UIImageView+AFNetworking.h"
And
Use **setImageWithURL** function of AFNetwork....
Thanks
One way i've implemented it is the Following: (Although i do not know if it's the best one)
At first i create a queue by using Serial Asynchronous or on Parallel Queue
queue = dispatch_queue_create("com.myapp.imageProcessingQueue", DISPATCH_QUEUE_SERIAL);**
or
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
**
Which ever you may find better for your needs.
Afterwards:
dispatch_async( queue, ^{
// Load UImage from URL
// by using ImageWithContentsOfUrl or
UIImage *imagename = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
// Then to set the image it must be done on the main thread
dispatch_sync( dispatch_get_main_queue(), ^{
[page_cover setImage: imagename];
imagename = nil;
});
});
There is a set of methods introduced to UIImage in iOS 15 to decode images and create thumbnails asynchronously on background thread
func prepareForDisplay(completionHandler: (UIImage?) -> Void)
Decodes an image asynchronously and provides a new one for display in views and animations.
func prepareThumbnail(of: CGSize, completionHandler: (UIImage?) -> Void)
Creates a thumbnail image at the specified size asynchronously on a background thread.
You can also use a set of similar synchronous APIs, if you need more control over where you want the decoding to happen, e.g. specific queue:
func preparingForDisplay() -> UIImage?
func preparingThumbnail(of: CGSize) -> UIImage?

Resources