I have a UITableViewController with a custom UITableViewCell. Each cell has 2 labels. When the cell is selected it expands to a fixed value, I do it via tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat . I also have set the rowHeight = UITableViewAutomaticDimension , as some cells have to display multiple lines of text.
What I want to achieve is when a cell needs to be expanded I want to add 50 points to its current height. So here's the question, how can I get current height of the cell, when the rowHeight = UITableViewAutomaticDimension is set?
Here's my code for the fixed height for the selected state:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if selectedIndexPath == indexPath {
return selectedHeight
}else{
return tableView.estimatedRowHeight
}
}
EDIT: I also need to change it after that, by adding some variable to it.
Building on HamzaLH's answer you might do something like this...
import UIKit
class TableViewController: UITableViewController {
var selectedRow: Int = 999 {
didSet {
tableView.beginUpdates()
tableView.endUpdates()
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == selectedRow { //assign the selected row when touched
let thisCell = tableView.cellForRow(at: indexPath)
if let thisHeight = thisCell?.bounds.height {
return thisHeight + 50
}
}
return 60 //return a default value in case the cell height is not available
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedRow = indexPath.row
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
cell.detailTextLabel?.text = "test"
return cell
}
}
I'm animating the expansion of the height with a didSet when a the selectedRow is altered.
Also, don't forget you may still need to connect your dataSource and delegate by dragging the outlets in Interface Builder to your View Controller in the storyboard. After that you still need to add this to ViewDidLoad in your ViewController's swift file.
tableView.delegate = self
tableView.dataSource = self
I also have an Outlet declared for the tableView like below, and connected in Interface Builder storyboard.
#IBOutlet weak var tableView: UITableView!
You need to get the cell using cellForRow and then get the height of the cell.
let cell = tableView.cellForRow(at: indexPath)
let height = cell.bounds.height
Related
I'm trying to set dynamic row height in TableView. But unfortunately is not working.
The only thing is working is to set the heightForRowAt indexPath to a constant number.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 470 //or any other number
}
If I delete this function, or try to set it to automatic the cell will not show at all. Something like this:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
I also tried to use:
self.tableView.estimatedRowHeight = UITableView.automaticDimension //Tried even with a number
self.tableView.rowHeight = UITableView.automaticDimension
But seems nothing to work besides using a constant height in heightForRowAt indexPath function. Which is not what I want, I want cell to be expanded based on content inside the cell.
I also tried to set height in heightForRowAt indexPath function to automatic but set a fixed height on constraints in the content of the cell xib file, still same (I did just to see what happens). So seems, something is wrong around this function.
Note:
My TableView and UITableViewCell are divided in a storyboard and a xib files.
If you want to set the dynamic tableview row height, you must use autolayout.
Please set the constraint on the UI components in the cell as shown in the image below.
I used UILabel as sample code. For UILabel, be sure to set Lines to 0.
Here is the sample code
class ViewController: UIViewController {
let stringArr = ["aaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"]
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableView.automaticDimension
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return stringArr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "TestCell", for: indexPath) as? TestCell else {
return UITableViewCell()
}
cell.label.text = stringArr[indexPath.row]
return cell
}
}
You need to register the cell in the tableview.
tableView.register(UINib(nibName: “YourCellNibName”, bundle: nil), forCellReuseIdentifier: “YourCellReuseIdentifier”)
My custom cell don't appear in my table View and I didn't find anything to answer that.
Here's my storyboard that contains the TableView :
This is my listController :
extension MatchListViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return matchArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "MatchCell", for: indexPath) as? MatchTableViewCell else {
return UITableViewCell()
}
let match = matchArray[indexPath.row]
cell.configure(nomDuMatch: match.matchName, scoreFinal: match.finalScore)
return cell
}
}
(I've configured the dataSourceDelegate by storyboard)
the customCell identifier is correct and I really don't understand why nothing appears at launch..
Feel free to ask me more pictures / infos !
Edit :
This is the result :
You need to implement
func tableView(_ tableView: UITableView,
heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100 // or any value
}
or use automatic cell and set constraints properly in IB as it seems that you have a constraints problem and set this in viewDidLoad
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension
I am trying to make a FAQ view controller using table view, I need little bit fix in the UI. here is the display of my FAQ VC right now
(please ignore the red line)
as we can see, basically there are 2 row height, 80 & 160. if the row is tapped (selected) the row height will expand from 80 (yellow line) to 160 (purple line).
I want to make the row height under the last question is still 80 not 160. I have tried but I can't set the row below the last question. here is my code I use
class FAQVC: UIViewController {
#IBOutlet weak var retryButton: DesignableButton!
#IBOutlet weak var tableView: UITableView!
var FAQs = [FAQ]()
var selectedIndexs: [IndexPath: Bool] = [:]
override func viewDidLoad() {
super.viewDidLoad()
getFAQData()
}
}
extension FAQVC : UITableViewDelegate, UITableViewDataSource {
// MARK: - Table View Delegate and Datasource
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return FAQs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FAQCell") as! FAQCell
cell.FAQData = FAQs[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if self.cellIsSelected(indexPath: indexPath) {
return 160
} else {
return 80
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let isSelected = !self.cellIsSelected(indexPath: indexPath)
selectedIndexs[indexPath] = isSelected
tableView.beginUpdates()
tableView.endUpdates()
}
func cellIsSelected(indexPath: IndexPath) -> Bool {
if let number = selectedIndexs[indexPath] {
return number
} else {
return false
}
}
}
Please put this code in your viewDidLoad() method of your controller.
YOUR_TABLE_VIEW.tableFooterView = UIView(frame: .zero)
this will remove extra separators.
Quickest way to do this would be to add an extra empty cell at the end with row height 80
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return FAQs.count + 1
}
Also, make sure you make a change to cellForRowAt method to accommodate this :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row < FAQs.count {
let cell = tableView.dequeueReusableCell(withIdentifier: "FAQCell") as! FAQCell
cell.FAQData = FAQs[indexPath.row]
return cell
}
return UITableViewCell()
}
EDIT :
I just read that you don't really require separators after the last cell. In that case look here https://spin.atomicobject.com/2017/01/02/remove-extra-separator-lines-uitableview/
I have an UITableView with a default cell where I just add a string:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
cell.textLabel?.text = tasks[indexPath.row].name
cell.textLabel?.textColor = .white
cell.textLabel?.textAlignment = .center
return cell
}
My problem is that the cell is too big for just some normal text. Here is a screenshot, the cell has the blue background:
I didn't have this issue before, when my data source was an array on String's. The issue appeared after I changed from Strings to custom objects. Not sure if this is related.
How can I fix this ? I tried setting the row height to custom value, but it doesn't have any effect.
edit with table view code:
extension TasksViewController {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tasks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
cell.textLabel?.text = tasks[indexPath.row].name
cell.textLabel?.textColor = .white
cell.textLabel?.textAlignment = .center
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
dummyTxt.textField.text = tasks[indexPath.row].name
dummyTxt.becomeFirstResponder()
dummyTxt.textField.becomeFirstResponder()
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let delete = UITableViewRowAction(style: .destructive, title: "Delete") { (_, index) in
self.tasks.remove(at: index.row)
}
return [delete]
}
}
Set this on tableView:
tableView.estimatedRowHeight = 44
tableView.rowHeight = UITableViewAutomaticDimension
Do NOT override heightForRowAt delegate method.
And then just make sure that the prototype cell that you registered as "Cell" has proper constraints that can be used to calculate its height. In your case make sure that both top and bottom anchors of the label are constrained to the top and bottom anchors of the cell.
Use following delegate method with returning some integer value, if you need same size for every cell.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 40
}
Hopes It may help you.
Try this:
To set automatic dimension for row height & estimated row height, ensure following steps to make, auto dimension effective for cell/row height layout.
Assign and implement tableview dataSource and delegate
Assign UITableViewAutomaticDimension to rowHeight & estimatedRowHeight
Implement delegate/dataSource methods (i.e. heightForRowAt and return a value UITableViewAutomaticDimension to it)
-
#IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Don't forget to set dataSource and delegate for table
table.dataSource = self
table.delegate = self
// Set automatic dimensions for row height
table.rowHeight = UITableViewAutomaticDimension
table.estimatedRowHeight = UITableViewAutomaticDimension
}
// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
For label instance in UITableviewCell
Set number of lines = 0 (& line break mode = truncate tail)
I have a UITableViewCell which contains a UICollectionView on top and a UITableView on the bottom. The idea is that a dynamic amount of cells will be created in the inner UITableView and the parent UITableViewCell that encloses the two subviews will increase its height proportionally.
I am trying to take advantage of the estimatedRowHeight + UITableViewAutomaticDimentionfeature of the UITableViewCell that will allow the cell height to increase dynamically. However, it is not working. It completely removes the embedded UITableView from view.
I have not made any constraints that limit the height of the enclosed UITableView, so I am not sure why it is not working.
Here is the implementation that attempts to make a dynamically sized UITableViewCell:
class OverviewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Enclosed Table View Example"
}
func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 325 // Height for inner table view with 1 cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 45
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "appHeaderCell") as! AppHeaderCell
return cell
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "appCell", for: indexPath) as! AppCell
return cell
}
}
My only guess is that the constraint bottom = Inner Table View.bottom + 7 is causing the issue, but the entire view falls apart when this constraint is removed.
What can I do to make the complex outer UITableViewCell dynamically adjust height based on the number of cells in the embedded UITableView?
Although it may seem like a good idea, the use of UITableViewAutomaticDimension in conjunction with estimatedRowHeight is not good to use in scenarios like this where we have general content inside table view cells. Making use of the heightForRowAt method, you can calculate the size of each individual cell before it centers the table.
Once we know how many cells will be in the inner table, you need to create an array whose elements correspond to the number inner cells that will ultimately determine the height of the outer cell, as all other content is constant.
let cellListFromData: [CGFloat] = [3, 1, 4]
This array will give us the number of sections in our outer table view:
func numberOfSections(in tableView: UITableView) -> Int {
return cellListFromData.count
}
We will convert each element in this array to a cell height in the following way:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let prototypeCell = tableView.dequeueReusableCell(withIdentifier: "appCell") as! AppCell
let baseHeight = betweenCellSpacing + prototypeCell.innerCollectionView.contentSize.height + prototypeCell.innerTableView.sectionHeaderHeight + outerTableView.sectionHeaderHeight
let dynamicHeight = prototypeCell.innerTableView.contentSize.height - prototypeCell.innerTableView.sectionHeaderHeight
return baseHeight + (dynamicHeight * cellListFromData[indexPath.section])
}
That is, inside of the heightForRowAt method, we dequeue a prototype cell that will not be used in the resulting view (as dequeueReusableCell is not called inside cellForRowAt in this case). We use this prototype cell to extract information about what is constant and what is dynamic about the cell's content. The baseHeight is the accumulated height of all the constant elements of the cell (plus the between-cell spacing) and the dynamicHeight is the height of an inner UITableViewCell. The height of each cell then becomes baseHeight + dynamicHeight * cellListFromData[indexPath.section].
Next, we add a numberOfCells variable to the class for the custom cell and set this in the cellForRowAt method in the main table view:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "appCell", for: indexPath) as! AppCell
cell.numberOfCells = Int(cellListFromData[indexPath.section])
cell.innerTableView.reloadData()
return cell
}
numberOfCells is set with the same cellListFromData that we used to get the height of the cell. Also, it is critical to call reloadData() on the inner table view after setting its number of cells so that we see that update in the UI.
Here is the full code:
class OverviewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var outerTableView: UITableView!
let cellSpacing: CGFloat = 25
let data: [CGFloat] = [3, 1, 4]
override func viewDidLoad() {
super.viewDidLoad()
}
func numberOfSections(in tableView: UITableView) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let prototypeCell = tableView.dequeueReusableCell(withIdentifier: "appCell") as! AppCell
let baseHeight = cellSpacing + prototypeCell.innerCollectionView.contentSize.height + prototypeCell.innerTableView.sectionHeaderHeight + outerTableView.sectionHeaderHeight
let dynamicHeight = prototypeCell.innerTableView.contentSize.height - prototypeCell.innerTableView.sectionHeaderHeight
return baseHeight + (dynamicHeight * data[indexPath.section])
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "appCell", for: indexPath) as! AppCell
cell.numberOfCells = Int(data[indexPath.section])
cell.innerTableView.reloadData()
return cell
}
}
class AppCell: UITableViewCell, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var innerCollectionView: UICollectionView!
#IBOutlet weak var innerTableView: UITableView!
var numberOfCells: Int = 0
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numberOfCells
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "featureHeaderCell") as! BuildHeaderCell
return cell
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "innerCell", for: indexPath) as! InnerCell
return cell
}
}
Methods relating to configuring the inner collection view is not included here as it is not related to the problem.