Autosizing UITableView inside custom UIView for section - ios

I have created UIViewController and added UITableView on it (pinned to all four edges with autolayout).
Then I set estimatedRowHeight (44) and rowHeight (UITableViewAutomaticDimension) and returned 5 custom cells. And it worked.
Now, I want to add custom UITableViewHeaderFooterView that would have dynamic height.
I'm doing next:
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 88.0
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return R.nib.orderStatusHeaderView.firstView(owner: self)!
}
My OrderStatusHeaderView is a xib view that has UITableView on it pinned to all 4 edges with autolayout.
OrderStatusHeaderView:
final class OrderStatusHeaderView: UITableViewHeaderFooterView {
#IBOutlet weak var tableView: UITableView! {
didSet {
tableView.dataSource = self
tableView.delegate = self
tableView.rowHeight = 44.0
}
}
}
extension OrderStatusHeaderView: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: "\(indexPath.row)")
cell.textLabel?.text = "\(indexPath.row)"
cell.backgroundColor = .red
return cell
}
}
This displays like:
And when I tap or scroll, all red cells disappears. What could it be? And how to make UITableView dynamically load content and UITableViewHeaderFooterView will size itself so it fit UITableView.contentSize. Is it possible?

Check out:
tableView(_:estimatedHeightForFooterInSection:)
and
tableView(_:heightForFooterInSection:)
You also have the equilavant for headerInSection.

Since you are asking for a table's header and footer view, you can skip the delegate methods you describe. Those (as the name implies) are for SECTION headers and footers.
When you set a view that is using AutoLayout as the table's header or footer, the its frame still has a zero height (That's why buttons in such view for example won't work as they are not receiving the touches).
To correctly size a table's header or footer views using AutoLayout you have to apply a trick to actually calculate the height yourself, and set the headerView again. It is described in detail in many posts like these:
https://stackoverflow.com/a/28102157/756039
https://gist.github.com/marcoarment/1105553afba6b4900c10
http://collindonnell.com/2015/09/29/dynamically-sized-table-view-header-or-footer-using-auto-layout/
http://roadfiresoftware.com/2015/05/how-to-size-a-table-header-view-using-auto-layout-in-interface-builder/
Hope this helps.

Related

Why did I get empty cells in UITableView?

I put my UITableView in UIScrollView > View > StackView.
And I implemented table view in View Controller as usual with numberOfRowsInSection and cellForRowAt. Here's my extension.
extension OrderViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("DEBUG: orderItems.count - \(orderItems.count)")
return orderItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: OrderCell.id, for: indexPath) as! OrderCell
cell.configure(orderItems[indexPath.row])
return cell
}
}
But my simulator actually shows empty cells. I already checked the orderItems array only has 2 members.
I guess it is because either auto layout or stack view. In the stack view, I gave height constraints for every views except table view since it has to have dynamic cell number.
I don't know what I have to change.
table view's attribute
This is because your tableview has full height with related your view. Just add on viewDidLoad
yourTableView.tableFooterView = UIView()
But if you want dynamic height for your tableview then do that;
Give a height constraint for your tableview
Create an IBOutlet for this constraint
After you reload your tableview
heightConstraint.constant = yourRowHeight * arrayCount
self.view.layoutIfNeeded()

UITableViewCell Auto Sizing Issue Swift

This is the custom UITableViewCell I've designed In a xib. The selected element is a UILabel, which is required to be expand with respect to amount of text.
In storyboard cell each element has a constant height with spacing.
Here's the code that I have in a UITableViewController:
class TableViewController: UITableViewController {
private var exampleContent = ["This is a short text.", "This is another text, and it is a little bit longer.", "Wow, this text is really very very long! I hope it can be read completely! Luckily, we are using automatic row height!, Wow, this text is really very very long! I hope it can be read completely! Luckily, we are using automatic row height!"]
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
tableView.estimatedRowHeight = 224.0
tableView.rowHeight = UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("DescriptionOnlyCell", owner: self, options: nil)?.first as! DescriptionOnlyCell
cell.label.numberOfLines = 0
cell.label.text = exampleContent[indexPath.row]
return cell
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return exampleContent.count
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
Output of the code is same exactly as the storyboard cell, no dynamic resizing.
I've read a few threads on stackover and most of them explains same like the code posted above. Though I suspect there's some issue with the storyboard constraints. Any hint would be really helpful.
Instead of setting the constant height values for your labels, set the top and the bottom spacing constraints for all elements and remove height constraints or set their priority lower.
Do the following for the selected label :
Set number of lines = 0
Set lineBreak mode = WordWrap
Set height constraint to >= 15
I am relying on the info you shared that vertical spacing for all UI components is set.
I hope this would fix your resizing.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.layoutIfNeeded()
}

Custom UITableView Cell inside a UITableViewController footer

I have a UITableviewController which I created programmatically in Swift4.
The marked rectangle in the picture is my footerview with a UITableView inside
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let tableFooterView = UITableView()
tableFooterView.rowHeight = 100
return tableFooterView
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 365
}
I want to load custom cells in the tableview cells in footerview. I tried using this following code
tableFooterView.dequeueReusableCell(withIdentifier: emptyCellIdentifier) as! EmptyTableViewCells
But I get error -
fatal error: unexpectedly found nil while unwrapping an Optional value
Please help
You have to register the cell for the footer tableView when you are creating in viewForFooterInSection method and you should distinguish both the tables via tag or something if you are making the delegate and datasource as self for the tableFooterView UITableView
tableFooterView.register(EmptyTableViewCells.self, forCellReuseIdentifier: emptyCellIdentifier)
So your method will be :
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
if tableView.tag == 1 {
tableFooterView = UITableView()
tableFooterView.register(EmptyTableViewCells.self, forCellReuseIdentifier: emptyCellIdentifier)
tableFooterView.rowHeight = 100
tableFooterView.tag = 2
tableFooterView.dataSource = self
tableFooterView.delegate = self
return tableFooterView
} else {
//footer view is nil for the table view which is in the main table footer
return nil
}
}
This will stop the crash on the tableFooterView.dequeueReusableCell line but you need to add the other views like buttons and labels on the tableFooter Empty Cell by code and not IBOutlet. It will again crash if you access any IBOutlet connected component.

heightForHeaderInSection only called once

Coding in Swift 3. Have a tableView with custom cells and header.
I have a tableView with custom cells and headers. The headers have two (2) labels in them and have dynamic cell heights since the labels may be long. My problem is the first time the tableView and sections are configured the label appears as it should, HOWEVER, after scrolling down and then back up the headers' layout somehow breaks.
As you can see below, after I scroll down then back up to the cells, the label is getting cutoff.
After printing out what methods are being called I found that the first time scrolling down the tableView the following two (2) override functions are called.
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
print("\(section) heightForHeaderInSection")
print("\(section) returning AUTO for header")
return UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
print("\(section) viewForHeaderInSection")
let header = tableView.dequeueReusableCell(withIdentifier: "QuestionHeader") as! QuestionHeader
header.delegate = self
header.contentView.backgroundColor = UIColor.groupTableViewBackground
header.questionTextLabel.text = String(questionStringArray[section])
header.questionNumberLabel.text = (String(section + 1) + ")")
return header.contentView
}
But when i scroll back up ONLY the viewForHeader function is called and I think because the height is no longer being set to UITableViewAutomaticDimension the labels get cutoff?
Any ideas?
You should return header instead of header.contentView from tableView: viewForHeaderInSection: method:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableCell(...
...
return header
}

tableView section headers disappear SWIFT

I have a tableView set up so that when a cell is touched, it expands in height to reveal more information. The tableView has 5 sections.
I have a bug: when a cell expands, all headersCells below that cell go invisible. The console outputs the following: "[31233:564745] no index path for table cell being reused"
In my storyboard I have 2 custom cells : "myCell" for the data bearing cells, and "headerCell" for the headers.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let thisGoal : GoalModel = goalArray[indexPath.section][indexPath.row]
if self.currentPath != nil && self.currentPath == indexPath {
self.currentPath = nil
} else {
self.currentPath = indexPath
}
tableView.beginUpdates()
tableView.endUpdates()
}
If I enter tableView.reloadData() in between the begin/end updates, it functions properly, although the header background turns black, and loses animation.
I have all of the stuff for headers declared in: func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
what am I missing? I'd really like the tableView to still have animations, and keep the background clearColor().
Thanks in advance. I did read through the objective C answers, but couldn't get them to work for me. I'd really appreciate some SWIFT help.
I think the problem is the no index path for table cell being reused.
I found an answer in the console output. Use this code in the header function:
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
Do not return your headerCell, or your reusable identifier. Return the reuseIdentifier.contentView. For me it's: return headerCell!.contentView.
Just to add, I was baffled for WAY longer than I should have been as to why I couldn't refer to the contentView of my cell, when I could quite clearly see it was there. My custom class (using UITableViewCell rather than UITableViewHeaderFooterView) would return a fatal error each time. Therefore make sure any custom styling is setup under UITableViewHeaderFooterView class like:
class CustomHeaderCell: UITableViewHeaderFooterView {
You will also need to register the resuableIdentifer like this:
tableView.registerNib(UINib(nibName: "HeaderCell", bundle: nil), forHeaderFooterViewReuseIdentifier: "CellHeader")
Then this bad boy:
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableHeaderFooterViewWithIdentifier("CellHeader") as! CustomHeaderCell!
return headerCell!.contentView
}
Since I'm not at 50 reputation yet, I can't comment on the previous answer, so I apologize for listing this as another answer.
Returning the ContentView will make the function work but will remove all formatting done to the reuseIdentifier (headerCell)
headerCell.backgroundColor = UIColor.cyanColor()
This will NOT provide a Cyan color to your headerCell
To fix this, just add the ".contentView" to your formatting lines
headerCell.contentView.backgroundColor = UIColor.cyanColor()
Table view headers in 2 tables disappeared when I converted my app to IOS 10 - I found the reason in Apple developer API documentation on table headers. When I added the following, the missing headers reappeared!
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
{
return 44 // where 44 is the header cell view height in my storyboard
}
You could wrap the TableViewCell inside an UIView
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let containerView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: 50)) // 50 = Header height
guard let headerCell = tableView.dequeueReusableCell(withIdentifier: "MyHeaderView") as? MyHeaderView else { fatalError(" Failed to load MyHeaderView") }
headerCell.frame = containerView.bounds
containerView.addSubview(headerCell)
return containerView
}
I had the same bug because I was returning a cell using dequeue method instead of a UITableViewHeaderFooterView.
Solution:
Add a view outside of the view hierarchy
Set the type to UITableViewHeaderFooterView
Customize
Link to an #IBOutlet
In func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? return the outlet
Common pitfalls:
Don't forget to set the header sizes
Don't forget to set the outlet as strong.

Resources