UITableViewCell shows incorrect results from document folder
var superArray = [String]()
var filterArray = [String]()
func filter() {
var proString: String!
for proItem in mp3Files {
var proFolder = fileManager.URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask)
var americaURL: NSURL!
if var proURL: NSURL = proFolder.first as? NSURL {
americaURL = proURL.URLByAppendingPathComponent(proItem)
}
var proPlayerItem = AVPlayerItem(URL: americaURL)
var proData = proPlayerItem.asset.commonMetadata as! [AVMetadataItem]
for proFiles in proData {
if proFiles.commonKey == "artist" {
superArray.append(proFiles.stringValue)
}
}
}
filterArray = Array(Set(superArray))
filterArray.sort(){ $0 < $1 }
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1 ?? 0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return filterArray.count ?? 0
}
var name: String!
var nameArtist: String!
//
var cellStrings: String!
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! UITableViewCell
nameArtist = filterArray[indexPath.row]
cell.textLabel?.text = nameArtist
return cell
}
UITableViewCell has duplicates from document folder
I solved my problem
var superArray = [String]()
var filterArray = [String]()
func filter() {
var proString: String!
for proItem in mp3Files {
var proFolder = fileManager.URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask)
var americaURL: NSURL!
if var proURL: NSURL = proFolder.first as? NSURL {
americaURL = proURL.URLByAppendingPathComponent(proItem)
}
var proPlayerItem = AVPlayerItem(URL: americaURL)
var proData = proPlayerItem.asset.commonMetadata as! [AVMetadataItem]
for proFiles in proData {
if proFiles.commonKey == "artist" {
superArray.append(proFiles.stringValue)
}
}
}
filterArray = Array(Set(superArray))
filterArray.sort(){ $0 < $1 }
}
Custom UITableViewCell showing incorrect values
So the problem turned out to be a lack of calling setNeedsDisplay()
in my CircleView class. Thanks William GP.
UITableViewCell image always showing the last cell image
So at issue here is the loop inside of cell at index path. This function is called once for every row returned by the rows in section function. So you should almost never need a loop in this function. Try something like the following.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
let findPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let card = allCards[indexPath.row]
cell?.textLabel?.text = card.name
let savedFile = (findPath[0] + "/" + card.cardID + ".png")
print(savedFile)
let image = UIImage(contentsOfFile: savedFile)
cell?.imageView?.image = image
return cell!
}
UITableViewCell shows the wrong image while images load
UITableView
only uses a handful of cells (~ the max number of visible cells on screen) when displaying a collection of items, so you'll have more items than cells. This works because of the table view reusing mechanism, which means that the same UITableViewCell
instance will be used for displaying different items. The reason why you are having problems with the images is because you aren't handling the cell reusing properly.
In the cellForRowAt
function you call:
cell.postHeroImage.loadImageUsingCacheWithUrlString(postImageURL)
While you scroll the table view, in different invocations of cellForRowAt
this function will be called for the same cell, but (most probably) displaying the content of different items (because of the cell reusing).
Let's X be the cell you are reusing, then these are roughly the functions that will be called:
1. X.prepareForReuse()
// inside cellForRowAt
2. X.postHeroImage.loadImageUsingCacheWithUrlString(imageA)
// at this point the cell is configured for displaying the content for imageA
// and later you reuse it for displaying the content of imageB
3. X.prepareForReuse()
// inside cellForRowAt
4. X.postHeroImage.loadImageUsingCacheWithUrlString(imageB)
When the images are cached, then you will always have 1, 2, 3 and 4 in that order, that's why you don't see any issues in that case. However, the code that downloads an image and set it to the image view runs in a separate thread, so that order isn't guaranteed anymore. Instead of only the four steps above, you will have something like:
1. X.prepareForReuse()
// inside cellForRowAt
2. X.postHeroImage.loadImageUsingCacheWithUrlString(imageA)
// after download finishes
2.1 X.imageView.image = downloadedImage
// at this point the cell is configured for displaying the content for imageA
// and later you reuse it for displaying the content of imageB
3. X.prepareForReuse()
// inside cellForRowAt
4. X.postHeroImage.loadImageUsingCacheWithUrlString(imageB)
4.1 X.imageView.image = downloadedImage
In this case, because of concurrency, you could end up with the following cases:
- 1, 2, 2.1, 3, 4, 4.1: Everything is displayed properly (this will happen if you scroll slowly)
- 1, 2, 3, 2.1, 4, 4.1: In this case the first image finishes downloading after the call to reuse the cell finishes, so the old image will be displayed (wrongly) for a short period of time while the new one is downloaded, and then replaced.
- 1, 2, 3, 4, 2.1, 4.1: Similar to the case above.
- 1, 2, 3, 4, 4.1, 2.1: In this case the old image finishes downloading after the new one (there is no guaranty the downloads finish in the same order they started) so you will end up with the wrong image. This is the worst case.
For fixing this problem, let's turn our attention to the problematic piece of code inside the loadImageUsingCacheWithUrlString
function:
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
DispatchQueue.main.async(execute: {
if let downloadedImage = UIImage(data: data!) {
imageCache.setObject(downloadedImage, forKey: urlString as NSString)
// this is the line corresponding to 2.1 and 4.1 above
self.image = downloadedImage
}
})
}).resume()
As you can see, you are setting self.image = downloadedImage
even when you aren't displayed the content associated to that image anymore, so what you need is some way to check if that's still the case. Since you define loadImageUsingCacheWithUrlString
in an extension for UIImageView
, then you don't have much context there to know whether you should display the image or not. Instead of that, I propose to move that function to an extension of UIImage
that will return that image in a completion handler, and then call that function from inside your cell. It would look like:
extension UIImage {
static func loadImageUsingCacheWithUrlString(_ urlString: String, completion: @escaping (UIImage) -> Void) {
if let cachedImage = imageCache.object(forKey: urlString as NSString) as? UIImage {
completion(cachedImage)
}
//No cache, so create new one and set image
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
if let error = error {
print(error)
return
}
DispatchQueue.main.async(execute: {
if let downloadedImage = UIImage(data: data!) {
imageCache.setObject(downloadedImage, forKey: urlString as NSString)
completion(downloadedImage)
}
})
}).resume()
}
}
class FeedItem: UITableViewCell {
// some other definitions here...
var postImageURL: String? {
didSet {
if let url = postImageURL {
self.image = UIImage(named: "loading")
UIImage.loadImageUsingCacheWithUrlString(url) { image in
// set the image only when we are still displaying the content for the image we finished downloading
if url == postImageURL {
self.imageView.image = image
}
}
}
else {
self.imageView.image = nil
}
}
}
}
// inside cellForRowAt
cell.postImageURL = postImageURL
Swift 4 Table View Getting Wrong Cell
Cells are being reused - you can only get reference to the visible cells.
let myIndexPath = IndexPath(row: 4 ,section: 0)
let cell = MyTableView.cellForRow(at: myIndexPath)
cell?.backgroundColor = UIColor.yellow
cell
in your case is nil when row number 4 isn't visible. If you wanna change behaviour in the cells you should modify the model and call for example reloadData
on your UITableView.
UItableview Cell changes data and attributes randomly or while scrolling
I've had this issue before, because TableViewCells are re-used you need to ensure you set the background regardless of if it is default or not.
So when you are adding in the code to set the background to green, add an else statement or before the query set the cell background to white/your default color Issue with UITableViewCells Repeating Content
Related Topics
Your Credentials Do Not Allow Access to This Resource Twitter API Error
Is There Really No Way to Style Sklabelnode
Uibezierpath + Cashapelayer - Animate a Circle Filling Up
Modeling Sub-Collections in Mongodb Realm Sync
Storyboard Localization in Swift 4.0
Swift 3 Filter Array of Dictionaries by String Value of Key in Dictionary
Pausing Timer When App Is in Background State Swift
How to Create an Image of Specific Size from Uiview
Can't Copy File from Bundle to Documents Directory in iOS
Find Delegate in a Swift Array of Delegates
Making the Map Zoom to User Location and Annotation (Swift 2)
How to Change Uinavigationbar Per View Controller
Multiple Image Pickers in One Controller
Landscape Orientation for Collection View in Swift
Animate a Change in Part of an Nsmutableattributedstring