I'm having a very odd issue with my app, and all I can think of is that it is some sort of caching issue. Basically, when I scroll my UITableView, the thumbnails that are shown are being reloaded multiple times for the same row, typically with different images each time, before finally landing on the right image.
A short (20 second) screen capture is here: https://youtu.be/oa04mlOgMeQ
The app should be caching these once loaded, and the images are named to match the bonus codes (and the name is in the JSON file). I can't figure out why it is doing this.
EDIT: Here is my cellForRowAt code:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "BonusListViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? BonusListViewCell else {
fatalError("The dequeued cell is not an instance of BonusListViewCell.")
}
// let bonus = bonuses[indexPath.row]
let bonus: JsonFile.JsonBonuses
if isFiltering() {
bonus = filteredBonuses[indexPath.row]
} else {
bonus = bonuses[indexPath.row]
}
let urlString = "http://tourofhonor.com/appimages/"+(bonus.imageName)
let url = URL(string: urlString)
cell.primaryImage.downloadedFrom(url: url!)
cell.nameLabel.text = bonus.name.capitalized
cell.bonusCodeLabel.text = bonus.bonusCode.localizedUppercase
cell.categoryLabel.text = bonus.category
cell.valueLabel.text = "\(bonus.value)"
cell.cityLabel.text = "\(bonus.city.capitalized),"
cell.stateLabel.text = bonus.state.localizedUppercase
return cell
}
Here is my downloadedFrom function:
extension UIImageView {
func downloadedFrom(url: URL, contentMode mode: UIViewContentMode = .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() {
self.image = image
}
}.resume()
}
func downloadedFrom(link: String, contentMode mode: UIViewContentMode = .scaleAspectFit) {
guard let url = URL(string: link) else { return }
downloadedFrom(url: url, contentMode: mode)
}
}
My UITableViewCell code is:
import UIKit
class BonusListViewCell: UITableViewCell {
//MARK: Properties
#IBOutlet weak var bonusCodeLabel: UILabel!
#IBOutlet weak var categoryLabel: UILabel!
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var valueLabel: UILabel!
#IBOutlet weak var cityLabel: UILabel!
#IBOutlet weak var stateLabel: UILabel!
#IBOutlet weak var flavorText: UITextView!
#IBOutlet weak var primaryImage: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "BonusListViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? BonusListViewCell else {
fatalError("The dequeued cell is not an instance of BonusListViewCell.")
}
// let bonus = bonuses[indexPath.row]
let bonus: JsonFile.JsonBonuses
if isFiltering() {
bonus = filteredBonuses[indexPath.row]
} else {
bonus = bonuses[indexPath.row]
}
let urlString = "http://tourofhonor.com/appimages/"+(bonus.imageName)
let url = URL(string: urlString)
//set image url
cell.imageUrl = URL(string: images[indexPath.row])
cell.nameLabel.text = bonus.name.capitalized
cell.bonusCodeLabel.text = bonus.bonusCode.localizedUppercase
cell.categoryLabel.text = bonus.category
cell.valueLabel.text = "\(bonus.value)"
cell.cityLabel.text = "\(bonus.city.capitalized),"
cell.stateLabel.text = bonus.state.localizedUppercase
return cell
}
In your cell
import UIKit
class BonusListViewCell: UITableViewCell {
//MARK: Properties
#IBOutlet weak var bonusCodeLabel: UILabel!
#IBOutlet weak var categoryLabel: UILabel!
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var valueLabel: UILabel!
#IBOutlet weak var cityLabel: UILabel!
#IBOutlet weak var stateLabel: UILabel!
#IBOutlet weak var flavorText: UITextView!
#IBOutlet weak var primaryImage: UIImageView!
#IBOutlet weak var activity: UIActivityIndicatorView!
var imageUrl: URL? {
didSet {
loadImage()
}
}
var task: URLSessionDataTask?
override func awakeFromNib() {
super.awakeFromNib()
clean()
}
func loadImage() {
//you need cancel previous task
task?.cancel()
clean()
guard let imageUrl = imageUrl else {
return
}
activity.startAnimating()
task = URLSession.shared.dataTask(with: imageUrl) { [weak self] data, response, error in
DispatchQueue.main.async {
self?.activity.stopAnimating()
if error == nil, let data = data {
self?.primaryImage.image = UIImage(data: data)
}
}
}
task?.resume()
}
func clean() {
primaryImage.image = nil
}
}
But it's better to use libraries like Alamofire or SDWebImage
Your problem is that previously dispatched image fetches are completing after the cell has been reused, resulting in outdated images appearing, in turn, until the final, correct, image is loaded.
There are existing frameworks available that will make this easier for you and also perform caching, but you may not want to introduce 3rd party code into your app.
One approach is to use a UIImageView subclass rather than an extension so that you can add a property for the current url and an in-memory cache:
class LoadableImageView: UIImageView {
private var currentURL: URL?
private static imageCache = NSCache<NSURL,UIImage>()
private static cacheUpdateQueue = DispatchQueue.global(qos: .userInitiated)
func downloadedFrom(url: URL, contentMode mode: UIViewContentMode = .scaleAspectFit) {
contentMode = mode
self.currentURL = url
if let image = LoadableImageView.imageCache.object(forKey:url as NSURL) {
self.image = image
return
}
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),
url == self.currentURL
else { return }
LoadableImageView.cacheUpdateQueue.sync {
LoadableImageView.imageCache.setObject(image, forKey: url as NSURL)
}
DispatchQueue.main.async() {
self.image = image
}
}.resume()
}
func downloadedFrom(link: String, contentMode mode: UIViewContentMode = .scaleAspectFit) {
guard let url = URL(string: link) else { return }
downloadedFrom(url: url, contentMode: mode)
}
}
You will need to set an appropriate placeholder image or nil in prepareForReuse or cellForRowAt
Related
I have the following code for my tableView:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
// Reset the image in the cell
cell.coverImageView.image = nil
// Get the recipe that the tableView is asking about
let recipeInTable = recipe[indexPath.row]
cell.displayRecipe(recipe: recipeInTable, indexPathRow: indexPath.row)
return cell
}
This is my custom cell class, where I am caching the image data to pull from:
class CustomCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var coverImageView: UIImageView!
var recipeToDisplay:Recipe?
var recipeToPullFrom:Recipe!
func displayRecipe(recipe:Recipe, indexPathRow:Int) {
recipeToPullFrom = recipe
DispatchQueue.main.async {
self.titleLabel.text = self.recipeToPullFrom.title
if self.recipeToPullFrom.image == nil {
return
}
else {
let urlString = self.recipeToPullFrom.image
if let imageData = CacheManager.retrieveData(urlString!) {
self.coverImageView.image = UIImage(data: imageData)
return
}
let url = URL(string: urlString!)
guard url != nil else {
print("Could not create url object")
return
}
let session = URLSession.shared
let dataTask = session.dataTask(with: url!) { (data, response, error) in
if error == nil && data != nil {
CacheManager.saveData(urlString!, data!)
if self.recipeToPullFrom.image == urlString {
DispatchQueue.main.async {
// Display the image data in the imageView
self.coverImageView.image = UIImage(data: data!)
}
}
DispatchQueue.main.async {
self.coverImageView.image = UIImage(data: data!)
}
} // End if
} // End dataTask
// Kick off the dataTask
dataTask.resume()
}
}
}
}
And finally my Cache Manager:
class CacheManager {
static var imageDictionary = [String:Data]()
static func saveData(_ url:String, _ imageData:Data) {
// Save the image data along with the url
imageDictionary[url] = imageData
}
static func retrieveData(_ url:String) -> Data? {
// Return the saved imageData or nil
return imageDictionary[url]
}
}
From what I've researched, adding the following in my tableView function should have reset the image in my cell before inputting a new one: cell.coverImageView.image = nil.
Is there something I'm missing? I've also noticed that only my images are showing in the wrong cells. Could I be doing something incorrect with retrieving the image data from cache?
Any direction or support is much appreciated!
Since your images are downloaded asynchronously then it can be downloaded after the cell been reused for another object. What you can do:
When your image is ready to be displayed (downloaded) you need to check if it should be displayed in that cell.
Or
You can also hold a reference to the URLSessionDataTask and cancel the it before reusing the cell by overriding the prepareForReuse method.
I don't think that you need here an asynchronously call especially, when you using .main in other .main. But If you what so you what so make some thing like
DispatchQueue.global(qos: .userInteractive).async {
// Never do here ui code - label, view, images.. anything; or would crash
DispatchQueue.main.async {
// To do here you ui updates
}
}
here is some refactored code with reuse method. I agree with
Hach3m. U need to cancel requests if you don't it anymore, when next cell is about to shown
class CustomCell: UITableViewCell {
#IBOutlet private weak var titleLabel: UILabel!
#IBOutlet private weak var coverImageView: UIImageView!
private var dataTask: URLSessionDataTask?
func displayRecipe(recipe: Recipe, indexPathRow: Int) {
titleLabel.text = recipe.title enter code here
guard let urlString = recipe.image else { return }
if let imageData = CacheManager.retrieveData(urlString) {
coverImageView.image = UIImage(data: imageData)
return
}
guard let url = URL(string: urlString) else { return print("Could not create url object") }
dataTask = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return self.clear() }
CacheManager.saveData(urlString, data)
self.coverImageView.image = UIImage(data: data)
}
dataTask?.resume()
}
override func prepareForReuse() {
super.prepareForReuse()
clear()
}
private func clear() {
dataTask?.cancel()
dataTask = nil
}
}
}
If someone have good idea to implement this let me know
thanks in adance
class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var tableViewCell: UITableViewCell!
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "trendingCell", for: indexPath)
print(self.array[indexPath.row])
cell.textLabel?.text = (self.array[indexPath.row]["title"] as! String)
cell.detailTextLabel?.text = (self.array[indexPath.row]["username"] as! String)
Alamofire.request(imageUrl!, method: .get).response { response in
guard let image = UIImage(data:response.data!) else {
// Handle error
return
}
let imageData = UIImageJPEGRepresentation(image,1.0)
cell.myImage.image = UIImage(data : imageData!)
}
return cell
}
}
You are force unwrapping some values. Try to safely unwrap the values instead. You still have to figure out why the values are nil though.
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
if let url = NSURL(string: self.restaurants[indexPath.row].restaurantImage), let data = NSData(contentsOfURL: url), let image = UIImage(data: data) {
dispatch_async(dispatch_get_main_queue()) { () -> Void in
cell.restaurantImage.image = image
}
}
})
This will help you figure out where the exact issue is
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
guard indexPath.row < self.restaurants.count
else
{
print("Indexpath greater than restaurants count")
return
}
guard let imageUrl = NSURL(string:self.restaurants[indexPath.row].restaurantImage)
else
{
print("Image url is empty")
return
}
guard let imageData = NSData(contentsOfURL: imageUrl)
else
{
print("Cannot retreive data at url")
return
}
guard let image = UIImage(data: imageData)
else
{
print("Data doesnt contain image")
return
}
dispatch_async(dispatch_get_main_queue()) { () -> Void in
cell.restaurantImage.image = image
}
})
I have a UITableView where the data is coming from a Firebase RealtimeDatabase. Once the user selects the row, the data from the row i.e: Title, Description and an Image will be taken to the next ViewController.
I'm able to pass the Title and Description but I'm unable to pass the Image.
Here is my code for the UITableView:
import UIKit
import Firebase
class PostTable: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView:UITableView!
var posts = [Post]()
override func viewDidLoad() {
super.viewDidLoad()
tableView = UITableView(frame: view.bounds, style: .plain)
view.addSubview(tableView)
let cellNib = UINib(nibName: "PostTableViewCell", bundle: nil)
tableView.register(cellNib, forCellReuseIdentifier: "postCell")
var layoutGuide:UILayoutGuide!
layoutGuide = view.safeAreaLayoutGuide
tableView.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: layoutGuide.topAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor).isActive = true
tableView.delegate = self
tableView.dataSource = self
tableView.tableFooterView = UIView()
tableView.reloadData()
observePosts()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func observePosts() {
let postsRef = Database.database().reference().child("Data")
print(postsRef)
postsRef.observe(.value, with: { snapshot in
var tempPosts = [Post]()
for child in snapshot.children{
if let childSnapshot = child as? DataSnapshot,
let dict = childSnapshot.value as? [String:Any],
let title = dict["title"] as? String,
let logoImage = dict["image"] as? String,
let url = URL(string:logoImage),
let description = dict["description"] as? String{
let userProfile = UserProfile(title: title, photoURL: url)
let post = Post(id: childSnapshot.key, title: userProfile, description: description, image: userProfile)
print(post)
tempPosts.append(post)
}
}
self.posts = tempPosts
self.tableView.reloadData()
})
}
func getImage(url: String, completion: #escaping (UIImage?) -> ()) {
URLSession.shared.dataTask(with: URL(string: url)!) { data, response, error in
if error == nil {
completion(UIImage(data: data!))
} else {
completion(nil)
}
}.resume()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(posts.count)
return posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell
cell.set(post: posts[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let postsInfo = posts[indexPath.row]
print(postsInfo)
let Storyboard = UIStoryboard(name: "Main", bundle: nil)
let DvC = Storyboard.instantiateViewController(withIdentifier: "PostTableDetailed") as! PostTableDetailed
DvC.getName = postsInfo.title.title
DvC.getDesc = postsInfo.description
// DvC.getImg = postsInfo.title.photoURL
self.navigationController?.pushViewController(DvC, animated: true)
}
}
Here is the second ViewControler which has the post details:
import UIKit
class PostTableDetailed: UIViewController {
var getName = String()
var getDesc = String()
#IBOutlet weak var Name: UILabel!
#IBOutlet weak var Description: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
Name.text! = getName
Description.text! = getDesc
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I also have a few Models (Post, UserProfile) and Services (UserService and ImageService), please let me know if that is required to break down this problem.
if you have the imageUrl, all you need is to pass it from PostTable to PostTableDetailed and download the image.
// PostTable
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let postsInfo = posts[indexPath.row]
print(postsInfo)
let Storyboard = UIStoryboard(name: "Main", bundle: nil)
let DvC = Storyboard.instantiateViewController(withIdentifier: "PostTableDetailed") as! PostTableDetailed
DvC.getName = postsInfo.title.title
DvC.getDesc = postsInfo.description
DvC.getImg = postsInfo.photoURL
self.navigationController?.pushViewController(DvC, animated: true)
}
// PostTableDetailed
class PostTableDetailed: UIViewController {
var getName = String()
var getDesc = String()
var imageUrl = ""
#IBOutlet weak var Name: UILabel!
#IBOutlet weak var Description: UILabel!
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
Name.text! = getName
Description.text! = getDesc
updayeImage()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
private func updateImage() {
URLSession.shared.dataTask(with: URL(string: self.imageUrl)!) { data, response, error in
if error == nil, let data = data {
imageView.image = UIImage(data: data)
}
}.resume()
}
}
The image will be shown when the task will complete.
so I suggest for you to add a spinner to the imageView.
In PostDetail ViewController do like this
import UIKit
class PostTableDetailed: UIViewController {
var getName = String()
var getDesc = String()
var getImg = String()
#IBOutlet weak var Name: UILabel!
#IBOutlet weak var Description: UILabel!
#IBOutlet weak var ImageContainer: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
Name.text! = getName
Description.text! = getDesc
if let image = getImage(url: getImg) { (image)
ImageContainer.image = image
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func getImage(url: String, completion: #escaping (UIImage?) -> ()) {
URLSession.shared.dataTask(with: URL(string: url)!) { data, response, error in
if error == nil {
completion(UIImage(data: data!))
} else {
completion(nil)
}
}.resume()
}
}
First of all, you can use this code to download the image:
let imageCache = NSCache<AnyObject, AnyObject>()
extension UIImageView {
func downloadImageWithUrlString(urlString: String) -> Void {
if urlString.count == 0 {
print("Image Url is not found")
return
}
self.image = nil
if let cachedImage = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = cachedImage
return
}
let request = URLRequest(url: URL(string: urlString)!)
let dataTask = URLSession.shared.dataTask(with: request) {data, response, error in
if error != nil { return }
DispatchQueue.main.async {
let downloadedImage = UIImage(data: data!)
if let image = downloadedImage {
imageCache.setObject(image, forKey: urlString as AnyObject)
self.image = UIImage(data: data!)
}
}
}
dataTask.resume()
}
}
Now, if you are using the model that contains Title, Description, and ImageUrlString, then simply pass the selected model object to the next viewController.
In next ViewController, just simply call the same method to download the image which you are using on first ViewController. You don't need to pass the image from VC1 to VC2 because it might be the possible image is not downloaded yet and you select a row to move on next VC.
So here simple thing that pass the model object and calls the image downloading method.
I have a tableview that populates its cells with data from firebase. Each cell posses an image and each image is loaded asynchronously and cached. The problem is that random cells will use the wrong image, although the other data in the cell is correct. If I scroll so that the cell goes off view, the correct image then loads.
Research:
Images in UITableViewCells are loading wrong (Dont really understand Obj C)
What is the correct way to use prepareForReuse?
Image Cache:
import Foundation
import UIKit
let imageCache = NSCache<NSString, AnyObject>()
extension UIImageView {
func loadImageUsingCache(urlString: String) {
self.image = #imageLiteral(resourceName: "logo3")
if let cachedImage = imageCache.object(forKey: urlString as NSString) as? UIImage {
self.image = cachedImage
return
}
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!, completionHandler: {(data, response, error) in
if error != nil {
print(error!)
return
}
DispatchQueue.main.async(execute: {
if let downloadedImage = UIImage(data: data!) {
imageCache.setObject(downloadedImage, forKey: urlString as NSString)
self.image = downloadedImage
}
})
}).resume()
}
}
Tableview Cell
import Foundation
import Firebase
class GroupCell: UITableViewCell {
var group: Groups!
var members = [String]()
#IBOutlet weak var groupImage: UIImageView!
#IBOutlet weak var groupName: UILabel!
#IBOutlet weak var groupRep: UILabel!
#IBOutlet weak var memberCount: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
func configureCell(group: Groups) {
self.group = group
self.groupName.text = group.name
self.groupRep.text = "\(group.groupScore)"
if let groupImage = group.groupImage {
self.groupImage.loadImageUsingCache(urlString: groupImage)
} else {
self.groupImage.image = //random image
}
for member in group.members {
self.members.append(member.key)
}
self.memberCount.text = "\(members.count)"
}
}
TableView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "groupCell") as? GroupCell {
let group = FriendSystem.system.activeGroups[indexPath.row]
cell.configureCell(group: group)
return cell
}
return UITableViewCell()
}
Sounds like you need to add the prepareForReuse method to your GroupCell class.
In that method add self.groupImage.image = nil It will reset your image view to empty until the correct image is set.
I know there are many answers about this error but I cannot seem to figure it out. I am receiving the error Thread 1: EXC_BAD_ACCESS (code=257, address=0x200000003) on the line where the function is called. My table cell view controller is as follows.
import UIKit
class NewsTableViewCell: UITableViewCell {
#IBOutlet weak var postImageView: CustomImageView!
#IBOutlet weak var postTitleLabel:UILabel!
#IBOutlet weak var authorLabel:UILabel!
#IBOutlet weak var dateLabel: UILabel!
var article: Article? {
didSet {
postTitleLabel.text = article?.title
authorLabel.text = article?.author
dateLabel.text = article?.date
setupArticleImage()
}
}
func setupArticleImage() {
postImageView.loadImageUsingUrlString("http://theblakebeat.com/uploads/873463.jpg")
}
This code calls the function loadImageUsingUrlString, which is located in my extensions.swift file. It is called for each table view cell that is loaded in order to load its image. The code for extensions.swift is as follows.
import UIKit
let imageCache = NSCache()
class CustomImageView: UIImageView {
var imageUrlString: String?
func loadImageUsingUrlString(urlString: String) {
imageUrlString = urlString
let url = NSURL(string: urlString)
image = nil
if let imageFromCache = imageCache.objectForKey(urlString) as? UIImage {
self.image = imageFromCache
return
}
NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler: { (data, respones, error) in
if error != nil {
print(error)
return
}
dispatch_async(dispatch_get_main_queue(), {
let imageToCache = UIImage(data: data!)
if self.imageUrlString == urlString {
self.image = imageToCache
}
imageCache.setObject(imageToCache!, forKey: urlString)
})
}).resume()
}
}
Thank you in advance for any help.
You did not set the class CustomImageView to the postImageView in the IBInspector:
Your extensions.swift is in the right Target?
In XCode select the file in Project Navigator and check in File Inspector the section Target Membership