How to make consistent display of image in cell in tableView? - swift

I have an image in a custom cell (called "StoryCell"), the reference to which is in a Realm database and which I am loading up in the CellForRowAt with the following code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
....
let story = stories?[indexPath.row]
if story?.storyImage.isEmpty == true {
print("Do nothing")
} else {
let path = getDocumentDirectory().appendingPathComponent(story!.storyImage)
do {
let imageData = try Data(contentsOf: path)
let retrievedImage = UIImage(data: imageData)
cell.storyImage.image = retrievedImage
} catch {
print("Error retrieving image: \(error)")
}
}
cell.delegate = self
return cell
}
However, whenever I am adding a new table item, after the eighth time the display of the images becomes inconsistent i.e. the first picture repeats on the eighth line. I know this is connected with the 'reuse' nature of cells in tableviews and have tried to resolve it using the 'if' statement in my code above, and also by 'reloading' the tableview data when the new item is added:
#IBAction func addStoryButton(_ sender: UIBarButtonItem) {
let newStory = Story()
newStory.dateCreated = Date()
self.save(story: newStory)
self.storiesTableView.reloadData()
}
However, I still get the same behaviour. Thanks in advance for any thoughts on this.

A full explanation of this issue is here: https://fluffy.es/solve-duplicated-cells/. I finally resolved with the following code in the cellForRowAt although the 'reset' code can also be placed in the 'prepareForReuse' function as indicated by Rocky. Setting the 'defaultImage' as seen in code below also helped:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "storyCell", for: indexPath) as! StoryCell
let story = stories?[indexPath.row]
//reset to default image
let defaultURL = Bundle.main.url(forResource: "test", withExtension: "jpg")
do {
let imageData1 = try Data(contentsOf: defaultURL!)
let retrievedImage = UIImage(data: imageData1)
cell.storyImage.image = retrievedImage
} catch {
print("Error retrieving defaultImage: \(error)")
}
//place saved image in storyImage
if story?.storyImage.isEmpty == true {
print("Do nothing")
} else {
let path = getDocumentDirectory().appendingPathComponent(story!.storyImage)
do {
let imageData = try Data(contentsOf: path)
let retrievedImage = UIImage(data: imageData)
cell.storyImage.image = retrievedImage
} catch {
print("Error retrieving image: \(error)")
}
}
cell.delegate = self
return cell
}
Thanks #Rocky for putting me on the right path.

prepareForReuse is the method which you are looking for.
override func prepareForReuse() {
super.prepareForReuse()
// Do your stuff here, like reset image or content of cell for reusing
self.storyImage.image = nil
}

Related

UITableView with Xcode, Fetching CoreData Problem

I have been working for a project but stuck on how to retrieve CoreData to my Table View. This is my code:
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func loadData(){
let requestData: NSFetchRequest<Person> = Person.fetchRequest()
do {
try
personArr = context.fetch(requestData)
try context.save()
} catch {print ("Error retrieving data request \(error)")
self.tableView.reloadData()
}
}
// let DVC = segue.destination as! addPersonController
// numberOfRowsInSection ... cellForRowAt
// let cell = tableView.dequeueReusableCell(withIdentifier: "personCell", for: indexPath)
// cell.textLabel?.text = itemArray[indexPath.row].title > let person = personArr[indexPath.row]
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if(tableView.cellForRow(at: indexPath)?.accessoryType == .checkmark)
{tableView.cellForRow(at: indexPath)?.accessoryType = .none
personArr[indexPath.row].finish = false
//cell.accessoryType = .checkmark //forRaw
}
tableView.deselectRow(at: indexPath, animated: true) //not selected
savePerson()
// let myPerson = PersonArr[indexPath.row].name
// performSegue(withIdentifier: "personSegue", sender: myPerson)
}
Here is just an example of some function that I applied. my concern is at fetching data to personArr that is not been fetching as expected. Any ideas?
A closer look at the code reveals that you are reloading the table view in the catch scope which is almost never going to happen.
Move the line into the do scope and delete the pointless line to save the context
func loadData(){
let requestData: NSFetchRequest<Person> = Person.fetchRequest()
do {
personArr = try context.fetch(requestData)
self.tableView.reloadData()
} catch {
print ("Error retrieving data request \(error)")
}
}

TableView white cell after reloadData

I've a problem after load data from firebase, when i call the reload data, the table view populate with new list data but are all white and if I click I can get the data value correctly.
If I use static data all works fine.
Any suggestion? thanks!
private func loadUniversity(url: String) {
let configuration = URLSessionConfiguration.ephemeral
let session = URLSession(configuration: configuration)
let url = URL(string: url)!
let task = session.dataTask(with: url) {
(data, response, error) in
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200, let data = data else {
return
}
do {
let decoder = JSONDecoder()
let university = try decoder.decode([UniversityJSON].self, from: data)
for item in university {
self.universityArray.append(University(name: item.university_name.trim()))
}
let queue = OperationQueue.main
queue.addOperation {
self.currentUniversityArray = self.universityArray
print(self.currentUniversityArray)
self.table.reloadData()
}
} catch {
print("Error info: \(error)")
}
}
task.resume()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? UniversityCell else {
return UITableViewCell()
}
cell.name.text = currentUniversityArray[indexPath.row].name
return cell
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? UniversityCell else {
return UITableViewCell()
}
cell.name.text = currentUniversityArray[indexPath.row].name
// Put this background Color
cell.backgroundColor = .clear
return cell
}

Swift, Stop Images from loading in table View

Here I load my images, I want to stop the images from loading when I click on the path. How can this be done? I tried setting the URL to nil but that didn't work.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as? CustomCell
let pintrestsUrl = pintrest[indexPath.row].urls?.thumb
Library().parseImages(ImagesUrlArrayPath: pintrestsUrl!, completion: { (image) -> Void in
if let imageFromCache = imageCache.object(forKey: pintrestsUrl as AnyObject ) as? UIImage {
cell?.ImageView.image = imageFromCache
}
})
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// stop images from loading
}
EDIT -- added ParseImages Function
func parseImages(ImagesUrlArrayPath: String, completion: #escaping (UIImage)-> Void) {
if let imageFromCache = imageCache.object(forKey: ImagesUrlArrayPath as AnyObject ) as? UIImage {
completion(imageFromCache)
}
else
{
if let imageURL = URL(string: (ImagesUrlArrayPath)){
DispatchQueue.global().async{
let data = try? Data(contentsOf: imageURL)
if let data = data{
let imageToCache = UIImage(data: data)
// let image = imageToCache
DispatchQueue.main.async {
imageCache.setObject(imageToCache!, forKey: ImagesUrlArrayPath as AnyObject)
completion(imageToCache!)
print("sucess")
//cell?.videoImageView.image = image //?.resizeImage(targetSize: size)
}
}
}
}
}
}
Solved this awhile back
You have to set the images to nil before loading new images on them

Can not display image in table view cell using firebase

I can not display image using firebase in table view cell, I don't know why because my code seems to work, but not there, may anyone help me?
Note: The label Works. I created a custom cell using a cocoa touch class, then I have linked that using the notation to cell as you can see.
import UIKit
import Firebase
class PlacesTableViewController: UIViewController,
UITableViewDataSource, UITableViewDelegate{
let ref = Database.database().reference()
var PlacesRef:DatabaseReference! = nil
var SelectedRef:DatabaseReference! = nil
let storage = Storage.storage()
var postdata:[String] = [String]()
#IBOutlet weak var tableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
print("Here Variables")
print("Here translated Names")
PlacesRef = ref.child("Places")
let storageRef = self.storage.reference()
PlacesRef.observe(.value) { (snapshot) in
let postDict = snapshot.value as? [String : AnyObject] ?? [:]
if snapshot.exists() {
for a in ((snapshot.value as AnyObject).allKeys)!{
var now:String = ""
self.postdata.append(a as! String)
DispatchQueue.main.async{
self.tableview.reloadData()
}
}
} else {
print("we don't have that, add it to the DB now")
}
print(self.postdata) //add key to array
}
tableview.delegate = self
tableview.dataSource = self
}
func tableView(_ _tableView: UITableView, numberOfRowsInSection selection: Int) ->Int{
print(postdata)
return postdata.count
}
func tableView( _ tableview: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableview.dequeueReusableCell(withIdentifier: "cell") as! CustomTableViewCell
var image=UIImage()
let StorageRef = storage.reference()
let NationRef = StorageRef.child("Nations/\(postdata[indexPath.row]).jpg")
print("\(postdata[indexPath.row]).jpg")
NationRef.getData(maxSize: 1 * 1024 * 1024) { data, error in
if let error = error {
print(error)
} else {
// Data for "images/island.jpg" is returned
image = UIImage(data: data!)!
print("image downloaded!")
}
}
cell.DetailLabel.text = postdata[indexPath.row]
cell.DetailImage.image = image
return cell
}
}
Let's see what happens in
func tableView( _ tableview: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableview.dequeueReusableCell(withIdentifier: "cell") as! CustomTableViewCell
var image=UIImage()
let StorageRef = storage.reference()
let NationRef = StorageRef.child("Nations/\(postdata[indexPath.row]).jpg")
// HERE we starting async web request for image data (tag 1)
NationRef.getData(maxSize: 1 * 1024 * 1024) { data, error in
if let error = error {
print(error)
} else {
// HERE we getting image data from async request and storing it in image var (tag 3)
image = UIImage(data: data!)!
print("image downloaded!")
}
}
cell.DetailLabel.text = postdata[indexPath.row]
// HERE we setting UIImage to cell's imageView (tag 2)
cell.DetailImage.image = image
return cell
}
if printing tags we'll get tag 1, tag 2 and tag 3. It means that firstly you create empty uiimage, then set this image to cell's imageView and at last you change image to image from request (image, not cell's imageView)
I think the answer can be - replace image = UIImage(data: data!)! with cell.DetailImage.image = UIImage(data: data!)
and there are many ways to improve this method

TableView Cell show image with dispatch_async shows "unexpected non-void return value in void function" issue

I want to show the image in the TableViewCell. There are the codes:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "myCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! DIYSquareALLCell
cell.titles!.text = titles[indexPath.row]
cell.leftImages!.image = getPic(leftImages[indexPath.row])
return cell
}
func getPic(PicURL: String) -> UIImage! {
let image = self.imageCache[PicURL] as UIImage?
if image == nil {
let url = NSURL(string: PicURL.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!)
if let data = NSData(contentsOfURL: url!) {
imageCache[PicURL] = UIImage(data: data)
return UIImage(data: data)!
}
} else {
return image
}
return nil
}
But scrolling the TableView is very lag so I change the function and add some dispatch_async feature in it.
It shows the issue "unexpected non-void return value in void function" in my getPic function.
After I changed, there are the codes:
func getPic(PicURL: String) -> UIImage! {
let image = self.imageCache[PicURL] as UIImage?
if image == nil {
let url = NSURL(string: PicURL.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!)
let priority = DISPATCH_QUEUE_PRIORITY_HIGH
dispatch_async(dispatch_get_global_queue(priority, 0)) {
let data = NSData(contentsOfURL: url!)
if data != nil {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.imageCache[PicURL] = UIImage(data: data!)
return UIImage(data: data!)// here is the issue
})
}
}
} else {
return image
}
return nil
}
Anyone can tell me how to fix it? Thanks!
You can't return a value r an object when using the Asynchronous task, The function which is running on the main thread and it won't wait for your async task to be finish.
Lets do this with the Closure.
Your code should be like this:
typealias CompletionHandler = (image: UIImage) -> Void
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: testCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! testCell
downloadFileFromURL(NSURL(string: "http://img.youtube.com/vi/PCwL3-hkKrg/sddefault.jpg")!, completionHandler:{(img) in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
cell.imgView.image = img
})
})
return cell
}
func downloadFileFromURL(url1: NSURL?,completionHandler: CompletionHandler) {
// download code.
if let url = url1{
let priority = DISPATCH_QUEUE_PRIORITY_HIGH
dispatch_async(dispatch_get_global_queue(priority, 0)) {
let data = NSData(contentsOfURL: url)
if data != nil {
print("image downloaded")
completionHandler(image: UIImage(data: data!)!)
}
}
}
}
Sample project uploaded to GIT.