Swift Async Load Image

How to load image asynchronously with Swift using UIImageViewExtension and preventing duplicate images or wrong Images loaded to cells

You are asynchronously updating your image view, regardless of whether the image view has been re-used for another cell.

When you start a new request for an image view, assuming you didn’t find an image in the cache immediately, before starting network request, you should (a) remove any prior image (like Brandon suggested); (b) possibly load a placeholder image or UIActivityIndicatorView; and (c) cancel any prior image request for that image view. Only then should you start a new request.

In terms of how you save a reference to the prior request in an extension, you can’t add stored properties, but you can use objc_setAssociatedObject to save the session task when you start the session, set it to nil when the request finishes, and objc_getAssociatedObject when retrieving the session object to see if you need to cancel the prior one.

(Incidentally, Kingfisher wraps this associated object logic in their computed property for the task identifier. This is a fine way to save and retrieve this task identifier.


In terms of failed requests, the fact that you are performing unbridled image requests could cause that problem. Scroll around a bit and your requests will get backlogged and timeout. Doing the cancelation (see above) will diminish that problem, but it might still eventually happen. If you continue to have requests fail after fixing the above, then you might need to constrain the number of concurrent requests. A common solution is to wrap requests in asynchronous Operation subclass and add them to OperationQueue with a maxConcurrentOperationCount. Or if you’re looking for a cheap and cheerful solution, you could try bumping up the timeout threshold in your requests.

Loading/Downloading image from URL on Swift

Xcode 8 or later • Swift 3 or later

Synchronously:

if let filePath = Bundle.main.path(forResource: "imageName", ofType: "jpg"), let image = UIImage(contentsOfFile: filePath) {
imageView.contentMode = .scaleAspectFit
imageView.image = image
}

Asynchronously:

Create a method with a completion handler to get the image data from your url

func getData(from url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> ()) {
URLSession.shared.dataTask(with: url, completionHandler: completion).resume()
}

Create a method to download the image (start the task)

func downloadImage(from url: URL) {
print("Download Started")
getData(from: url) { data, response, error in
guard let data = data, error == nil else { return }
print(response?.suggestedFilename ?? url.lastPathComponent)
print("Download Finished")
// always update the UI from the main thread
DispatchQueue.main.async() { [weak self] in
self?.imageView.image = UIImage(data: data)
}
}
}

Usage:

override func viewDidLoad() {
super.viewDidLoad()
print("Begin of code")
let url = URL(string: "https://cdn.arstechnica.net/wp-content/uploads/2018/06/macOS-Mojave-Dynamic-Wallpaper-transition.jpg")!
downloadImage(from: url)
print("End of code. The image will continue downloading in the background and it will be loaded when it ends.")
}

Extension:

extension UIImageView {
func downloaded(from url: URL, contentMode mode: ContentMode = .scaleAspectFit) {
contentMode = mode
URLSession.shared.dataTask(with: url) { data, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else { return }
DispatchQueue.main.async() { [weak self] in
self?.image = image
}
}.resume()
}
func downloaded(from link: String, contentMode mode: ContentMode = .scaleAspectFit) {
guard let url = URL(string: link) else { return }
downloaded(from: url, contentMode: mode)
}
}

Usage:

imageView.downloaded(from: "https://cdn.arstechnica.net/wp-content/uploads/2018/06/macOS-Mojave-Dynamic-Wallpaper-transition.jpg")

Asynchronous Image Loading Bug

Okay so I created a new solution to patch this issue up and I am very glad that this little work around is working. So, I started looking at what I could do server side to optimize content being pulled into the app. What I did was use PHP to encode the image with base64, then the app pulls it in and decodes the base64 data and the image populates with lightning speed!

Server Side code:

$article['image'] = base64_encode(file_get_contents(PATH . "/cache/content/topstory/" . $row['app_article']));

Client Side code:

let dataDecoded:NSData = NSData(base64Encoded: result.image, options: NSData.Base64DecodingOptions(rawValue: 0))!
let decodedimage:UIImage = UIImage(data: dataDecoded as Data)!
Image(uiImage: decodedimage)
.frame(height: 250)
.aspectRatio(contentMode: .fill)

How to asynchronous load image from a web-server in UICollectionView using NSCache

Try this one it's Working code (Swift 4).

func NKPlaceholderImage(image:UIImage?, imageView:UIImageView?,imgUrl:String,compate:@escaping (UIImage?) -> Void){

if image != nil && imageView != nil {
imageView!.image = image!
}

var urlcatch = imgUrl.replacingOccurrences(of: "/", with: "#")
let documentpath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
urlcatch = documentpath + "/" + "\(urlcatch)"

let image = UIImage(contentsOfFile:urlcatch)
if image != nil && imageView != nil
{
imageView!.image = image!
compate(image)

}else{

if let url = URL(string: imgUrl){

DispatchQueue.global(qos: .background).async {
() -> Void in
let imgdata = NSData(contentsOf: url)
DispatchQueue.main.async {
() -> Void in
imgdata?.write(toFile: urlcatch, atomically: true)
let image = UIImage(contentsOfFile:urlcatch)
compate(image)
if image != nil {
if imageView != nil {
imageView!.image = image!
}
}
}
}
}
}
}

Use Like this :

// Here imgPicture = your imageView and UIImage(named: "placeholder") is Display image brfore download actual image.  
imgPicture.image = nil
NKPlaceholderImage(image: UIImage(named: "placeholder"), imageView: imgPicture, imgUrl: "Put Here your server image Url Sting") { (image) in }

Loading an image into UIImage asynchronously

Take a look at SDWebImage:

https://github.com/rs/SDWebImage

It's a fantastic set of classes that handle everything for you.

Tim

what should I do to load asynchronous images in tableView?

I think you have 2 options

  • You download image async when cell visible ( I recommend )

  • Download all images and show cell visible

If you are download all images increase your memory usage of app and if too much usage it, iOS will crash your app.

First path:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if let logo = arrFavServices[indexPath.section]?.logo {
// We need download image here
cell.imageView?.downloaded(from: logo, contentMode: .center)
}

// Configure the cell...
if let color = arrFavServices[indexPath.section]?.color {
cell.backgroundColor = UIColor(hexString: color)
}
return cell
}

Second Path:

You can use dispatch group. UITableView is waiting for download all images.

// Cache array
var downloadedImages: [UIImage] = []

// Create an instance
var dispatchGroup = DispatchGroup()


func loadImages() {

// Every tick of loop, we enter the group
for img in arrFavServices {

// Join the group
dispatchGroup.enter()
if let url = img?.logo {
let imgDownload = UIImageView()
imgDownload.downloaded(from: url, contentMode: .redraw, completion: { [weak self] downloadedImage in
guard let self = self else { return }
self.downloadedImages.append(downloadedImage)

// And leave group when task is done
dispatchGroup.leave()

})
} else {
let imgDownload = UIImageView()
imgDownload.image = UIImage(named: "logo")
arrImages.append(imgDownload)

// We can leave here too because we add image to array
dispatchGroup.leave()
}

}

// We have to listen group, and that we update tableView or UI todo
dispatchGroup.notify(queue: .main) {
self.tableView.reloadData()
self.tableView.layoutIfNeeded()
self.tableView.layoutSubviews()
self.utilActivityIndicator.shared.hideLoader(view: self.view)
}
}

You can set completion handler like below

extension UIImageView {
func downloaded(from url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFit, completion: ((UIImage) -> Void)?) {
contentMode = mode
URLSession.shared.dataTask(with: url) { data, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else { return }
DispatchQueue.main.async() {
completion?(image)
}
}.resume()
}
func downloaded(from link: String, contentMode mode: UIView.ContentMode = .scaleAspectFit, completion: ((UIImage) -> Void)?) {
guard let url = URL(string: link) else { return }
downloaded(from: url, contentMode: mode, completion: completion)
}
}

Load images async in SwiftUI with DispatchQueue

Take a look at some blog posts about asynchronous image loading in SwiftUI. This one for instance looks like it describes the technique you need to use perfectly.

In a nutshell: put a custom View in your hierarchy that handles downloading on a background thread and then updates the image when the view has been downloaded.

There are Cocoapods available as well, like this one for instance.

MessageKit - How to async load images in subclass of MessagesViewController?

I have since learnt that MessageKit provides a delegate method in MessagesDisplayDelegate called

func configureMediaMessageImageView(_ imageView: UIImageView,
for message: MessageType,
at indexPath: IndexPath,
in messagesCollectionView: MessagesCollectionView)

We can asynchronously load images for given message with this delegate method.

I am using a UIImageView extension to help with this

import UIKit

extension UIImageView {
func load(url: URL) {
DispatchQueue.global().async { [weak self] in
if let data = try? Data(contentsOf: url) {
if let image = UIImage(data: data) {
DispatchQueue.main.async {
self?.image = image
}
}
}
}
}
}

So the solution is as follows

func configureMediaMessageImageView(_ imageView: UIImageView,
for message: MessageType,
at indexPath: IndexPath,
in messagesCollectionView: MessagesCollectionView) {
/*acquire url for the image in my case i had a
custom type Message which stored the image url */
guard
let msg = message as? Message,
let url = msg.downloadURL
else { return }
imageView.load(url: url)
}


Related Topics



Leave a reply



Submit