UITableView contentSize too big by 10pt (iOS 10 only!) - ios

I am having a strange issue. I have a UITableView set like so:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 200.0
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 210.0
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if drawerState == .closed {
return 0
}
return 1
}
Initially the drawerState is .closed so the contentSize is 210. I then hit a button that changes the state and prints the new content size like so:
drawerState = .assessmentOpen
print("old content size is \(tableView.contentSize.height)")
tableView.insertRows(at: [IndexPath(item: 0, section: 0)], with: .none)
print("new content size is \(self.tableView.contentSize.height)")
The result I get is:
old content size is 210.0
new content size is 420.0
The new content size is 10pt more than it should be (new content size should be 410). Where does this excess height come from? Oddly enough if I print the value again after a delay it is correct. I have tried all combinations of layoutIfNeeded and using DispatchQueue.main.async the value is always 10pt more than it should be. What am I doing wrong? NOTE: It only behaves like this on iOS10, iOS11 there are no issues.

If you have a .grouped-styled UITableView then the footer of each section has a default height which is not zero. So to fix that add this:
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return CGFloat.leastNormalMagnitude
}
If you return just 0. It falls back to the default value. Technically the CGFloat.leastNormalMagnitude is pretty much like a 0. But if you compare it a 0 is smaller than CGFloat.leastNormalMagnitude.

Did you try setting height of footer to zero and returning nil for footer view?
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return nil
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 0
}

Related

Grouped UITableView have small extra space on bottom on iOS 15+

Grouped UITableview has an extra small space on the bottom on iOS 15+
this functions doesn't help
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
.leastNonzeroMagnitude
}
tableView.sectionHeaderTopPadding = .leastNonzeroMagnitude
You can try
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return nil
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
.leastNormalMagnitude
}

Conditionally hide a section header of a grouped tableview with each section having title

I have a condition based on which, I need to hide the header for my static tableView. My tableview's style is grouped and every section has a title in the section Header.
I am setting the height of the 5th section to CGFloat.leastNormalMagnitude because I want it to disappear but it is not working.
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if hideFifthRow{
if section == 4 {
return CGFloat.leastNormalMagnitude
}
}
return tableView.sectionHeaderHeight
}
Can anyone please help.
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if hideFifthRow {
if section == 4 {
return 0.1
}
}
return tableView.sectionHeaderHeight
}
So, I figured out the reason for header not being able to hide. If there is anything in the header title field, making the section height to be the smallest possible height will not work.
So, on top of
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if hideFifthRow {
if section == 4 {
return CGFloat.leastNormalMagnitude
}
}
return tableView.sectionHeaderHeight
}
Here is what I need to perform additionally-
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if hideFifthRow {
if section == 4 {
return nil
}
}
return sectionTitles[section]
}
Note, I had to store the header titles in an array and set the titles programmatically from titleForHeaderInSection delegate method too. For my hiding clause, I had to return nil to get rid of the title first.

How do I add spacing between two cells in UITableView? There's nothing like separator height or something

I need a layout something like in attached image. I have tried adding a subView at the bottom of UICell but it actually distorts the other items UI. Can someone please help? I am working in Xamarin.iOS
One option is to design the cell as the xib.
One option is to use sections and section footers:
override func numberOfSections(in tableView: UITableView) -> Int {
return 5
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
// your spacing
return 20
}
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let footer = UIView()
footer.backgroundColor = .clear
footer.isOpaque = false
return footer
}

Strange issue with displaying cells in UITableView

I have UITableView with methods:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
override func numberOfSections(in tableView: UITableView) -> Int
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
So, the problem is that Xcode runs at first
override func numberOfSections(in tableView: UITableView) -> Int
which returns me 1.
Later it runs override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int which also returns me 1.
BUT I do not know why after these 2 methods it does not run cellForRowAt method and does not display any row on my UITableView.
I do not know what happens there and why this happens.
Have you met such problem and could you please help me to fix it?
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let fetchedResultsController = self.fetchedResultsController, let fetchedObjects = fetchedResultsController.fetchedObjects {
if !searchController.isActive {
print("numberOfRowsInSection fetchedObjects.count - \(fetchedObjects.count)")
return fetchedObjects.count
} else {
if let results = filteredResult[searchController.searchBar.selectedScopeButtonIndex] {
print("numberOfRowsInSection results.count - \(results.count)")
return results.count
}
}
}
print("numberOfRowsInSection - 0")
return 0
}
override func numberOfSections(in tableView: UITableView) -> Int {
if let frc = fetchedResultsController, frc.fetchedObjects?.count != 0 {
print("numberOfSections - 1")
return 1
} else {
print("numberOfSections - 0")
return 0
}
}
What is probably happening is that your cell has height 0. When this happens, cellForRowAt won't be called at all, even if the other methods return a non zero section/row count.
My guess as to why this is happening is that you may be using auto layout for your cell, but your constraints don't allow the cell to figure out its own height. You may be using auto layout unknowingly, since on iOS 11, it's now the default. If you are using storyboards you can set the height for the cell prototype on the attributes inspector, instead of checking the automatic box. Alternatively, you can set it in code with tableView.rowHeight or by implementing func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat

UITableView custom section footer

We do have a TableViewModel which is implementing the UITableViewDelegate and the UITableViewDataSource. It holds the sections and returns the right rows and elements of our UITableViews.
Now we want to have a custom table view section footer. Therefor we implement the optional public func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? so that this returns a UIView from a xib file. This is all fine but it ends up in a wrong footer height. If we use func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? the footer is displayed correct and the height of the footer is set correct.
So we also have to implement the optional public func tableView(_ tableView: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat or the optional public func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat
Question: How do i get the height of an UIView which this was instantiated? view.frame.bounds or view.frame.height is always 0.
Question: What is the default value Apple uses for the footer and how does it calculate that? I recognised that if we return table.sectionFooterHeight for the default (not our custom footer view) the hight is correct.
TableViewModel:
class TableViewModel: NSObject, UITableViewDelegate, UITableViewDataSource {
var sections: [TableViewSection] = []
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].numberOfRowsInTableView(tableView)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return sections[(indexPath as NSIndexPath).section].tableView(tableView, cellForRowAtIndexPath: indexPath)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
sections[(indexPath as NSIndexPath).section].tableView(tableView, didSelectRowAtIndexPath: indexPath)
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section].headerText
}
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
sections[section].tableView(tableView, willDisplayHeaderView: view)
}
func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return sections[section].footerText
}
func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) {
sections[section].tableView(tableView, willDisplayFooterView: view)
}
func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool {
return sections[(indexPath as NSIndexPath).section].tableView(tableView, cellContentsToCopyAtIndexPath: indexPath, withSender: nil) != nil
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
??
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return sections[section].footerView
}
func tableView(_ tableView: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat {
??
}
}
I solved it by adding the following method:
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return sections[section].footerViewHeight ?? UITableViewAutomaticDimension
}
And returning the calculated height for the footer in section. It does resize properly and we are able to adjust it. As mentioned, returning the UITableViewAutomaticDimension requires:
// Returning this value from tableView:heightForHeaderInSection: or tableView:heightForFooterInSection: results in a height that fits the value returned from // tableView:titleForHeaderInSection: or tableView:titleForFooterInSection: if the title is not nil.
If you are using auto layout you can still return UITableViewAutomaticDimension in heightForFooterInSection and the footer's height will be automatically calculated. As same with heightForRowAt , heightForFooterInSection must be used in combination with estimatedHeightForFooterInSection. A simple example would look like this:
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat {
return 18 // or whatever value
}

Resources