UITableViewCell flashing on resize or delete - ios

I am building a card type layout with a UITableView, the "cards" need to be able to resize and be swiped to remove them. I implemented the delegate calls for both actions and the resizing is done as follows:
The "preferred height" is set right before the call to actually resize.
//supply card heights to cell
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
//if no cards are in memory put them in memory.
if (cards == nil){
populateCardData()
}
let card = cards![indexPath.row]
return card.preferedHeight
}
And the call to resize:
func resizeCardWithIndexPath(inIndexPath: NSIndexPath) {
//simply reloads the data at the given index path
shouldAnimate = [inIndexPath:false]//DONT PAY ATTENTION TO THIS(for something else)
tableView.beginUpdates()//THIS IS THE UPDATE PART
tableView.endUpdates()
shouldAnimate = [inIndexPath:true]
}
And The removing of cards:
func removeCardWithIndexPath(inIndexPath:NSIndexPath){
//if no cards are in memory put them in memory.
if (cards == nil){
populateCardData()
}
//remove card from the array
cards!.removeAtIndex(inIndexPath.row)
//delete row from tableview
tableView.deleteRowsAtIndexPaths([inIndexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
}
Now...
All of this works. The cell gets resized appropriately and cells are removed correctly. But when resizing or deleting is done, sometimes irritatingly enough, some of the other cells flash while moving in the Table View.
Am I just doing something stupid or is there a better way?
I did a lot of research and tried a few solutions to no avail.
I did find one way to fix the problem is to make the cell background a solid color and not transparent, this fixed the flashing, but is not a solution because the cards need a transparent border so the background can be seen.
If anyone has any insight help will be a life saver!

Related

UITableViewCell remake constraints when reuse problem

I have a problem with reusing a cell.
In my cell there is a view that changes the constraints depending on its state. By default, cell height = 0, when the user clicks on the button, it changes its constraints top, leading, trailing and bottom. The problem is that if I expand, press expand view in one cell, scroll down (or up), then another cell will also be expanded.
I change the variable responsible for uncovering the cell in the prepareForReuse method, and it doesn't help :(
My solution used to be like this: I redid the constraints in the configure method (it is called in the cellForRow method in VC), and it worked, BUT there were lags when scrolling, apparently, because I change the constraints every time a cell is configured and reused
Are there ways to avoid lag when scrolling? Is there another way to do this?
I use SnapKit for make layout.
I would try avoiding making the cell have a height of zero. This may mess with table view and create an overhead in multiple situations. Since cells are dequeued to reduce resource consumptions you should not produce zero-sized cells as theoretically you can see infinite number of such cells in screen. So basically you can easily have a situation where hundreds of cells are being processed by your table view which user is not even seeing.
An alternative to zero-height cell is to insert/delete cells from table view. There are even animations for that already in place. To achieve this you need to correct your numberOfRows method and your cellForRowAt where both of them need to ignore "hidden" cells. Beside that you basically just need something like
func hideCell(indexPath: IndexPath) {
tableView.beginUpdates()
self.items[indexPath].isHidden = true
tableView.deleteRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
}
func showCell(indexPath: IndexPath) {
tableView.beginUpdates()
self.items[indexPath].isHidden = false
tableView.insertRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
}
This should fix most (all) of your issues. But if you still want to stay with having small/zero sized cells there are other ways to fix your issue.
When you change your constraints you should also call (maybe you already do that but did not post it)
cell.remakeParentCommentConstraints()
tableView.beginUpdates()
tableView.endUpdates()
and make sure that when cellForRow is being called you also call cell.remakeParentCommentConstraints() because as cell is dequeued you can not know what state the previous cell was in.
Even when doing all of this correctly you may experience jumping. This is now because estimated height logic is not very smart by default and you need to improve it. A quick fix is to cache your cell heights:
private var cellHeightCache: [IndexPath: CGFloat] = [:]
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return cellHeightCache[indexPath] ?? 44.0
}
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeightCache[indexPath] = cell.bounds.height
}
The part with 44.0 should be whatever you have currently set as estimated row height.
This trick may get a bit more complicated if you insert/delete rows (as in first solution). As you also need to insert/remove heights from cache. It is doable, just not as easy.
I solved the problem by dividing the states into different cells

Swift Tableview get specific cell not working

I'm trying to clear a specific cell for my tableview. I know to achieve that by using this code right here:
let index = IndexPath(row: 1, section: 0)
if let cell = self.tableView.cellForRow(at: index) as? SomeTableViewCell {
// do sth
}else {
print("Row 1 not found")
}
But the problem is when I use this method with a bigger screen phone like Iphone7, 8, X... It'll be working fine but when I tested on iphone5, 6 or the smaller screen that Row 1 is not visible, It'll print that "Row 1 not found"
Seems like you've almost found the problem yourself!
As with tableviews, the iOS reuses cells them to preserve memory and to offer a seamless smooth scrolling experience to the user.
The iOS reuses the cells when they disappear from the screen. ie: When you scroll down, the initial cells start to be reused. In your larger screens, this takes a while to occur as it may be displaying 20 cells at once vs 10 screens (at once) on your smaller screens.
If you want to permanently remove this cell, then you should remove the corresponding data object from your model which backs up the table view (datasource) and reload the tableview.
If you don't want to permanently remove this cell, then you can reset it only when the table actually needs it to be drawn. This can be done in the datasource method
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell : UITableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell") as UITableViewCell
//Do your Resetting code here
}

Swift - How to expand UITableViewCell's height, while pushing adjacent cells away from the selected cell?

I am working on a ViewController with a TableView populated with dynamic cells from a prototype nib. I have run into a dead-end trying to recreate certain a cell-expanding animation.
My goal:
When a cell is selected, the selected cell should "expand" (grow in height to twice it's starting size) while at the same time creating space between the cell directly above and below the selected cell.
I've found an example of EXACTLY the asethetic I am looking for in the app "Things". Below are two screenshots showing the table before and and after a cell is selected:
Screenshot of table BEFORE expansion
Screenshot of table AFTER expansion
The best way I can describe the desired animation is the UITableView version of "the parting of the Red Sea". When a cell is selected, the surrounding cells give way lending the selected cell more room.. and the user's focus.
What I've tried:
I found Simon Lee's method answering a similar question and implemented it into my project. And although it animates the row-height change perfectly, it only pushes the adjacent cells on one side of the selected cell. (ie: if the cell at index 4 is selected, all the cells from index 5+ move down but those from index 0-3 stay static. Thereby not achieving the look I'm seeking.)
Using that method, the relevant sections of my code looked something like:
var cellHeight: CGFloat = 72
var selectedCell: IndexPath?
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath == selectedCell {
return cellHeight * 2
} else {
return cellHeight
}
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedCell = indexPath
tableView.beginUpdates()
tableView.endUpdates()
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
selectedCell = nil
tableView.beginUpdates()
tableView.endUpdates()
}
Because that didn't result in my desired animation, I tried insertRows:at:with: to insert 2 empty cells (1 above and 1 below the selected row) -- then deleteRows:at:with: to remove them upon deselecting the cell. This ultimately made for a better looking animation, and looked closer to the "Things" example I'm shooting for. However this made the table overly complicated because by adding and removing cells each time a row is selected, it would change the index of the other cells making it frustrating to predict which cells would have what index at any given time.
A possible solution idea?
After working on this for a couple of days the only other way I could think to accomplish what I want is to somehow scroll the table slightly at the same time that the selected cell's height it changed.. so that as the cell expands (moving the following cells downward) it would make the previous cells appear to move upward. I'm hesitant to try this because it feels like a hack, there should be a better way to accomplish this.
ANY help would be thoroughly appreciated! I've been pulling my hair out at an alarming rate. Thank you to anyone who can share their knowledge.

Why using SDWebImage in a UITableViewCell triggers ImageViews not to render properly?

I have a UITableViewController containing my own cells that I dequeue in cellForRowAtIndexPath.
After dequeuing, I configure the cell and reload, asynchronously, the image for that cell.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCellWithIdentifier("PeopleCell", forIndexPath: indexPath) as? PeopleListViewCell {
cell.configureCell(headImageUrl)
}
}
In my PeopleListView class,
func configureCell(img:NSURL) {
if headImageView == nil { // to avoid allocation memory if not used
headImageView = UIImageView()
addSubView(headImageView)
}
headImageView.sd_cancelCurrentImageLoad()
headImageView.sd_setImageWithURL(headImageUrl)
}
It works fine at the first loading and also while scrolling.
But when I push another viewController after
didSelectRowAtIndexPath
and come back to the list after the
dismissViewController()
I end up with a weird effect on my UIImageView , it's kind of a stacked or ghost image effect..
I'm having a hard time to figure out where is even triggered as when I m coming back from the viewController, cellForRowAtIndexPath is not called.
This actually has nothing to do with asynchronous image loading.
The images I had was displayed within circles, with a cornerRadius.
Somehow, it was displayed without any problem at first load...
The issue here is I simply forgot the
headImageView.layer.maskToBounds = true
The result I got before setting maskToBounds to true was that I had the feeling that multiple images were located within the headImageView (UIImageView). If you ever have some artefacts like that, I hope this question/answer will help you.

Why does my UITableView's formatting go completely awry when I return from a view controller I segued to?

I have a UITableView with a custom cell, which has a few labels in it that dynamically decide the height of the cell. When I tap on one cell and segue to a new view controller, upon returning all the formatting for the cells is completely messed up, and I can't figure out what is causing it.
Here is what the cells normally look like:
And I have some pretty basic constraints set on them. The top label is pinned to the top and left margins, and must always be >= 20 from the right. The other labels are aligned to the left of this first label, with vertical spacing set between all of them. The middle label has a right spacing constraint to the margin, and the bottom labels are aligned to the baseline of the first and have horizontal spacing between all of them.
When I segue back to this table view it looks like this however:
I can't figure out what is causing it to layout differently than when I left. If I scroll around it seems to "reset" them back to what they should be, but on initial load they're really messed up. I can attach the project if desired, but there's really not much outside of the Storyboard.
cellForRowAtIndexPath:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as CustomTableViewCell
let object = objects[indexPath.row]
cell.title1.text = object.name
cell.title2.text = object.color
cell.title3.text = object.roar
return cell
}
Sample project: http://cl.ly/040L2z0q0V2d
It appears that the table view cells aren't resizing based on the contents when returning from the segue. Using the sample project, I threw a reload data in the viewWillAppear and that seemed to fix the issue.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.tableView.reloadData()
}
There are actually couple of issues with your project.
Data Loading and AutoLayout.
The first one is causing a strange behavior at the time of drawing the cells with data. When unwinding from the segue you'll see those additional cells on top of your table caused by ambiguous layout calculation.
Solution: Move the data into override func viewWillAppear(animated: Bool) { and perform a tableView.reloadData() (as correctly suggested by #rFessler).
On the other hand, Autolayout is a kind of fiery beast. Tamable. It's worth investigating the topic further. I wasn't able to make your layout work with autosizing cell height but I'll leave few references and the project for you.
References:
http://www.appcoda.com/self-sizing-cells/
http://captechconsulting.com/blog/tyler-tillage/ios-8-tutorial-series-auto-sizing-table-cells
Project:
http://cl.ly/3z3a2Z3a3U2K
I've had a similar problem myself. I downloaded your project and it seems I've solved it by removing and tweaking some constraints. This is how my constraints look now:
Also I've added this to viewDidLoad:
self.tableView.estimatedRowHeight = 120
self.tableView.rowHeight = UITableViewAutomaticDimension
I also added this to test delete:
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete
{
self.objects.removeAtIndex(indexPath.row)
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
}
Now you can even rotate the device and remove rows and it's all working splendid!
However, there's still problem if you push this view on a Navigation Controller (Which is what my problem was about in the beginning). See my storyboard below to get some funky labels:
To solve this, it seems we actually have to do a hack! (Damn you apple, what is going on with this?!)
var firstAppearance=true
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if firstAppearance
{
if let indexPaths = self.tableView.indexPathsForVisibleRows()
{
self.tableView.reloadRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
self.firstAppearance = false
}
}
}
At the moment, I think this is as good as it gets.
I played with this and find a simple solution, add this seems to fix the problem.
override func viewWillDisappear(animated:Bool) {
super.viewWillDisappear(animated)
self.tableView.estimatedRowHeight = 166.0
}
Since the the method tableView:estimatedHeightForRowAtIndexPath will be called every time you segue to a new MVC, and change the autolayout, you can just do
override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
to reuse the autolayout

Resources