I am trying to create expand/ collapse tableView having multiple labels, textViews and images. The problem is when I expand a cell, the top most label (Black Text/ Blue background in image) disappears and then comes back when cell updates. Is there any proper solution to fix this type of problem? Is this related to reloadRows?
// ViewController Class:
private func bindTableView() {
guard let tableView = self.planServicesTableView,
let viewModel = self.viewModel else {
return
}
tableView.estimatedRowHeight = 130
tableView.rowHeight = UITableView.automaticDimension
let dataSource = RxTableViewSectionedReloadDataSource<PlanServiceSection>(configureCell:
{(dataSource: TableViewSectionedDataSource<PlanServiceSection>,
tableView: UITableView,
indexPath: IndexPath,
item: PlanServiceSection.Item) -> UITableViewCell in
let cell = tableView.dequeueReusableCell(withIdentifier: item.cellType.cellIdent, for: indexPath)
if let planServiceCell = cell as? PlanServiceDescriptionTableViewCell {
planServiceCell.setCollapsed(collapsed:(viewModel.cellIsExpanded(at: indexPath)) ? false : true)
planServiceCell.configureCell(item: item)
planServiceCell.upgradeTextView.sizeToFit()
planServiceCell.featureDisclaimerTextView.sizeToFit()
}
if let disclaimerCell = cell as? PlanDisclaimerTableViewCell {
disclaimerCell.setCollapsed(collapsed: (viewModel.cellIsExpanded(at: indexPath)) ? false : true)
disclaimerCell.configureCell(item: item)
disclaimerCell.disclaimerDescriptionTextView.sizeToFit()
}
return cell
})
viewModel.dataSource = dataSource
tableView.tableFooterView = UIView()
tableView.delegate = self
viewModel.sections.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: self.disposeBag)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let descriptionCell = tableView.cellForRow(at: indexPath) as? PlanServiceDescriptionTableViewCell {
descriptionCell.setCollapsed(collapsed: shouldCollapseCell(indexPath: indexPath))
}
if let disclaimerCell = tableView.cellForRow(at: indexPath) as? PlanDisclaimerTableViewCell {
disclaimerCell.setCollapsed(collapsed: shouldCollapseCell(indexPath: indexPath))
}
DispatchQueue.main.async {
tableView.reloadRows(at: [indexPath], with: .automatic)
}
}
private func shouldCollapseCell(indexPath: IndexPath) -> Bool {
if let isExpanded = viewModel?.cellIsExpanded(at: indexPath),
isExpanded {
self.viewModel?.removeExpandedIndexPath(indexPath)
return true
}
self.viewModel?.addExpandedIndexPath(indexPath)
return false
}
// TableViewCell Class:
func setCollapsed(collapsed: Bool) {
self.toggleArrowImage.image = (collapsed ? expandImage : collapseImage)
self.stackView.isHidden = collapsed
}
you need to do the following to fix it
var cellHeights:[IndexPath:CGFloat] = [ : ]
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = cellHeights[indexPath]{
return height
}
return UITableView.automaticDimension
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeights[indexPath] = cell.frame.size.height
}
let me know once you have tested!
Below is the code of my tableView controller which takes an image from a UIImage array resizes it to aspect ratio and displays it on screen. While I scroll the images are very choppy? Is there anyway to reduce the choppiness?
import UIKit
class TableViewController: UITableViewController {
var images = imagePost.defaultimages //An array of a class containing images
var indexPathPass = IndexPath(row: 0, section: 0)
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
tableView.backgroundColor = colors.backgroundColor
DispatchQueue.main.async {
self.tableView.scrollToRow(at: self.indexPathPass, at: .top, animated: false)
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCell
// Configure the cell...
cell.cellImageView.image = self.images[indexPath.row].image
cell.cellImageView.image = cell.cellImageView.image?.resizeImageWith()
cell.backgroundColor = UIColor(displayP3Red: 0, green: 20, blue: 1, alpha: 0.99)
cell.layer.shouldRasterize = true
return cell
}
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
}
Check for the difference in your estimatedRowHeight and the actualRowHeight, the more is the difference the more it will jump while scrolling.
Solution 1.
If possible, try to implement 'heightForItem at indexPath' method. And return calculated height
Solution 2.
You can implement height caching mechanism. Like below code.
class TableViewController: UITableViewController {
var images = imagePost.defaultimages //An array of a class containing images
var indexPathPass = IndexPath(row: 0, section: 0)
fileprivate var cachedHeight = [IndexPath: CGFloat]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
tableView.backgroundColor = colors.backgroundColor
DispatchQueue.main.async {
self.tableView.scrollToRow(at: self.indexPathPass, at: .top, animated: false)
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCell
// Configure the cell...
cell.cellImageView.image = self.images[indexPath.row].image
cell.cellImageView.image = cell.cellImageView.image?.resizeImageWith()
cell.backgroundColor = UIColor(displayP3Red: 0, green: 20, blue: 1, alpha: 0.99)
cell.layer.shouldRasterize = true
return cell
}
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return cachedHeight[indexPath] ?? 100
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cachedHeight[indexPath] = cell.frame.height
}
}
I have added a seperator between cells to add spacing cell with separator
But when I change color of seperatorView to clearColor from blueColor it disappears,can someone help ?
Here's my code:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 50
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell",forIndexPath: indexPath)
cell.textLabel?.text = "Hello World"
cell.textLabel?.textColor = UIColor.blackColor()
cell.backgroundColor = UIColor.clearColor()
let additionalSeperatorThickness = CGFloat(6)
let additionalSeperator = UIView(frame: CGRectMake(0,
cell.frame.size.height - additionalSeperatorThickness,
cell.frame.size.width,
additionalSeperatorThickness))
additionalSeperator.backgroundColor = UIColor.clearColor()
cell.addSubview(additionalSeperator)
let imageView = UIImageView(image: UIImage(named: "text_box_1_converted.png")!)
cell.backgroundView = imageView
cell.bringSubviewToFront(additionalSeperator)
return cell
}
Any idea on how I can manage to do the use case I have below. Although I manage to do this using UITableView, I still get some issue every time I scroll the table view.
Scenario: The table view needs to adjust the height of the cell dynamically based on which item is selected. Each item would have different options inside of it. When Options is selected it should be able to show the options under the item and automatically adjust the height.
Solution: I subclassed UITableView and UITableViewCell. Every time Options is selected/tapped I will call beginUpdate and endUpdate which is working perfectly.
Problem: Every time the user scrolls down the options are not showing correctly.
Everytime when you scrolls up or down the uitableview will call the cellForRowAtIndexPath and the tableView will reload.
So in this case you need to track down the cell which is selected before scrolling up or down and after scrolling you just need to replace the states (e.g. heights) with that desired cell.
In my case I am using some veriable to keep track of the hegiht of the cell.
Here is my code below : -
import UIKit
class WaitingListClass: UIViewController,UITableViewDataSource, UITableViewDelegate {
var selectedCellIndexPath: NSIndexPath!
let SelectedCellHeight: CGFloat = 88.0
let UnselectedCellHeight: CGFloat = 44.0
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
let appdelegateObjForThisClass = UIApplication.sharedApplication().delegate as! AppDelegate
tableView.delegate = self
tableView.dataSource = self
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.backgroundColor = UIColor.clearColor()
self.tableView.separatorColor = tableViewSeperatorColor
self.tableView.tableFooterView = UIView(frame: CGRectZero)
self.tableView.contentInset = UIEdgeInsetsZero
}
internal func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
return 1
}
internal func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if noDataForTable == true{
return 1
}
return appdelegateObjForThisClass.nameDataForTable.count
}
internal func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cellIdentifier = "WaitingCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! CellClassWaitingList
cell.nameDisplayLbl.text = appdelegateObjForThisClass.nameDataForTable[indexPath.row]["name"]!
cell.backgroundColor = tableViewCellColor
cell.nameDisplayLbl.textColor = UIColor(red: 191/255, green: 193/255, blue: 192/255, alpha: 1)
cell.idDisplayLbl.textColor = UIColor(red: 48/255, green: 143/255, blue: 94/255, alpha: 1)
cell.idDisplayLbl.text = appdelegateObjForThisClass.indexForTable[indexPath.row]
cell.userInteractionEnabled = true
cell.clipsToBounds = true
cell.selectionStyle = UITableViewCellSelectionStyle.None
let heightOfCell : CGFloat = self.tableView.delegate!.tableView!(self.tableView, heightForRowAtIndexPath: indexPath)
if heightOfCell == self.UnselectedCellHeight{
cell.stackOfBtnInCell.hidden = true
}else if heightOfCell == self.SelectedCellHeight{
cell.stackOfBtnInCell.hidden = false
}
return cell
}
internal func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! CellClassWaitingList
if let selectedCellIndexPath = selectedCellIndexPath {
if selectedCellIndexPath == indexPath {
self.selectedCellIndexPath = nil
UIView.animateWithDuration(0.5, animations: {
cell.stackOfBtnInCell.hidden = true
self.view.layoutIfNeeded()
})
} else {
UIView.animateWithDuration(0.5, animations: {
cell.stackOfBtnInCell.hidden = false
self.view.layoutIfNeeded()
})
self.selectedCellIndexPath = indexPath
}
} else {
UIView.animateWithDuration(0.5, animations: {
cell.stackOfBtnInCell.hidden = false
self.view.layoutIfNeeded()
})
selectedCellIndexPath = indexPath
}
tableView.beginUpdates()
tableView.endUpdates()
}
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! CellClassWaitingList
cell.stackOfBtnInCell.hidden = true
}
// MARK: - Cell Separator Setting to Full Size
internal func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if(self.tableView.respondsToSelector(Selector("setSeparatorInset:"))){
self.tableView.separatorInset = UIEdgeInsetsZero
}
if(self.tableView.respondsToSelector(Selector("setLayoutMargins:"))){
self.tableView.layoutMargins = UIEdgeInsetsZero
}
if(cell.respondsToSelector(Selector("setLayoutMargins:"))){
cell.layoutMargins = UIEdgeInsetsZero
}
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if let selectedCellIndexPath = selectedCellIndexPath {
if selectedCellIndexPath == indexPath {
return SelectedCellHeight
}
}
return UnselectedCellHeight
}
}
Now what I am doing here is taking a variable called selectedCellIndexPath which will take the selected cell's indexpath first. Next I am taking two more variable called SelectedCellHeight and UnselectedCellHeight which generally stores some height value to interchange each other for the cell in some cases like -
When tableview will be scrolled.
When cell will be selected and deselected.
For rest of the implementation try to look over the tableView datasource and delegate methods.
Thank you,
Hope this helped.
you need to keep a list of your cell states and in your table view data source cellAtIndexPath: and heightForCellAtIndexPath: you should respectively format your cell and specify the desired cell height.
You could create an NSMutableSet and add/remove NSIndexPath objects to it, or use an NSMutableDictionary and check whether a certain key corresponding to a cell's index has a value.
what do you mean "the options are not showing correctly"?
Also, how I would do it:
Create another UITableViewCell in the UITableView via the Storyboard. That TableView cell has the custom height you need.
Create a new UITableViewCell class file.
Assign the new class to the cell in the storyboard.
Call TableView.reloadData() when Options is pressed. And turn a Bool to true for the selected cell.
In the cellForRowAtIndexPath() use the custom UITableViewCell class when the Bool is on true.
// Doesn't work
cell.selectionStyle = .Blue
//Works when the selection is not multiple, if it's multiple with each selection the previous one disappear...
let cellBGView = UIView()
cellBGView.backgroundColor = UIColor(red: 0, green: 0, blue: 200, alpha: 0.4)
cell.selectedBackgroundView = cellBGView
Any answer how to set background color of the cells which are selected?
All the above answers are fine but a bit to complex to my liking. The simplest way to do it is to put some code in the cellForRowAtIndexPath. That way you never have to worry about changing the color when the cell is deselected.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
/* this is where the magic happens, create a UIView and set its
backgroundColor to what ever color you like then set the cell's
selectedBackgroundView to your created View */
let backgroundView = UIView()
backgroundView.backgroundColor = YOUR_COLOR_HERE
cell.selectedBackgroundView = backgroundView
return cell
}
This worked for me:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var selectedCell:UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
selectedCell.contentView.backgroundColor = UIColor.redColor()
}
// if tableView is set in attribute inspector with selection to multiple Selection it should work.
// Just set it back in deselect
override func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
var cellToDeSelect:UITableViewCell = tableView.cellForRowAtIndexPath(indexPath)!
cellToDeSelect.contentView.backgroundColor = colorForCellUnselected
}
//colorForCellUnselected is just a var in my class
Swift 4.2
For multiple selections you need to set the UITableView property allowsMultipleSelection to true.
myTableView.allowsMultipleSelection = true
In case you subclassed the UITableViewCell, you override setSelected(_ selected: Bool, animated: Bool) method in your custom cell class.
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
contentView.backgroundColor = UIColor.green
} else {
contentView.backgroundColor = UIColor.blue
}
}
Swift 3
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "yourCellIdentifier", for: indexPath)
cell.selectionStyle = .none
return cell
}
Swift 2
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "yourCellIdentifier", for: indexPath)
cell.selectionStyle = .None
return cell
}
The problem with Kersnowski's approach is that when the cell is redrawn the changes made when it's selected/deselected will be gone. So I would move the changes into the cell itself, which means subclassing is required here. For example:
class ICComplaintCategoryCell: UITableViewCell {
#IBOutlet var label_title: UILabel!
#IBOutlet var label_checkmark: UILabel!
override func layoutSubviews() {
super.layoutSubviews()
reload()
}
func reload() {
if isSelected {
contentView.backgroundColor = UIColor.red
}
else if isHighlighted{
contentView.backgroundColor = UIColor.red
}
else {
contentView.backgroundColor = UIColor.white
}
}
}
And in your table view delegate just call reload:
if let cell = self.table.cellForRowAtIndexPath(path) as? ICComplaintCategoryCell {
cell.reload()
}
Updated for Swift 3+, thanks #Bogy
You can also set cell's selectionStyle to.none in interface builder. The same solution as #AhmedLotfy provided, only from IB.
For Swift 3,4 and 5 you can do this in two ways.
1) class: UITableViewCell
override func awakeFromNib() {
super.awakeFromNib()
//Costumize cell
selectionStyle = .none
}
or
2) tableView cellForRowAt
cell.selectionStyle = .none
If you want to set selection color for specific cell, check this answer:
https://stackoverflow.com/a/56166325/7987502
UITableViewCell has an attribute multipleSelectionBackgroundView.
https://developer.apple.com/documentation/uikit/uitableviewcell/1623226-selectedbackgroundview
Just create an UIView define the .backgroundColor of your choice and assign it to your cells .multipleSelectionBackgroundView attribute.
Swift 3
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedCell:UITableViewCell = tableView.cellForRow(at: indexPath)!
selectedCell.contentView.backgroundColor = UIColor.darkGray
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let selectedCell:UITableViewCell = tableView.cellForRow(at: indexPath)!
selectedCell.contentView.backgroundColor = UIColor.clear
}
By adding a custom view with the background color of your own you can have a custom selection style in table view.
let customBGColorView = UIView()
customBGColorView.backgroundColor = UIColor(hexString: "#FFF900")
cellObj.selectedBackgroundView = customBGColorView
Add this 3 line code in cellForRowAt method of TableView.
I have used an extension in UIColor to add color with hexcode. Put this extension code at the end of any Class(Outside the class's body).
extension UIColor {
convenience init(hexString: String) {
let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int = UInt32()
Scanner(string: hex).scanHexInt32(&int)
let a, r, g, b: UInt32
switch hex.characters.count {
case 3: // RGB (12-bit)
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
case 6: // RGB (24-bit)
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
case 8: // ARGB (32-bit)
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
default:
(a, r, g, b) = (255, 0, 0, 0)
}
self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255)
}
}
SWIFT 3/4
Solution for CustomCell.selectionStyle = .none if you set some else style you saw "mixed" background color with gray or blue.
And don't forget! func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) didn't call when CustomCell.selectionStyle = .none.
extension MenuView: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cellType = menuItems[indexPath.row]
let selectedCell = tableView.cellForRow(at: indexPath)!
selectedCell.contentView.backgroundColor = cellType == .none ? .clear : AppDelegate.statusbar?.backgroundColor?.withAlphaComponent(0.15)
menuItemDidTap?(menuItems[indexPath.row])
UIView.animate(withDuration: 0.15) {
selectedCell.contentView.backgroundColor = .clear
}
}
}
Swift 5 - This works for me:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedCell:UITableViewCell = tableView.cellForRow(at: indexPath as IndexPath)!
selectedCell.contentView.backgroundColor = .red
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cellToDeSelect:UITableViewCell = tableView.cellForRow(at: indexPath as IndexPath)!
cellToDeSelect.contentView.backgroundColor = .clear
}
You can use standard UITableViewDelegate methods
- (nullable NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
EntityTableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[cell selectMe];
return indexPath;
}
- (nullable NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
EntityTableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[cell deSelectMe];
return indexPath;
}
in my situation this works, cause we need to select cell, change color, and when user taps 2 times on the selected cell further navigation should be performed.
Swift 4
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let selectedCell = tableView.cellForRow(at: indexPath)! as! LeftMenuCell
selectedCell.contentView.backgroundColor = UIColor.blue
}
If you want to unselect the previous cell, also you can use the different logic for this
var tempcheck = 9999
var lastrow = IndexPath()
var lastcolor = UIColor()
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
if tempcheck == 9999
{
tempcheck = 0
let selectedCell = tableView.cellForRow(at: indexPath)! as! HealthTipsCell
lastcolor = selectedCell.contentView.backgroundColor!
selectedCell.contentView.backgroundColor = UIColor.blue
lastrow = indexPath
}
else
{
let selectedCelllasttime = tableView.cellForRow(at: lastrow)! as! HealthTipsCell
selectedCelllasttime.contentView.backgroundColor = lastcolor
let selectedCell = tableView.cellForRow(at: indexPath)! as! HealthTipsCell
lastcolor = selectedCell.contentView.backgroundColor!
selectedCell.contentView.backgroundColor = UIColor.blue
lastrow = indexPath
}
}