Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 264 Vote(s) - 3.49 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Async image loading from url inside a UITableView cell - image changes to wrong image while scrolling

#11
# Swift 3 #
I write my own light implementation for image loader with using NSCache.
**No cell image flickering!**

### ImageCacheLoader.swift ###

typealias ImageCacheLoaderCompletionHandler = ((UIImage) -> ())

class ImageCacheLoader {

var task: URLSessionDownloadTask!
var session: URLSession!
var cache: NSCache<NSString, UIImage>!

init() {
session = URLSession.shared
task = URLSessionDownloadTask()
self.cache = NSCache()
}

func obtainImageWithPath(imagePath: String, completionHandler: @escaping ImageCacheLoaderCompletionHandler) {
if let image = self.cache.object(forKey: imagePath as NSString) {
DispatchQueue.main.async {
completionHandler(image)
}
} else {
/* You need placeholder image in your assets,
if you want to display a placeholder to user */
let placeholder = #imageLiteral(resourceName: "placeholder")
DispatchQueue.main.async {
completionHandler(placeholder)
}
let url: URL! = URL(string: imagePath)
task = session.downloadTask(with: url, completionHandler: { (location, response, error) in
if let data = try? Data(contentsOf: url) {
let img: UIImage! = UIImage(data: data)
self.cache.setObject(img, forKey: imagePath as NSString)
DispatchQueue.main.async {
completionHandler(img)
}
}
})
task.resume()
}
}
}

## Usage example ##

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

let cell = tableView.dequeueReusableCell(withIdentifier: "Identifier")

cell.title = "Cool title"

imageLoader.obtainImageWithPath(imagePath: viewModel.image) { (image) in
// Before assigning the image, check whether the current cell is visible
if let updateCell = tableView.cellForRow(at: indexPath) {
updateCell.imageView.image = image
}
}
return cell
}
Reply

#12
You can just pass your URL,

NSURL *url = [NSURL URLWithString:@"http://www.myurl.com/1.png"];
NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (data) {
UIImage *image = [UIImage imageWithData:data];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
yourimageview.image = image;
});
}
}
}];
[task resume];
Reply

#13
Assuming you're looking for a quick tactical fix, what you need to do is make sure the cell image is initialized and also that the cell's row is still visible, e.g:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];

cell.poster.image = nil; // or cell.poster.image = [UIImage imageNamed:@"placeholder.png"];

NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myurl.com/%@.jpg", self.myJson[indexPath.row][@"movieId"]]];

NSURLSessionTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (data) {
UIImage *image = [UIImage imageWithData:data];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
MyCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
if (updateCell)
updateCell.poster.image = image;
});
}
}
}];
[task resume];

return cell;
}

The above code addresses a few problems stemming from the fact that the cell is reused:

1. You're not initializing the cell image before initiating the background request (meaning that the last image for the dequeued cell will still be visible while the new image is downloading). Make sure to `nil` the `image` property of any image views or else you'll see the flickering of images.

2. A more subtle issue is that on a really slow network, your asynchronous request might not finish before the cell scrolls off the screen. You can use the `UITableView` method `cellForRowAtIndexPath:` (not to be confused with the similarly named `UITableViewDataSource` method `tableView:cellForRowAtIndexPath:`) to see if the cell for that row is still visible. This method will return `nil` if the cell is not visible.

The issue is that the cell has scrolled off by the time your async method has completed, and, worse, the cell has been reused for another row of the table. By checking to see if the row is still visible, you'll ensure that you don't accidentally update the image with the image for a row that has since scrolled off the screen.

3. Somewhat unrelated to the question at hand, I still felt compelled to update this to leverage modern conventions and API, notably:

- Use `NSURLSession` rather than dispatching `-[NSData contentsOfURL:]` to a background queue;

- Use `dequeueReusableCellWithIdentifier:forIndexPath:` rather than `dequeueReusableCellWithIdentifier:` (but make sure to use cell prototype or register class or NIB for that identifier); and

- I used a class name that conforms to [Cocoa naming conventions][1] (i.e. start with the uppercase letter).

Even with these corrections, there are issues:

1. The above code is not caching the downloaded images. That means that if you scroll an image off screen and back on screen, the app may try to retrieve the image again. Perhaps you'll be lucky enough that your server response headers will permit the fairly transparent caching offered by `NSURLSession` and `NSURLCache`, but if not, you'll be making unnecessary server requests and offering a much slower UX.

2. We're not canceling requests for cells that scroll off screen. Thus, if you rapidly scroll to the 100th row, the image for that row could be backlogged behind requests for the previous 99 rows that aren't even visible anymore. You always want to make sure you prioritize requests for visible cells for the best UX.

The simplest fix that addresses these issues is to use a `UIImageView` category, such as is provided with [SDWebImage][2] or [AFNetworking][3]. If you want, you can write your own code to deal with the above issues, but it's a lot of work, and the above `UIImageView` categories have already done this for you.


[1]:

[To see links please register here]

[2]:

[To see links please register here]

[3]:

[To see links please register here]

Reply

#14
There are multiple frameworks that solve this problem. Just to name a few:

Swift:

- [**Nuke**](

[To see links please register here]

) (mine)
- [Kingfisher](

[To see links please register here]

)
- [AlamofireImage](

[To see links please register here]

)
- [HanekeSwift](

[To see links please register here]

)

Objective-C:

- [AFNetworking](

[To see links please register here]

)
- [PINRemoteImage](

[To see links please register here]

)
- [YYWebImage](

[To see links please register here]

)
- [SDWebImage](

[To see links please register here]

)
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through