tableView cell is displaying the wrong image sometimes? - ios

I have a tableView that displays an image in the cell. Most of the time the correct image will be displayed, however occasionally it will display the wrong image (usually if scrolling down the tableView very quickly). I download the images asynchronously and store them in a cache. Can't find what else could be causing the issue?? Below is the code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// try to reuse cell
let cell:CustomCell = tableView.dequeueReusableCellWithIdentifier("DealCell") as CustomCell
// get the venue image
let currentVenueImage = deals[indexPath.row].venueImageID
let unwrappedVenueImage = currentVenueImage
var venueImage = self.venueImageCache[unwrappedVenueImage]
let venueImageUrl = NSURL(string: "http://notrealsite.com/restaurants/\(unwrappedVenueImage)/photo")
// reset reused cell image to placeholder
cell.venueImage.image = UIImage(named: "placeholder venue")
// async image
if venueImage == nil {
let request: NSURLRequest = NSURLRequest(URL: venueImageUrl!)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
if error == nil {
venueImage = UIImage(data: data)
self.venueImageCache[unwrappedVenueImage] = venueImage
dispatch_async(dispatch_get_main_queue(), {
// fade image in
cell.venueImage.alpha = 0
cell.venueImage.image = venueImage
cell.venueImage.fadeIn()
})
}
else {
}
})
}
else{
cell.venueImage.image = venueImage
}
return cell
}

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 for ensuring that it's right cell
if let updateCell = tableView.cellForRow(at: indexPath) {
updateCell.imageView.image = image
}
}
return cell
}

I think the issue is with sendAsynchronousRequest. If you are scrolling faster than this is taking, when you reuse a cell, you can end up with the old completionHandler replacing the "wrong" cell (since it's now showing a different entry). You need to check in the completion handler that it's still the image you want to show.

So after some of the previous answers pointing me in the right direction, this is the code I added, which seems to have done the trick. The images all load and are displayed as they should now.
dispatch_async(dispatch_get_main_queue(), {
// check if the cell is still on screen, and only if it is, update the image.
let updateCell = tableView .cellForRowAtIndexPath(indexPath)
if updateCell != nil {
// fade image in
cell.venueImage.alpha = 0
cell.venueImage.image = venueImage
cell.venueImage.fadeIn()
}
})

This is the problem with dequeued re-usable cell. Inside the image download completion method, you should check whether this downloaded image is for correct index-path. You need to store a mapping data-structure that stores the index-path and a corresponding url. Once the download completes, you need to check whether this url belongs to current indexpath, otherwise load the cell for that downloaded-indexpath and set the image.

The following code changes worked me.
You can download the image in advance and save it in the application directory which is not accessible by the user. You get these images from the application directory in your tableview.
// Getting images in advance
var paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String
var dirPath = paths.stringByAppendingPathComponent("XYZ/")
var imagePath = paths.stringByAppendingPathComponent("XYZ/\(ImageName)" )
println(imagePath)
var checkImage = NSFileManager.defaultManager()
if checkImage.fileExistsAtPath(imagePath) {
println("Image already exists in application Local")
} else {
println("Getting Image from Remote")
checkImage.createDirectoryAtPath(dirPath, withIntermediateDirectories: true, attributes: nil, error: nil)
let request: NSURLRequest = NSURLRequest(URL: NSURL(string: urldata as! String)!)
let mainQueue = NSOperationQueue.mainQueue()
NSURLConnection.sendAsynchronousRequest(request, queue: mainQueue, completionHandler: { (response, data, error) -> Void in
if let httpResponse = response as? NSHTTPURLResponse {
// println("Status Code for successful------------------------------------>\(httpResponse.statusCode)")
if (httpResponse.statusCode == 502) {
//Image not found in the URL, I am adding default image
self.logoimg.image = UIImage(named: "default.png")
println("Image not found in the URL")
} else if (httpResponse.statusCode == 404) {
//Image not found in the URL, I am adding default image
self.logoimg.image = UIImage(named: "default.png")
println("Image not found in the URL")
} else if (httpResponse.statusCode != 404) {
// Convert the downloaded data in to a UIImage object
let image = UIImage(data: data)
// Store the image in to our cache
UIImagePNGRepresentation(UIImage(data: data)).writeToFile(imagePath, atomically: true)
dispatch_async(dispatch_get_main_queue(), {
self.logoimg.contentMode = UIViewContentMode.ScaleAspectFit
self.logoimg.image = UIImage(data: data)
println("Image added successfully")
})
}
}
})
}
// Code in cellForRowAtIndexPath
var checkImage = NSFileManager.defaultManager()
if checkImage.fileExistsAtPath(imagePath) {
println("Getting Image from application directory")
let getImage = UIImage(contentsOfFile: imagePath)
imageView.backgroundColor = UIColor.whiteColor()
imageView.image = nil
imageView.image = getImage
imageView.frame = CGRectMake(xOffset, CGFloat(4), CGFloat(30), CGFloat(30))
cell.contentView.addSubview(imageView)
} else {
println("Default image")
imageView.backgroundColor = UIColor.whiteColor()
imageView.image = UIImage(named: "default.png")!
imageView.frame = CGRectMake(xOffset, CGFloat(4), CGFloat(30), CGFloat(30))
cell.contentView.addSubview(imageView)
}

Swift 5
So a simple solution to your problem would be by creating a custom class which subclasses UIImageView.
Add a property to store the url string.
Initially set the image to your placeholder to stop flickering.
While parsing and setting the image from response data compare class property url string with the url string passed through to the function and make sure they are equal.
Cache your image with the key as the url string and retrieve accordingly.
Note: Do not extend UIImageview as we plan to add property imageUrl.
reference: https://www.youtube.com/watch?v=XFvs6eraBXM
let imageCache = NSCache<NSString, UIImage>()
class CustomIV: UIImageView {
var imageUrl: String?
func loadImage(urlStr: String) {
imageUrl = urlStr
image = UIImage(named: "placeholder venue")
if let img = imageCache.object(forKey: NSString(string: imageUrl!)) {
image = img
return
}
guard let url = URL(string: urlStr) else {return}
imageUrl = urlStr
URLSession.shared.dataTask(with: url) { data, response, error in
if let err = error {
print(err)
} else {
DispatchQueue.main.async {
let tempImg = UIImage(data: data!)
if self.imageUrl == urlStr {
self.image = tempImg
}
imageCache.setObject(tempImg!, forKey: NSString(string: urlStr))
}
}
}.resume()
}
}
Just update you tableview cell's imageview to a CustomIV object.
And then update the image using:
cell.venueImage.loadImage(urlStr: "http://notrealsite.com/restaurants/\(unwrappedVenueImage)/photo")

Related

TableView with labels, images, gifs and video hangs / gets stuck incorrect while fetch from firestore in iOS, Swift

I have tableview with label, imageView (for image, gif & video thumbnail). I am sure that doing something wrong and I can't handle its completion handler due to which the app is hanged and gets stuck for a long time.
My model is like,
struct PostiisCollection {
var id :String?
var userID: String?
var leadDetails : NSDictionary?
var company: NSDictionary?
var content: String?
init(Doc: DocumentSnapshot) {
self.id = Doc.documentID
self.userID = Doc.get("userID") as? String ?? ""
self.leadDetails = Doc.get("postiiDetails") as? NSDictionary
self.company = Doc.get("company") as? NSDictionary
self.content = Doc.get("content") as? String ?? ""
}
}
I wrote in my view controller for fetch this,
var postiisCollectionDetails = [PostiisCollection]()
override func viewDidLoad() {
super.viewDidLoad()
let docRef = Firestore.firestore().collection("PostiisCollection").whereField("accessType", isEqualTo: "all_access")
docRef.getDocuments { (querysnapshot, error) in
if let doc = querysnapshot?.documents, !doc.isEmpty {
print("Document is present.")
for document in querysnapshot!.documents {
_ = document.documentID
if let compCode = document.get("company") as? NSDictionary {
do {
let jsonData = try JSONSerialization.data(withJSONObject: compCode)
let companyPost: Company = try! JSONDecoder().decode(Company.self, from: jsonData)
if companyPost.companyCode == AuthService.instance.companyId ?? ""{
print(AuthService.instance.companyId ?? "")
if (document.get("postiiDetails") as? NSDictionary) != nil {
let commentItem = PostiisCollection(Doc: document)
self.postiisCollectionDetails.append(commentItem)
}
}
} catch {
print(error.localizedDescription)
}
DispatchQueue.main.async {
self.tableView.isHidden = false
self.tableView.reloadData()
}
}
}
}
}
}
I need to check for the index path with image view is either image or gif or video with different parameters, I tried with tableview delegate and datasource method by,
extension AllAccessPostiiVC: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return postiisCollectionDetails.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "AllAccessCell", for: indexPath)
let label1 = cell.viewWithTag(1) as? UILabel
let imagePointer = cell.viewWithTag(3) as? UIImageView
let getGif = arrPostiisCollectionFilter[indexPath.row].leadDetails?.value(forKey: "gif") as? NSArray
let getPhoto = arrPostiisCollectionFilter[indexPath.row].leadDetails?.value(forKey: "photo") as? NSArray
let getVideo = arrPostiisCollectionFilter[indexPath.row].leadDetails?.value(forKey: "video") as? NSArray
label1?.text = "\(arrPostiisCollectionFilter[indexPath.row].leadDetails?.value(forKey: "title"))"
if getGif != nil {
let arrGif = getGif?.value(forKey: "gifUrl") as! [String]
print(arrGif[0])
let gifURL : String = "\(arrGif[0])"
let imageURL = UIImage.gifImageWithURL(gifURL)
imagePointer?.image = imageURL
playButton?.isHidden = true
}
if getPhoto != nil {
let arrPhoto = getPhoto?.value(forKey: "photoUrl") as! [String]
print(arrPhoto[0])
let storageRef = Storage.storage().reference(forURL: arrPhoto[0])
storageRef.downloadURL(completion: { (url, error) in
do {
let data = try Data(contentsOf: url!)
let image = UIImage(data: data as Data)
DispatchQueue.main.async {
imagePointer?.image = image
playButton?.isHidden = true
}
} catch {
print(error)
}
})
}
if getVideo != nil {
let arrVideo = getVideo?.value(forKey: "videoUrl") as! [String]
let videoURL = URL(string: arrVideo[0])
let asset = AVAsset(url:videoURL!)
if let videoThumbnail = asset.videoThumbnail{
SVProgressHUD.dismiss()
imagePointer?.image = videoThumbnail
playButton?.isHidden = false
}
}
}
}
If I run, the app hangs in this page and data load time is getting more, some cases the preview image is wrongly displayed and not able to handle its completion
As others have mentioned in the comments, you are better of not performing the background loading in cellFroRowAtIndexPath.
Instead, it's better practice to add a new method fetchData(), where you perform all the server interaction.
So for example:
// Add instance variables for fast access to data
private var images = [UIImage]()
private var thumbnails = [UIImage]()
private func fetchData(completion: ()->()) {
// Load storage URLs
var storageURLs = ...
// Load data from firebase
let storageRef = Storage.storage().reference(forURL: arrPhoto[0])
storageRef.downloadURL(completion: { (url, error) in
// Parse data and store resulting image in image array
...
// Call completion handler to indicate that loading has finished
completion()
})
}
Now you can call fetchData() whenever you would like to refresh data and call tableview.reloadData() within the completion handler. That of course must be done on the main thread.
This approach simplifies your cellForRowAtIndexPath method.
There you can just say:
imagePointer?.image = ...Correct image from image array...
Without any background loading.
I suggest using below lightweight extension for image downloading from URL
using NSCache
extension UIImageView {
func downloadImage(urlString: String, success: ((_ image: UIImage?) -> Void)? = nil, failure: ((String) -> Void)? = nil) {
let imageCache = NSCache<NSString, UIImage>()
DispatchQueue.main.async {[weak self] in
self?.image = nil
}
if let image = imageCache.object(forKey: urlString as NSString) {
DispatchQueue.main.async {[weak self] in
self?.image = image
}
success?(image)
} else {
guard let url = URL(string: urlString) else {
print("failed to create url")
return
}
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) {(data, response, error) in
// response received, now switch back to main queue
DispatchQueue.main.async {[weak self] in
if let error = error {
failure?(error.localizedDescription)
}
else if let data = data, let image = UIImage(data: data) {
imageCache.setObject(image, forKey: url.absoluteString as NSString)
self?.image = image
success?(image)
} else {
failure?("Image not available")
}
}
}
task.resume()
}
}
}
Usage:
let path = "https://i.stack.imgur.com/o5YNI.jpg"
let imageView = UIImageView() // your imageView, which will download image
imageView.downloadImage(urlString: path)
No need to put imageView.downloadImage(urlString: path) in mainQueue, its handled in extension
In your case:
You can implement following logic in cellForRowAt method
if getGif != nil {
let arrGif = getGif?.value(forKey: "gifUrl") as! [String]
let urlString : String = "\(arrGif[0])"
let image = UIImage.gifImageWithURL(urlString)
imagePointer?.image = image
playButton?.isHidden = true
}
else if getPhoto != nil {
let arrPhoto = getPhoto?.value(forKey: "photoUrl") as! [String]
let urlString = Storage.storage().reference(forURL: arrPhoto[0])
imagePointer?.downloadImage(urlString: urlString)
playButton?.isHidden = true
}
elseif getVideo != nil {
let arrVideo = getVideo?.value(forKey: "videoUrl") as! [String]
let urlString = arrVideo[0]
imagePointer?.downloadImage(urlString: urlString)
playButton?.isHidden = false
}
If you have one imageView to reload in tableView for photo, video and gif. then use one image array to store it prior before reloading. So that your main issue of hang or stuck will be resolved. Here the main issue is each time in table view cell collection data is being called and checked while scrolling.
Now I suggest to get all photo, gifs and video (thumbnail) as one single array prior to table view reload try this,
var cacheImages = [UIImage]()
private func fetchData(completionBlock: () -> ()) {
for (index, _) in postiisCollectionDetails.enumerated() {
let getGif = postiisCollectionDetails[index].leadDetails?.value(forKey: "gif") as? NSArray
let getPhoto = postiisCollectionDetails[index].leadDetails?.value(forKey: "photo") as? NSArray
let getVideo = postiisCollectionDetails[index].leadDetails?.value(forKey: "video") as? NSArray
if getGif != nil {
let arrGif = getGif?.value(forKey: "gifUrl") as! [String]
let gifURL : String = "\(arrGif[0])"
self.randomList.append(gifURL)
/////---------------------------
let imageURL = UIImage.gifImageWithURL(gifURL)
self.cacheImages.append(imageURL!)
//////=================
}
else if getVideo != nil {
let arrVideo = getVideo?.value(forKey: "videoUrl") as! [String]
let videoURL: String = "\(arrVideo[0])"
let videoUrl = URL(string: arrVideo[0])
let asset = AVAsset(url:videoUrl!)
if let videoThumbnail = asset.videoThumbnail{
////--------------
self.cacheImages.append(videoThumbnail)
//-----------
}
self.randomList.append(videoURL)
}else if getPhoto != nil {
let arrPhoto = getPhoto?.value(forKey: "photoUrl") as! [String]
let photoURL : String = "\(arrPhoto[0])"
/////---------------------------
let url = URL(string: photoURL)
let data = try? Data(contentsOf: url!)
if let imageData = data {
let image = UIImage(data: imageData)
if image != nil {
self.cacheImages.append(image!)
}
else {
let defaultImage: UIImage = UIImage(named:"edit-user-80")!
self.cacheImages.append(defaultImage)
}
}
//////=================
}
else {
//-----------------
let defaultImage: UIImage = UIImage(named:"edit-user-80")!
self.cacheImages.append(defaultImage)
//--------------------
}
}
completionBlock()
}
After getting all UIImage as array where loop is being called. Now you call this function inside your viewDidLoad. So after all values in images fetched then try to call tableView like this,
override func viewDidLoad() {
self.fetchData {
DispatchQueue.main.async
self.tableView.reloadData()
}
}
}
Now atlast, you may use SDWebImage or any other background image class or download method to call those in tableView cellforRow method,
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// your cell idetifier & other stuffs
if getVideo != nil {
imagePointer?.image = cacheImages[indexPath.row]
playButton?.isHidden = false
}else {
imagePointer?.image = cacheImages[indexPath.row]
// or get photo with string via SdWebImage
// imagePointer?.sd_setImage(with: URL(string: photoURL), placeholderImage: UIImage(named: "edit-user-80"))
playButton?.isHidden = true
}
return cell
}
You're handling data in a totally wrong manner. Data(contentsOf: url!) - This is wrong. You should chache the images and should download it to directory. When you convert something into data it takes place into the memory(ram) and it is not good idea when playing with large files. You should use SDWebImage kind of library to set images to imageview.
Second thing if let videoThumbnail = asset.videoThumbnail - This is also wrong. Why you're creating assets and then getting thumbnail from it? You should have separate URL for the thumbnail image for your all videos in the response of the API and then again you can use SDWebImage to load that thumbnail.
You can use SDWebImage for gif as well.
Alternative of SDWebImage is Kingfisher. Just go through both libraries and use whatever suitable for you.

UICollectionViewCell image reload issue after API call

I am trying to load image for the particular cell for whose indexpath the URL is present. Have function to download the image and send it through call back method, but after callback the other cells are also getting loaded by the downloaded image. Thanks in Adv.
Here is the code sample. In method cellForItemAtIndexPath
let stationCollectionViewcell : StationCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "stationCell", for: indexPath) as! StationCollectionViewCell
if imageURL.contains("http") {
self.loadImageWithURL(url: URL(string: station.imageURL)!) { (image) in
stationCollectionViewcell.radioImgView.image = image
}
} else if imageURL != "" {
stationCollectionViewcell.radioImgView.image = UIImage(named: "station-therockfm")
} else {
stationCollectionViewcell.radioImgView.image = UIImage(named: "stationImage")
}
And the function that will download the image
func loadImageWithURL(url: URL, callback: #escaping (UIImage) -> ()) {
print("This is getting excuted loadImageWithURL")
let session = URLSession.shared
let downloadTask = session.downloadTask(with: url, completionHandler: {
url, response, error in
if error == nil && url != nil {
if let data = NSData(contentsOf: url!) {
if let image = UIImage(data: data as Data) {
DispatchQueue.main.async(execute: {
callback(image)
})
}
}
}
})
downloadTask.resume()
}
You should reset image before reusing cell, because it is reused with previous image before new is downloaded. Also, you should compare saved url with url in callback, because callback may return when cell is reused.
// reset image
override func prepareForReuse() {
super.prepareForReuse()
radioImgView.image = nil // set nil or default image
}
Add url to callback
func loadImageWithURL(url: URL, callback: #escaping (UIImage, URL) -> ()) {
print("This is getting excuted loadImageWithURL")
let session = URLSession.shared
let downloadTask = session.downloadTask(with: url, completionHandler: {
url, response, error in
if error == nil && url != nil {
if let data = NSData(contentsOf: url!) {
if let image = UIImage(data: data as Data) {
DispatchQueue.main.async(execute: {
callback(image, url!)
})
}
}
}
})
downloadTask.resume()
}
Compare url
let stationCollectionViewcell : StationCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "stationCell", for: indexPath) as! StationCollectionViewCell
if imageURL.contains("http") {
let url = URL(string: station.imageURL)!
self.loadImageWithURL(url: url) { (image, callbackUrl) in
guard url == callbackUrl else { return }
stationCollectionViewcell.radioImgView.image = image
}
} else if imageURL != "" {
stationCollectionViewcell.radioImgView.image = UIImage(named: "station-therockfm")
} else {
stationCollectionViewcell.radioImgView.image = UIImage(named: "stationImage")
}
I hope, it will run Fine.
import UIKit
let imageCache = NSCache<AnyObject, AnyObject>()
class CustomImageView : UIImageView {
var imgUrlString : String?
func loadImageWithURL(urlString : String) {
imgUrlString = urlString
guard let url = URL(string: urlString) else { return }
image = nil
if let imgFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = imgFromCache
return
}
URLSession.shared.dataTask(with: url) { (data, resposne, error) in
if error != nil {
print(error)
return
}
DispatchQueue.main.sync {
let imageToCache = UIImage(data: data!)
imageCache.setObject(imageToCache!, forKey: urlString as AnyObject)
if self.imgUrlString == urlString {
self.image = imageToCache
}
}
}.resume()
}
}
Intialize your instance with this Class.
let thumbnailImageView : CustomImageView = {
let imgView = CustomImageView()
imgView.translatesAutoresizingMaskIntoConstraints = false
imgView.image = #imageLiteral(resourceName: "taylor_swift_blank_space")
imgView.contentMode = .scaleAspectFill
imgView.clipsToBounds = true
return imgView
}()
Cells are reusing each time, also you have async call in the controller. In the downloading completion, you need to reload cell by IndexPath.
Also, reset image in prepareForReuse
override func prepareForReuse() {
super.prepareForReuse()
stationCollectionViewcell.radioImgView.image = nil
}
The best solution with async images it's to have cache
let imageCache: NSCache<NSString, UIImage> = NSCache<NSString, UIImage>()
after loading has been completed save the image:
imageCache.setObject(image, forKey: url as NSString)
and next time, when cell appears, check is image already exists in imageCache
if yes, use image from the cache, if not, download an image
Code should looks like this:
if imageURL.contains("http") {
if let image = imageCache.object(forKey: station.imageURL as NSString) {
stationCollectionViewcell.radioImgView.image = image
} else {
self.loadImageWithURL(url: URL(string: station.imageURL)!) { [weak self] (image) in
self?.imageCache.setObject(image, forKey: url as NSString)
self?.collectionView.collectionView.reloadItems(at: [indexPath])
}
}
} else if imageURL != "" {
stationCollectionViewcell.radioImgView.image = UIImage(named: "station-therockfm")
} else {
stationCollectionViewcell.radioImgView.image = UIImage(named: "stationImage")
}

Loading images in tableview asynch

I run this code as an extension to imageView to load image to cells.the problem is images are loading to cells but all cells image are invisible or hidden till I click on one of them after that all cells images are shown !
extension UIImageView {
func downloadImageFrom(link:String, contentMode: UIViewContentMode) {
URLSession.shared.dataTask( with: NSURL(string:link)! as URL, completionHandler: {
(data, response, error) -> Void in
DispatchQueue.main.async {
self.contentMode = contentMode
if let data = data { self.image = UIImage(data: data) }
}
}).resume()
}
}
and in the cellforRowAt :
cell.imageView?.downloadImageFrom (link)
There is less info posted on question still I can assume following may be issue
-> Your cell has UIImageView and you are downloading image but after image download how you are notifying cell ? ,
-> Since tableview reuse cells and your imageview will also been re used by tableview cell in your code you are just downloading image data and set to UIImageView that will create problem in future
TIP:
1) Use disk or memory caching ,
2) Use placeholder image while your cell is downloading image
3) Use Completion handler (closure) to notify your cell when you have finished downloading
4) You can use third party library which is very ease to use like AlamofireImage or SDWebImage
You can use following code : -
extension UIImageView {
static let cacheData = NSCache<AnyObject, AnyObject>()
func downloadedFrom(link: String, placeHolder:String = "placeholder",isFromCache:Bool = true,isIndicator:Bool = true,isAppendBaseUrl:Bool = true) {
let placeHolderImage = UIImage.init(named: placeHolder);
self.contentMode = UIViewContentMode.scaleAspectFill
self.clipsToBounds = true;
self.image=placeHolderImage;
var strlink = ""
if isAppendBaseUrl
{
strlink = "Your base url" + link
}
else
{
strlink = link
}
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.whiteLarge)
guard let url = URL(string: strlink) else
{
return
}
if isIndicator
{
activityIndicator.center = CGPoint(x: self.bounds.size.width/2, y: self.bounds.size.height/2)
activityIndicator.color = UIColor.white
self.addSubview(activityIndicator)
activityIndicator.startAnimating()
}
if isFromCache
{
if let cachedImage = UIImageView.cacheData.object(forKey: url as AnyObject) as? UIImage
{
if isIndicator
{
activityIndicator.stopAnimating()
}
self.image = cachedImage
}
else
{
self.image = placeHolderImage
let urlStr = strlink
let url = URL(string: urlStr)
let request: URLRequest = URLRequest(url: url!)
URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
if error != nil
{
print(error!)
return
}
DispatchQueue.main.async(execute: { () -> Void in
let image = UIImage(data: data!)
if isIndicator
{
activityIndicator.stopAnimating()
activityIndicator.removeFromSuperview();
}
if (image != nil)
{
self.image = image
UIImageView.cacheData.setObject(image!, forKey: url as AnyObject)
}
})
}).resume()
}
}
else
{
self.image=placeHolderImage;
let urlStr = strlink
let url = URL(string: urlStr)
var request: URLRequest = URLRequest(url: url!)
request.setValue("xyz", forHTTPHeaderField:"DeviceId")
URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
if error != nil
{
print(error!)
return
}
DispatchQueue.main.async(execute: { () -> Void in
let image = UIImage(data: data!)
if isIndicator
{
activityIndicator.stopAnimating()
activityIndicator.removeFromSuperview();
}
if (image != nil)
{
self.image = image
UIImageView.cacheData.setObject(image!, forKey: url as AnyObject)
}
})
}).resume()}
}}
I solved my problem by adding a subview in table cells and hold images
Try this, it works for me. May be forgot to specify a method super.viewDidLoad() or super.viewDidAppear(true)...
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "idFriendCell", for: indexPath) as! FriendCell
cell.fotoFriend.downloadImageFrom(link: "http://foto.jpg")
return cell
}
extension UIImageView {
func downloadImageFrom(link: String) {
URLSession.shared.dataTask(with: URL(string: link)!) { (data, response, error) in
DispatchQueue.main.async {
guard let data = data else { return }
self.image = UIImage(data: data)
}
}.resume()
}
}

NSCache Doesn't work with all images when loading for the first time

I'm woking on a project in swift 3.0 where I cache the response from the server by using NSCache as to populate them in a UITableView. However for some reason I'm only seeing few images loading when the app loads for the first time, but if If i scroll and come back I see everything (end of retrieving the response from the server I reload my tableview too, but seems that not the case). I'm not sure what I''m exactly missing here, the code as bellow as to show how I cache the images.
let imageCache = NSCache<AnyObject, AnyObject>()
var imageURLString : String?
extension UIImageView {
public func imageFromServerURL(urlString: String) {
imageURLString = urlString
if let url = URL(string: urlString) {
image = nil
if let imageFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = imageFromCache
return
}
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if error != nil{
print(error as Any)
return
}
DispatchQueue.main.async(execute: {
if let imgaeToCache = UIImage(data: data!){
if imageURLString == urlString {
self.image = imgaeToCache
}
imageCache.setObject(imgaeToCache, forKey: urlString as AnyObject)// calls when scrolling
}
})
}) .resume()
}
}
}
I think this would be a better approach using subclassing rather than extension, (taking help from Jageen's comment, as we cannot contain stored properties inside extension so we use the idea of encapsulation)
let imageCache = NSCache<AnyObject, AnyObject>()
class CustomImageView: UIImageView {
var imageUrlString: String?
func loadImageUsingUrlString(_ urlString: String) {
let url = URL(string: urlString)
imageUrlString = urlString
image = nil
if let imageFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = imageFromCache
return
}
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print(error!)
return
}
DispatchQueue.main.async {
let imageToCache = UIImage(data: data!)
if self.imageUrlString == urlString {
self.image = imageToCache
}
imageCache.setObject(imageToCache!, forKey: urlString as AnyObject)
}
}.resume()
}
}
-Now use this subclass as the type of imageViews that you are showing on the screen
Here the images are downloading and stored in cache just fine. The problem lies in the updation of tableview cells.
When the table view is loading the cells on to the table the images are not downloaded yet. But once the image is downloaded we have to selectively update the cell so that the image is displayed instantly.
Since you are scrolling , the tableview calls 'cellForRowatIndexpath' again which updates the cell showing the downloaded images while scrolling.
If you still wish to use the extension , I suggest you add the tableView and indexpath as the parameters so that we can call reload specific row and have the view updated instantly.
I have updated the table reload code and structure of the function defined in extension. Let me know how it goes.
let imageCache = NSCache<AnyObject, AnyObject>()
var imageURLString : String?
extension UIImageView {
public func imageFromServerURL(urlString: String, tableView : UITableView, indexpath : IndexPath)) {
imageURLString = urlString
if let url = URL(string: urlString) {
image = nil
if let imageFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = imageFromCache
return
}
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if error != nil{
print(error as Any)
return
}
DispatchQueue.main.async(execute: {
if let imgaeToCache = UIImage(data: data!){
if imageURLString == urlString {
self.image = imgaeToCache
}
imageCache.setObject(imgaeToCache, forKey: urlString as AnyObject)// calls when scrolling
tableView.reloadRows(at: [indexpath], with: .automatic)
}
})
}) .resume()
}
}
Saving Images in UIImageView Swift 5 with Xcode 14.1 and above through URLCache :-
class CacheImageView: UIImageView {
let cachesURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
var diskCacheURL:URL {
self.cachesURL.appendingPathComponent("DownloadCache")
}
var cache:URLCache {
URLCache(memoryCapacity: 10_000_000, diskCapacity: 1_000_000_000, directory: diskCacheURL)
}
var session:URLSession {
let config = URLSessionConfiguration.default
config.urlCache = cache
return URLSession(configuration: config)
}
func downloadImageFrom(urlString: String, imageMode: UIView.ContentMode) {
guard let url = URL(string: urlString) else { return }
downloadImageFrom(url: url, imageMode: imageMode)
}
func downloadImageFrom(url: URL, imageMode: UIView.ContentMode) {
contentMode = imageMode
let req = URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad)
self.session.dataTask(with: req) { data, response, error in
guard let data = data, error == nil else { return }
DispatchQueue.main.async {
let imageToCache = UIImage(data: data)
self.image = imageToCache
}
}.resume()
}
}
Uses:
var imageViewAstronomy: CacheImageView = CacheImageView()
imageViewAstronomy.downloadImageFrom(urlString: yourStringUrlOfImage, imageMode: .scaleAspectFit)

TableView slow when adding images to cellForRowAtIndex [duplicate]

I'd like to load an image from a URL in my application, so I first tried with Objective-C and it worked, however, with Swift, I've a compilation error:
'imageWithData' is unavailable: use object construction 'UIImage(data:)'
My function:
#IBOutlet var imageView : UIImageView
override func viewDidLoad() {
super.viewDidLoad()
var url:NSURL = NSURL.URLWithString("http://myURL/ios8.png")
var data:NSData = NSData.dataWithContentsOfURL(url, options: nil, error: nil)
imageView.image = UIImage.imageWithData(data)// Error here
}
In Objective-C:
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *url = [NSURL URLWithString:(#"http://myURL/ios8.png")];
NSData *data = [NSData dataWithContentsOfURL:url];
_imageView.image = [UIImage imageWithData: data];
_labelURL.text = #"http://www.quentinroussat.fr/assets/img/iOS%20icon's%20Style/ios8.png";
}
Can someone please explain me why the imageWithData: doesn't work with Swift, and how can I solve the problem.
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")
(Swift 4 update)
To answer the original question directly, here's the swift equivalent of the posted Objective-C snippet.
let url = URL(string: image.url)
let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
imageView.image = UIImage(data: data!)
DISCLAIMER:
It's important to note that the Data(contentsOf:) method will download the contents of the url synchronously in the same thread the code is being executed, so do not invoke this in the main thread of your application.
An easy way to make the same code run asynchronously, not blocking the UI, is by using GCD:
let url = URL(string: image.url)
DispatchQueue.global().async {
let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
DispatchQueue.main.async {
imageView.image = UIImage(data: data!)
}
}
That said, in real life applications, if you want to have the best User Experience and avoid multiple downloads of the same image, you may want to also have them not only downloaded, but cached. There's already quite a few libraries that does that very seamless and they are all really easy to use. I personally recommend Kingfisher:
import Kingfisher
let url = URL(string: "url_of_your_image")
// this downloads the image asynchronously if it's not cached yet
imageView.kf.setImage(with: url)
And that's it
If you just want to load image (Asynchronously!) - just add this small extension to your swift code:
extension UIImageView {
public func imageFromUrl(urlString: String) {
if let url = NSURL(string: urlString) {
let request = NSURLRequest(URL: url)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {
(response: NSURLResponse?, data: NSData?, error: NSError?) -> Void in
if let imageData = data as NSData? {
self.image = UIImage(data: imageData)
}
}
}
}
}
And use it this way:
myImageView.imageFromUrl("https://robohash.org/123.png")
Xcode 12 • Swift 5
Leo Dabus's answer is awesome! I just wanted to provide an all-in-one function solution:
if let url = URL(string: "http://www.apple.com/euro/ios/ios8/a/generic/images/og.png") {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else { return }
DispatchQueue.main.async { /// execute on main thread
self.imageView.image = UIImage(data: data)
}
}
task.resume()
}
Swift 2.2 || Xcode 7.3
I got Amazing results!! with AlamofireImage swift library
It provides multiple features like:
Asynchronously download
Auto Purging Image Cache if memory warnings happen for the app
Image URL caching
Image Caching
Avoid Duplicate Downloads
and very easy to implement for your app
Step.1 Install pods
Alamofire 3.3.x
pod 'Alamofire'
AlamofireImage 2.4.x
pod 'AlamofireImage'
Step.2 import and Use
import Alamofire
import AlamofireImage
let downloadURL = NSURL(string: "http://cdn.sstatic.net/Sites/stackoverflow/company/Img/photos/big/6.jpg?v=f4b7c5fee820")!
imageView.af_setImageWithURL(downloadURL)
that's it!! it will take care everything
Great thanks to Alamofire guys, for making iDevelopers life easy ;)
Swift 4::
This will shows loader while loading the image.
You can use NSCache which store image temporarily
let imageCache = NSCache<NSString, UIImage>()
extension UIImageView {
func loadImageUsingCache(withUrl urlString : String) {
let url = URL(string: urlString)
if url == nil {return}
self.image = nil
// check cached image
if let cachedImage = imageCache.object(forKey: urlString as NSString) {
self.image = cachedImage
return
}
let activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView.init(activityIndicatorStyle: .gray)
addSubview(activityIndicator)
activityIndicator.startAnimating()
activityIndicator.center = self.center
// if not, download image from url
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
if error != nil {
print(error!)
return
}
DispatchQueue.main.async {
if let image = UIImage(data: data!) {
imageCache.setObject(image, forKey: urlString as NSString)
self.image = image
activityIndicator.removeFromSuperview()
}
}
}).resume()
}
}
Usage:-
truckImageView.loadImageUsingCache(withUrl: currentTruck.logoString)
swift 3 with error handling
let url = URL(string: arr[indexPath.row] as! String)
if url != nil {
DispatchQueue.global().async {
let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
DispatchQueue.main.async {
if data != nil {
cell.imgView.image = UIImage(data:data!)
}else{
cell.imgView.image = UIImage(named: "default.png")
}
}
}
}
With Extension
extension UIImageView {
func setCustomImage(_ imgURLString: String?) {
guard let imageURLString = imgURLString else {
self.image = UIImage(named: "default.png")
return
}
DispatchQueue.global().async { [weak self] in
let data = try? Data(contentsOf: URL(string: imageURLString)!)
DispatchQueue.main.async {
self?.image = data != nil ? UIImage(data: data!) : UIImage(named: "default.png")
}
}
}
}
Extension Usage
myImageView. setCustomImage("url")
With Cache support
let imageCache = NSCache<NSString, UIImage>()
extension UIImageView {
func loadImageUsingCacheWithURLString(_ URLString: String, placeHolder: UIImage?) {
self.image = nil
if let cachedImage = imageCache.object(forKey: NSString(string: URLString)) {
self.image = cachedImage
return
}
if let url = URL(string: URLString) {
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
//print("RESPONSE FROM API: \(response)")
if error != nil {
print("ERROR LOADING IMAGES FROM URL: \(String(describing: error))")
DispatchQueue.main.async { [weak self] in
self?.image = placeHolder
}
return
}
DispatchQueue.main.async { [weak self] in
if let data = data {
if let downloadedImage = UIImage(data: data) {
imageCache.setObject(downloadedImage, forKey: NSString(string: URLString))
self?.image = downloadedImage
}
}
}
}).resume()
}
}
}
I wrapped the code of the best answers to the question into a single, reusable class extending UIImageView, so you can directly use asynchronous loading UIImageViews in your storyboard (or create them from code).
Here is my class:
import Foundation
import UIKit
class UIImageViewAsync :UIImageView
{
override init()
{
super.init(frame: CGRect())
}
override init(frame:CGRect)
{
super.init(frame:frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func getDataFromUrl(url:String, completion: ((data: NSData?) -> Void)) {
NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: url)!) { (data, response, error) in
completion(data: NSData(data: data))
}.resume()
}
func downloadImage(url:String){
getDataFromUrl(url) { data in
dispatch_async(dispatch_get_main_queue()) {
self.contentMode = UIViewContentMode.ScaleAspectFill
self.image = UIImage(data: data!)
}
}
}
}
and here is how to use it:
imageView.downloadImage("http://www.image-server.com/myImage.jpg")
let url = NSURL.URLWithString("http://live-wallpaper.net/iphone/img/app/i/p/iphone-4s-wallpapers-mobile-backgrounds-dark_2466f886de3472ef1fa968033f1da3e1_raw_1087fae1932cec8837695934b7eb1250_raw.jpg");
var err: NSError?
var imageData :NSData = NSData.dataWithContentsOfURL(url,options: NSDataReadingOptions.DataReadingMappedIfSafe, error: &err)
var bgImage = UIImage(data:imageData)
FYI : For swift-2.0 Xcode7.0 beta2
extension UIImageView {
public func imageFromUrl(urlString: String) {
if let url = NSURL(string: urlString) {
let request = NSURLRequest(URL: url)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {
(response: NSURLResponse?, data: NSData?, error: NSError?) -> Void in
self.image = UIImage(data: data!)
}
}
}
}
Swift 4: A simple loader for small images (ex: thumbnails) that uses NSCache and always runs on the main thread:
class ImageLoader {
private static let cache = NSCache<NSString, NSData>()
class func image(for url: URL, completionHandler: #escaping(_ image: UIImage?) -> ()) {
DispatchQueue.global(qos: DispatchQoS.QoSClass.background).async {
if let data = self.cache.object(forKey: url.absoluteString as NSString) {
DispatchQueue.main.async { completionHandler(UIImage(data: data as Data)) }
return
}
guard let data = NSData(contentsOf: url) else {
DispatchQueue.main.async { completionHandler(nil) }
return
}
self.cache.setObject(data, forKey: url.absoluteString as NSString)
DispatchQueue.main.async { completionHandler(UIImage(data: data as Data)) }
}
}
}
Usage:
ImageLoader.image(for: imageURL) { image in
self.imageView.image = image
}
swift 5
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
}
}
}
}
}
}
for using
override func awakeFromNib() {
super.awakeFromNib()
if let url = URL(string:"<imageURLHere>"){
imgView.load(url: url)
}
}
You’ll want to do:
UIImage(data: data)
In Swift, they’ve replaced most Objective C factory methods with regular constructors.
See:
https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithObjective-CAPIs.html#//apple_ref/doc/uid/TP40014216-CH4-XID_26
Swift 2 with error Handle and custom request header
Simply add extension to UIImageView:
extension UIImageView {
public func imageFromUrl(urlString: String) {
if let url = NSURL(string: urlString) {
let request = NSMutableURLRequest(URL: url)
request.setValue("<YOUR_HEADER_VALUE>", forHTTPHeaderField: "<YOUR_HEADER_KEY>")
NSURLSession.sharedSession().dataTaskWithRequest(request) {
(data, response, error) in
guard let data = data where error == nil else{
NSLog("Image download error: \(error)")
return
}
if let httpResponse = response as? NSHTTPURLResponse{
if httpResponse.statusCode > 400 {
let errorMsg = NSString(data: data, encoding: NSUTF8StringEncoding)
NSLog("Image download error, statusCode: \(httpResponse.statusCode), error: \(errorMsg!)")
return
}
}
dispatch_async(dispatch_get_main_queue(), {
NSLog("Image download success")
self.image = UIImage(data: data)
})
}.resume()
}
}
}
And then, use the new imageFromUrl(urlString: String) to download image
Usage:
imageView.imageFromUrl("https://i.imgur.com/ONaprQV.png")
Swift 4
This method will download an image from a website asynchronously and cache it:
func getImageFromWeb(_ urlString: String, closure: #escaping (UIImage?) -> ()) {
guard let url = URL(string: urlString) else {
return closure(nil)
}
let task = URLSession(configuration: .default).dataTask(with: url) { (data, response, error) in
guard error == nil else {
print("error: \(String(describing: error))")
return closure(nil)
}
guard response != nil else {
print("no response")
return closure(nil)
}
guard data != nil else {
print("no data")
return closure(nil)
}
DispatchQueue.main.async {
closure(UIImage(data: data!))
}
}; task.resume()
}
In use:
getImageFromWeb("http://www.apple.com/euro/ios/ios8/a/generic/images/og.png") { (image) in
if let image = image {
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
imageView.image = image
self.view.addSubview(imageView)
} // if you use an Else statement, it will be in background
}
Kingfisher is one of the best library for load image into URL.
Github URL - https://github.com/onevcat/Kingfisher
// If you want to use Activity Indicator.
imageview_pic.kf.indicatorType = .activity
imageview_pic.kf.setImage(with: URL(string: "Give your url string"))
// If you want to use custom placeholder image.
imageview_pic.kf.setImage(with: URL(string: "Give your url string"), placeholder: UIImage(named: "placeholder image name"), options: nil, progressBlock: nil, completionHandler: nil)
Here is Working code for Loading / Downloading image from URL. NSCache automatically and Display Placeholder image before download and Load Actual image (Swift 4 | Swift 5 Code).
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
// UIImage(named: "placeholder") is Display image brfore download and load actual image.
NKPlaceholderImage(image: UIImage(named: "placeholder"), imageView: imgPicture, imgUrl: "Put Here your server image Url Sting") { (image) in }
a quick hack if you want to quickly check image from url
let imageURL = NSURL(string: "https://farm2.staticflickr.com/1591/26078338233_d1466b7da2_m.jpg")
let imagedData = NSData(contentsOfURL: imageURL!)!
imageView?.image = UIImage(data: imagedData)
I implemented within a tableview with a custom cell that has only a image
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCellWithIdentifier("theCell", forIndexPath: indexPath) as! customTableViewCell
let imageURL = NSURL(string: "https://farm2.staticflickr.com/1591/26078338233_d1466b7da2_m.jpg")
let imagedData = NSData(contentsOfURL: imageURL!)!
cell.imageView?.image = UIImage(data: imagedData)
return cell
}
Swift 2.0 :
1)
if let url = NSURL(string: "http://etc...") {
if let data = NSData(contentsOfURL: url) {
imageURL.image = UIImage(data: data)
}
}
OR
imageURL.image =
NSURL(string: "http:// image name...")
.flatMap { NSData(contentsOfURL: $0) }
.flatMap { UIImage(data: $0) }
2) Add this method to VC or Extension.
func load_image(urlString:String)
{ let imgURL: NSURL = NSURL(string: urlString)!
let request: NSURLRequest = NSURLRequest(URL: imgURL)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) { (response: NSURLResponse?, data: NSData?, error: NSError?) in
if error == nil {
self.image_element.image = UIImage(data: data)
}
}
}
Usage :
self.load_image(" url strig here")
class ImageStore: NSObject {
static let imageCache = NSCache<NSString, UIImage>()
}
extension UIImageView {
func url(_ url: String?) {
DispatchQueue.global().async { [weak self] in
guard let stringURL = url, let url = URL(string: stringURL) else {
return
}
func setImage(image:UIImage?) {
DispatchQueue.main.async {
self?.image = image
}
}
let urlToString = url.absoluteString as NSString
if let cachedImage = ImageStore.imageCache.object(forKey: urlToString) {
setImage(image: cachedImage)
} else if let data = try? Data(contentsOf: url), let image = UIImage(data: data) {
DispatchQueue.main.async {
ImageStore.imageCache.setObject(image, forKey: urlToString)
setImage(image: image)
}
}else {
setImage(image: nil)
}
}
}
}
Usage :
let imageView = UIImageView()
imageView.url("image url")
AsyncImage is officially introduced after iOS 15, a view that synchronously loads and displays an image.
var imageView : AsyncImage
imageView = AsyncImage(url: URL(string: entry.photo))
.frame(width: 200, height: 200)
It also supports:
specify a custom placeholder using init(url:scale:content:placeholder:).
gain more control over the loading process, use the init(url:scale:transaction:content:)
See more in doc
Swift 4.1 I have created a function just pass image url, cache key after image is generated set it to completion block.
class NetworkManager: NSObject {
private var imageQueue = OperationQueue()
private var imageCache = NSCache<AnyObject, AnyObject>()
func downloadImageWithUrl(imageUrl: String, cacheKey: String, completionBlock: #escaping (_ image: UIImage?)-> Void) {
let downloadedImage = imageCache.object(forKey: cacheKey as AnyObject)
if let _ = downloadedImage as? UIImage {
completionBlock(downloadedImage as? UIImage)
} else {
let blockOperation = BlockOperation()
blockOperation.addExecutionBlock({
let url = URL(string: imageUrl)
do {
let data = try Data(contentsOf: url!)
let newImage = UIImage(data: data)
if newImage != nil {
self.imageCache.setObject(newImage!, forKey: cacheKey as AnyObject)
self.runOnMainThread {
completionBlock(newImage)
}
} else {
completionBlock(nil)
}
} catch {
completionBlock(nil)
}
})
self.imageQueue.addOperation(blockOperation)
blockOperation.completionBlock = {
print("Image downloaded \(cacheKey)")
}
}
}
}
extension NetworkManager {
fileprivate func runOnMainThread(block:#escaping ()->Void) {
if Thread.isMainThread {
block()
} else {
let mainQueue = OperationQueue.main
mainQueue.addOperation({
block()
})
}
}
}
Edited for Latest change 09/2021
// It's better to use extension
extension UIImageView {
func downloadImage(from URLString: String, with completion: #escaping (_ response: (status: Bool, image: UIImage? ) ) -> Void) {
guard let url = URL(string: URLString) else {
completion((status: false, image: nil))
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
guard error == nil else {
completion((status: false, image: nil))
return
}
guard let httpURLResponse = response as? HTTPURLResponse,
httpURLResponse.statusCode == 200,
let data = data else {
completion((status: false, image: nil))
return
}
let image = UIImage(data: data)
completion((status: true, image: image))
}.resume()
}
}
Happy Codding. Cheers:)
A method for getting the image that is safe and works with Swift 2.0 and X-Code 7.1:
static func imageForImageURLString(imageURLString: String, completion: (image: UIImage?, success: Bool) -> Void) {
guard let url = NSURL(string: imageURLString),
let data = NSData(contentsOfURL: url),
let image = UIImage(data: data)
else {
completion(image: nil, success: false);
return
}
completion(image: image, success: true)
}
You would then call this method like so:
imageForImageURLString(imageString) { (image, success) -> Void in
if success {
guard let image = image
else { return } // Error handling here
// You now have the image.
} else {
// Error handling here.
}
}
If you are updating the view with the image, you will have to use this after the "if success {":
dispatch_async(dispatch_get_main_queue()) { () -> Void in
guard let image = image
else { return } // Error handling here
// You now have the image. Use the image to update the view or anything UI related here
// Reload the view, so the image appears
}
The reason this last part is needed if you are using the image in the UI is because network calls take time. If you try to update the UI using the image without calling dispatch_async like above, the computer will look for the image while the image is still being fetched, find that there is no image (yet), and move on as if there was no image found. Putting your code inside of a dispatch_async completion closure says to the computer, "Go, get this image and when you are done, then complete this code." That way, you will have the image when the code is called and things will work well.
I recommend using Kingfisher library to download images asynchronously. The best part about using Kingfisher is, it caches all the downloaded images by default with the image url as an id. Next time when you request to download image with that particular URl, it will load it from cache.
Usage:
newsImage.kf.setImage(with: imageUrl!, placeholder: nil, options: nil, progressBlock: nil, completionHandler: { (image, error, cacheType, imageUrl) in
if error == nil{
self.activityIndicator.stopAnimating()
}else if error != nil{
self.activityIndicator.stopAnimating()
}
})
You can use pod SDWebImage to achieve the same. Its easy to use. Yo can get documentaion here SDWebImage
Here is the sample code
self.yourImage.sd_setImage(with: NSURL(string: StrUrl as String ) as URL!, placeholderImage: placeholderImage, options: SDWebImageOptions(rawValue: 0), completed: { (image, error, cacheType, imageURL) in
if( error != nil)
{
print("Error while displaying image" , (error?.localizedDescription)! as String)
}
})
Image loading from server :-
func downloadImage(from url: URL , success:#escaping((_ image:UIImage)->()),failure:#escaping ((_ msg:String)->())){
print("Download Started")
getData(from: url) { data, response, error in
guard let data = data, error == nil else {
failure("Image cant download from G+ or fb server")
return
}
print(response?.suggestedFilename ?? url.lastPathComponent)
print("Download Finished")
DispatchQueue.main.async() {
if let _img = UIImage(data: data){
success(_img)
}
}
}
}
func getData(from url: URL, completion: #escaping (Data?, URLResponse?, Error?) -> ()) {
URLSession.shared.dataTask(with: url, completionHandler: completion).resume()
}
Usage :-
if let url = URL(string: "http://www.apple.com/euro/ios/ios8/a/generic/images/og.png") {
self.downloadImage(from:url , success: { (image) in
print(image)
}, failure: { (failureReason) in
print(failureReason)
})
}
Swift 4.2 and AlamofireImage
If using a library is not an issue, you can do it by help of the AlamofireImage.
my samples are from its Github
Placeholder Images Example:
let imageView = UIImageView(frame: frame)
let url = URL(string: "https://httpbin.org/image/png")!
let placeholderImage = UIImage(named: "placeholder")!
imageView.af_setImage(withURL: url, placeholderImage: placeholderImage)
it has many handy functions and extension to work with images. from caching to scaling and resizing or even applying filters on the image. if images are important in your app, I suggest to use this framework and save your time.
Swift 2.x answer that downloads image to file (as opposed to Leo Dabus's answer, which stores the image in memory). Based on Leo Dabus's answer and Rob's answer from Get the data from NSURLSession DownloadTaskWithRequest from completion handler:
// Set download vars
let downloadURL = NSURL() // URL to download from
let localFilename = "foobar.png" // Filename for storing locally
// Create download request
let task = NSURLSession.sharedSession().downloadTaskWithURL(downloadURL) { location, response, error in
guard location != nil && error == nil else {
print("Error downloading message: \(error)")
return
}
// If here, no errors so save message to permanent location
let fileManager = NSFileManager.defaultManager()
do {
let documents = try fileManager.URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false)
let fileURL = documents.URLByAppendingPathComponent(localFilename)
try fileManager.moveItemAtURL(location!, toURL: fileURL)
self.doFileDownloaded(fileURL, localFilename: localFilename)
print("Downloaded message # \(localFilename)")
} catch {
print("Error downloading message: \(error)")
}
}
// Start download
print("Starting download # \(downloadURL)")
task.resume()
// Helper function called after file successfully downloaded
private func doFileDownloaded(fileURL: NSURL, localFilename: String) {
// Do stuff with downloaded image
}
The only things there is missing is a !
let url = NSURL.URLWithString("http://live-wallpaper.net/iphone/img/app/i/p/iphone-4s-wallpapers-mobile-backgrounds-dark_2466f886de3472ef1fa968033f1da3e1_raw_1087fae1932cec8837695934b7eb1250_raw.jpg");
var err: NSError?
var imageData :NSData = NSData.dataWithContentsOfURL(url!,options: NSDataReadingOptions.DataReadingMappedIfSafe, error: &err)
var bgImage = UIImage(data:imageData!)

Resources