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
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
}
}
}
i created share extension for my app which can share image/videos from photos(Gallery) and it work properly but here problem is if i select .HEIC type file then click on my share extension it show error as shown in below screenshot .
import UIKit
import Social
import MobileCoreServices
import AVKit
import Toast_Swift
//#objc(ShareExtensionViewController)
class ShareViewController: UIViewController {
#IBOutlet weak var image: UIImageView!
#IBOutlet weak var btn: UIButton!
#IBOutlet weak var lbl: UILabel!
#IBOutlet weak var view2: UIView!
#IBOutlet weak var cancel: UIButton!
#IBOutlet weak var blurview: UIVisualEffectView!
var allMedia = [Data()]
var singleImage = UIImage()
var imagesUrl = [URL]()
let groupPath = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "
my group identifire ")
var saveDone = Bool()
override func viewDidLoad() {
super.viewDidLoad()
let attachments = (self.extensionContext?.inputItems.first as? NSExtensionItem)?.attachments ?? []
self.btn.setTitle("Add", for: .normal)
self.btn.layer.cornerRadius = 10
self.cancel.layer.cornerRadius = 10
self.image.clipsToBounds = false
applyshadow(image: image)
blurview.isHidden = true
}
override func viewDidAppear(_ animated: Bool) {
self.handleSharedFile()
}
// MARK:- function
func applyshadow(image: UIImageView) {
image.clipsToBounds = false
image.layer.shadowColor = UIColor.systemGray.cgColor
image.layer.shadowOpacity = 1
image.layer.shadowOffset = CGSize.zero
image.layer.shadowRadius = 7.5
}
func videotext(string : String) ->Bool{
let videoextension = [".MP4",".mp4",".mkv",".MKV",".AVI",".avi",".mov", ".MOV"]
if videoextension.contains(string){
return true
}
return false
}
func videoThumb(filepath: URL) -> UIImage{
do{
// let filepath = data?.appendingPathComponent(lbl[indexPath.row])
let asset = AVURLAsset(url: filepath, options: nil)
let imgGenerator = AVAssetImageGenerator(asset: asset)
imgGenerator.appliesPreferredTrackTransform = true
let cgImage = try imgGenerator.copyCGImage(at: CMTimeMake(value: 0, timescale: 1), actualTime: nil)
let uiImage = UIImage(cgImage: cgImage)
return uiImage
}catch let error {
print("Error: \(error.localizedDescription)")
return UIImage()
}
}
private func handleSharedFile() {
// extracting the path to the URL that is being shared
let attachments = (self.extensionContext?.inputItems.first as? NSExtensionItem)?.attachments ?? []
let contentType = kUTTypeData as String
for provider in attachments {
// Check if the content type is the same as we expected
if provider.hasItemConformingToTypeIdentifier(contentType) {
provider.loadItem(forTypeIdentifier: contentType,
options: nil) { [unowned self] (data, error) in
guard error == nil else { return }
if let url = data as? URL {
imagesUrl.append(url)
print(imagesUrl)
if videotext(string: String(url.lastPathComponent.suffix(4))){
DispatchQueue.main.async {
self.image.image = videoThumb(filepath: url)
if attachments.count > 1 {
lbl.text = "\(attachments.count) Items"
}else{
lbl.text = url.lastPathComponent
}
}
}else {
let imageData = try? Data(contentsOf: url)
DispatchQueue.main.async {
if provider == attachments.last{
self.image.image = UIImage(data: imageData!)
}
self.singleImage = UIImage(data: imageData!)!
if attachments.count > 1 {
lbl.text = "\(attachments.count) Items"
}else{
lbl.text = url.lastPathComponent
}
}
}
}else {
print("Impossible to save image")
}
}
}
}
}
thank You in advance for read, giving attention, answer and upvote my question :)
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
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 need to cache the image in Swift on iOS 8. I have a custom Table Cell View.
Here's my code:
import UIKit
class WorksTableViewCell: UITableViewCell {
#IBOutlet var workImage: UIImageView!
#IBOutlet var workTitle: UILabel!
#IBOutlet var workDescription: UILabel!
func configureCellWith(work: Work){
workTitle.text = work.title
workDescription.text = work.description
if let url = NSURL(string: work.image) {
if let data = NSData(contentsOfURL: url) {
var thumb = UIImage(data: data)
workImage.image = thumb
}
}
}
}
Create a dictionary mapping a String to a UIImage in the table's view controller and modify the data in the cellForRowAtIndexPath function. Jameson Quave has a nice tutorial on this very issue found here. It's updated to use new Swift 1.2 syntax as well.
Thanks I fixed like this:
import UIKit
class WorksTableViewCell: UITableViewCell {
#IBOutlet var workImage: UIImageView!
#IBOutlet var workTitle: UILabel!
#IBOutlet var workDescription: UILabel!
var imageCache = [String:UIImage]()
func configureCellWith(work: Work){
workTitle.text = work.title
workDescription.text = work.description
var imgURL = NSURL(string: work.image)
// If this image is already cached, don't re-download
if let img = imageCache[work.image] {
workImage.image = img
}else {
// The image isn't cached, download the img data
// We should perform this in a background thread
let request: NSURLRequest = NSURLRequest(URL: imgURL!)
let mainQueue = NSOperationQueue.mainQueue()
NSURLConnection.sendAsynchronousRequest(request, queue: mainQueue, completionHandler: { (response, data, error) -> Void in
if error == nil {
// Convert the downloaded data in to a UIImage object
let image = UIImage(data: data)
// Store the image in to our cache
self.imageCache[work.image] = image
// Update the cell
dispatch_async(dispatch_get_main_queue(), {
self.workImage.image = image
})
}else {
println("Error: \(error.localizedDescription)")
}
})
}
}
}
You can also create a UIImage extension and just call the async function in the configureCellWith function if you want a cleaner solution for Swift 3.
import Foundation
import UIKit
let imageCache = NSCache <AnyObject,AnyObject>()
extension UIImageView {
func loadUsingCache(_ theUrl: String) {
self.image = nil
//check cache for image
if let cachedImage = imageCache.object(forKey: theUrl as AnyObject) as? UIImage{
self.image = cachedImage
return
}
//otherwise download it
let url = URL(string: theUrl)
URLSession.shared.dataTask(with: url!, completionHandler: {(data, response, error) in
//print error
if (error != nil){
print(error!)
return
}
DispatchQueue.main.async(execute: {
if let downloadedImage = UIImage(data: data!){
imageCache.setObject(downloadedImage, forKey: theUrl as AnyObject)
self.image = downloadedImage
}
})
}).resume()
}
}