Custom expanding UITableViewCell has a glitch? - ios

Here's the glitch. As you can see when I push the custom UITableViewCell SummaryCell out of the view of the app the cell glitches and doesn't put the UILabels dayOfTheWeek and totalAmountSpent in their proper places. But if you click on the glitchy cell, it returns to normal.
Here are some of the methods of theUITableViewController SummaryTableViewController that I used to create the custom cell summaryCell :
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//Create day of week cell
let cell = tableView.dequeueReusableCellWithIdentifier("summaryCell", forIndexPath: indexPath) as! SummaryCell
let day = dayOfWeek[indexPath.row]
let height = CGFloat(55)
let dayOfWeekWidth = CGFloat(80)
cell.dayOfWeek.text = day.rawValue.uppercaseString
cell.dayOfWeek.frame = CGRectMake(0, 0, dayOfWeekWidth, height)
cell.dayOfWeek.backgroundColor = colorOfDay[day]
cell.totalAmountSpent.text = "$\(totalSpentPerDay[indexPath.row])"
cell.totalAmountSpent.frame = CGRectMake(cell.dayOfWeek.frame.maxX + 1, 0, view.bounds.width - dayOfWeekWidth, height)
cell.totalAmountSpent.backgroundColor = colorOfDay[day]
cell.totalAmountSpentView.backgroundColor = colorOfDay[day]
cell.heightOfMainView.constant = normalCellHeight
//^I have this to make sure the height of the cell is the same as
//the height of the mainView in the nib file `summaryCell` so that
//when the table loads intially, only the mainView of the `summaryCell` shows up.
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if expandedCellPaths.contains(indexPath){
let index = expandedCellPaths.indexOf(indexPath)
expandedCellPaths.removeAtIndex(index!)
}else{
expandedCellPaths.append(indexPath)
}
//old trick to animate cell expand/collapse
tableView.beginUpdates()
tableView.endUpdates()
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let expandedCellHeight = normalCellHeight*2
if expandedCellPaths.contains(indexPath){
return expandedCellHeight
}else{
return normalCellHeight
}
}
This is the nib file & its constraints corresponding to my custom UITableViewCell SummaryCell
My 2 best guess as to why this is happening are (1) the constraints in my nib file summaryCell or (2) the lines in my UITableViewConroller SummaryTableViewController:
If anyone could tell me how I can fix this glitch it would be greatly appreciated!
tableView.beginUpdates()
tableView.endUpdates()

Actually never mind! I realized my error! I just needed to delete these lines in tableView(:cellForRowAtIndexPath:):
let height = CGFloat(55)
let dayOfWeekWidth = CGFloat(80)
cell.dayOfWeek.frame = CGRectMake(0, 0, dayOfWeekWidth, height)
cell.totalAmountSpent.frame = CGRectMake(cell.dayOfWeek.frame.maxX + 1, 0, view.bounds.width - dayOfWeekWidth, height)

Related

Self sizing cells height change causes jumping animation

I have setup a tableview with dynamic height cells aka UITableView.automaticDimension using Autolayout. This works fine. Now what I am trying to achieve is to change the height of cell & animate it. The issue is that when I change cell height & animate it, the animation is weirdly jumping. The jump only occurs if I scroll down a bit & then expand/collapse cells.
I have a simple table view cell. It has a label & an empty UIView with fixed height constraint. When I want to collapse/expand the cell, I simply change the constant of that height constraint to 0 or 300.
I have tried many collapsable tableview examples off the internet. All of them have this issue. One exception is https://github.com/Ramotion/folding-cell, but that uses fixed heights for cells.
I have tried quite a few options to animate the cell height change.
1-> On didSelectRow, I change the height constraint & call tableview beginUpdate & endUpdates. Doesn't solve the jump issue.
2-> Change my model & call tableView.reloadRows. Doesn't solve the jump issue.
This is screenshot of my tableview cell setup.
https://drive.google.com/open?id=12nba6cwRszxRlaSA-IhrX3X_vLZ4AWxy
A link to video of this issue:
https://drive.google.com/open?id=19Xmc0PMXT0EuHTJeeGHm4M5aPoChAtf3
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
tableView.estimatedRowHeight = 50
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedSectionHeaderHeight = 0
tableView.estimatedSectionFooterHeight = 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! OuterTableViewCell
let height: CGFloat = isCellExpanded[indexPath.row] ? 300 : 0
cell.labelText.text = "Cell Number: \(indexPath.row + 1)"
cell.buttonExpansionToggle.setImage(UIImage(named: isCellExpanded[indexPath.row] ? "arrow-down" : "arrow-right"),
for: .normal)
cell.viewContainerHeight.constant = height
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
isCellExpanded[indexPath.row] = !isCellExpanded[indexPath.row]
tableView.reloadRows(at: [indexPath], with: .automatic)
}
Another form of didSelectRow:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) as? OuterTableViewCell else { return }
isCellExpanded[indexPath.row] = !isCellExpanded[indexPath.row]
let height: CGFloat = self.isCellExpanded[indexPath.row] ? 300 : 0
cell.viewContainerHeight.constant = height
cell.layoutIfNeeded()
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut, animations: {
tableView.beginUpdates()
tableView.endUpdates()
// fix https://github.com/Ramotion/folding-cell/issues/169
if cell.frame.maxY > tableView.frame.maxY {
tableView.scrollToRow(at: indexPath, at: UITableView.ScrollPosition.bottom, animated: true)
}
}, completion: nil)
}
I have also tried to call beginUpdates() & endUpdates() outside animation block, yet the issue persists.
I expect the animation to be smooth. Hope someone can help. If someone can setup a simple demo project on github that would be awesome.
Demo project link: https://gitlab.com/FahadMasoodP/nested-tableviews
Help in any form is appreciated. Thanks.
My solution is adding dynamic estimate row height. I think it is bug of UIKit. iOS 13 issue will be not occur.
First, You need add property estimateRowHeightStorage to store estimate height by indexpath
var estimateRowHeightStorage: [IndexPath:CGFloat] = [:]
Second, You need store current height of cell for use later
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
estimateRowHeightStorage[indexPath] = cell.frame.size.height
}
Final, you use estimateRowHeightStorage to set estimate row height.
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if estimateRowHeightStorage[indexPath] != nil {
return estimateRowHeightStorage[indexPath]!
}
return 50
}
Run and feel.
I did found new solution in your case. If you hardfix height when expand. Only need change some thing.
Replace all code above with estimatedHeightForRowAt function
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
let height: CGFloat = isCellExpanded[indexPath.row] ? 373 : 73
return height
}

Need to set new constraints for specific indexPath.row

I set my tableview constraints pinch to left and right at zero in the storyboard.
But depends on my indexPath.row, I need to set new constraints (inset & width). I've tried this without success.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch dataType! {
case .mycase:
let cell = tableView.dequeueReusableCell(withIdentifier: "NoBrandDeviceFoundTableViewCell", for: indexPath) as! NoBrandDeviceFoundTableViewCell
tableView.isScrollEnabled = false
var frame = tableView.frame
frame.size.width = 343
cell.frame = frame
tableView.contentInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
return cell
}
}
The frame of the cell won't be changed it's been set equal to the width of the table , you can hook the leading/left & trailing/right constraints of the items you want to shift and change their constant's value according to that indexPath in cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "NoBrandDeviceFoundTableViewCell", for: indexPath) as! NoBrandDeviceFoundTableViewCell
if indexPath.row == <#someValue#> {
cell.lblLeadingCon.constant = <#someValue#>
}
else {
cell.lblLeadingCon.constant = 0
}
return cell
}
you need to take cell leading constanint - nslcLeadingCell
you need to take cell trailing constatint - nslcTrailningCell
based on indexpathrow contion match you need make condition
if indexpath.row == 5
cell.nslcLeadingCell.constant = 10
cell.nslcTrailningCell.constanct = 15

Adding label programmatically in UITableViewCell?

I'm having a problem adding an extra label on some of my cells in the tableview. At the moment I determine the rowheight as follows:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let numberOfBusses = nearbyStops[indexPath.section].getDepartures()[indexPath.row]!.count
if (numberOfBusses > 2) {
return CGFloat((75/2) * numberOfBusses)
} else {
return 75
}
}
I try to do this to add the missing label, but nothing happens:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! BusDepartureTableViewCell
let numberOfBusses = nearbyStops[indexPath.section].getDepartures()[indexPath.row]!.count
if (numberOfBusses > 2) {
var label = UILabel(frame: CGRectMake(0, 0, 200, 21))
label.center = CGPointMake(160, 284)
label.textAlignment = NSTextAlignment.Center
label.text = "I'am a test label"
label.textColor = UIColor.redColor()
cell.foregroundView.addSubview(label)
}
.........
What am I doing wrong?
UPDATE:
I've taken a picture of my current achievements and I've gotten the cell to expand, however, as you can see, there's now room for two other labels, but how do I add them?
Cells get re"used" - that means to save memory iOS uses your UI-Elements only as an placeholder, and sets your values in
cellForRowAtIndexPath
In that case, its much more memory efficient to create 2 (or more) different cell Layouts in Storyboard, and give every cell an different identifier.
For example:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let item = yourPopulatingArray[indexPath.row] as Item
var cellIdentifier = "cellLayout1"
if item.anyPropertyToCheckforLayout {
cellIdentifier = "cellLayout2"
}
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! BusDepartureTableViewCell

When select cell, UITableView changing appearance when scroll?

I have the strangest glitch after I select a UITableView cell and scroll away(You can see it here):
When I select a cell, it's programmed to change its text and change the font color from brown to red. However, when I scroll, other cells that I have not selected change their font color to red. And when I scroll back to the selected cell it reverts to its original text and sometimes, its font color too (from red to brown).
I've used this post in attempt to fix it. But still the glitch remains.
I am completely baffled as to why this is happening and would love love love if anyone could tell me why.
In my code I made my ViewController CategoryViewController the UITableView's Datasource & Delegate instead of a UITableViewController b/c I have other views in my CategoryViewController, not just a UITableView
class CategoryViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
private let themeColors = ThemeColors()
private let expensesOrganizer = ExpensesOrganizer()
override func viewDidLoad() {
super.viewDidLoad()
//Set up subCategory table view
subCategoryTableView.dataSource = self
subCategoryTableView.delegate = self
}
// MARK: UITableViewDataSource
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return expensesOrganizer.getNumOfSubcategoriesFor(category!)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let subcategoryCell = tableView.dequeueReusableCellWithIdentifier("subCategoryCell", forIndexPath: indexPath) as! SubcategoryTableViewCell
let subcategory = expensesOrganizer.getSubcategoryFor(category!, index: indexPath.row)
subcategoryCell.subCategoryLabel.text = "\(indexPath.row) \(expensesOrganizer.getText(subcategory.rawValue))"
subcategoryCell.selectedBackgroundView = UIView(frame: CGRect.zero)
subcategoryCell.selectedBackgroundView?.backgroundColor = themeColors.getColorOfCategory(category!)
return subcategoryCell
}
// MARK: UITableViewDelegate
var indexPathSelectedCell: NSIndexPath?
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let subcategoryCell = tableView.cellForRowAtIndexPath(indexPath) as! SubcategoryTableViewCell
subcategoryCell.subCategoryLabel.textColor = UIColor.redColor()
subcategoryCell.subCategoryLabel.text = "\(indexPath.row) didSELECTRowAtIndexPath called"
indexPathSelectedCell = indexPath
//What the post said to add:
let selectedRows = subCategoryTableView.indexPathsForSelectedRows
for i in selectedRows! {
if !i.isEqual(indexPath){
subCategoryTableView.deselectRowAtIndexPath(i, animated: false)
}
}
}
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
let subcategoryCell = tableView.cellForRowAtIndexPath(indexPath) as! SubcategoryTableViewCell
subcategoryCell.subCategoryLabel.textColor = themeColors.getFontColor(Shade.Light)
subcategoryCell.subCategoryLabel.text = "\(indexPath.row) didDESELECTRowAtIndexPath called"
}
The approach that you take is incorrect, because you are not setting the color when you reuse a cell. Your cellForRowAtIndexPath needs to set color back to brown if the cell is not selected. It should be set to red if the cell is selected:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let subcategoryCell = tableView.dequeueReusableCellWithIdentifier("subCategoryCell", forIndexPath: indexPath) as! SubcategoryTableViewCell
let subcategory = expensesOrganizer.getSubcategoryFor(category!, index: indexPath.row)
subcategoryCell.subCategoryLabel.text = "\(indexPath.row) \(expensesOrganizer.getText(subcategory.rawValue))"
subcategoryCell.selectedBackgroundView = UIView(frame: CGRect.zero)
subcategoryCell.selectedBackgroundView?.backgroundColor = themeColors.getColorOfCategory(category!)
if let selected = tableView.indexPathsForSelectedRows() as? [NSIndexPath] && selected! == indexPath {
subcategoryCell.subCategoryLabel.textColor = UIColor.brownColor()
} else {
subcategoryCell.subCategoryLabel.textColor = UIColor.redColor()
}
return subcategoryCell
}
This has to do with cell reuse.
When you change the color of the label in didSelectRowAtIndexPath, then scroll that cell off-screen, it gets reused for a different cell that will appear on-screen.
However, since you don't prepare the cell for reuse, it is still using the selected font color for your label.
Assigning the label's default text color in prepareForReuse or cellForRowAtIndexPath will fix this issue.

How to get a appropriate height for a reused cell according to the Label in that cell?

This is my layout.The Label's top to the ContentView's top is 58pt.The numberOfLine of the Label is 0.So it can Enter automatically.
And I want the Height of every cell is 58.0+10.0+the height of label.
I have tried this code.
TV.estimatedRowHeight = 68
TV.rowHeight = UITableViewAutomaticDimension
But didn't work.
And then tried this code.
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! CommentDetailCell
return 68.0 + cell.Comment.frame.size.height
}
Didn't work again.
And then try this!To create a array to store the height of every cell's labelHeight.
var CommentHeightA:[CGFloat] = [60.0,60.0,60.0,60.0]
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell:CommentDetailCell = tableView.dequeueReusableCellWithIdentifier("cell") as! CommentDetailCell
cell.UserImg.image = UIImage(named: UserImgA[indexPath.row])
cell.AtNum.text = AtNumA[indexPath.row]
cell.LikeNum.text = LikeNumA[indexPath.row]
cell.isLike.image = UIImage(named: isLikeA[indexPath.row])
cell.Comment.text = CommentA[indexPath.row]
CommentHeightA[indexPath.row] = cell.Comment.frame.size.height
return cell
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 68.0 + CommentHeightA[indexPath.row]
}
And :( all the ways above is failed.
So how can I get what I want?
Update:
I have added the bottom constraint and used the 1st way.
This is the result.Only the 1st row has a appropriate height.
You need to add a bottom constraint from the comment label to the content view. Then add the following code:
TV.estimatedRowHeight = 68
TV.rowHeight = UITableViewAutomaticDimension
It will work,

Resources