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

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
}

Related

Weird Gap in iOS UITableView Cells in iOS 15

Gap in UITableView Cells
I have added a UITableViewController through code but in iOS and it works okay in iOS 14 but in iOS 15 it shows a weird gap between cells.
I have tried everything that is adding HeaderView, FooterView, two new properties that is introduced in iOS 15
if #available(iOS 15.0, *) {
tableView.fillerRowHeight = 0.0
tableView.sectionHeaderTopPadding = 0.0
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 0.0
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 0.0
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return UIView()
}
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return UIView()
}
But unfortunately nothing works and the weird gap appears in iOS 15 UITableView.

UITableViewDelegate: reduce repeated code by adding default implementations, any better idea?

In my iOS project, there are many tableViews.
I want reduce repeated code of UITableViewDelegate, by adding default implementations.
I turned the following code,
extension ViewController: UITableViewDelegate{
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return nil
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return CGFloat.zero
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return CGFloat.zero
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return nil
}
}
into
extension ViewController: UITableViewDelegate{
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return nil
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return CGFloat.zero
}
}
extension UIViewController{
#objc func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return CGFloat.zero
}
#objc func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return nil
}
}
When I need the tableViewFoot, just override the extension methods.
extension ViewController: UITableViewDelegate{
// ...
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 18
}
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return CustomView()
}
}
any better idea?
I was intended to extension UITableViewDelegate,
extension UITableViewDelegate{
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return CGFloat.zero
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return nil
}
}
It runs, but does not work.
extension UITableViewDelegate{
#objc func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return CGFloat.zero
}
#objc func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return nil
}
}
After I added #objc, Xcode reports:
#objc can only be used with members of classes, #objc protocols, and concrete extensions of classes
How to do some improvenment?
Instead of subclassing, you might try to use protocols (given also that Swift is positioned as Protocol Oriented language).
Introduce some kind of protocol, e.g.
protocol TableManaging: UITableViewDatasource, UITableViewDelegate {
associatedType Entry
var dataSource: [Entry] { get }
}
Then add default implementation for it:
extension TableManaging {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count
}
... etc
}
Then conform your view controllers to this protocol, override methods where behavior differs from the default one.
Another way would be to make a separate data source/delegate helper class and setting it as tableView.dataSource/delegate instead of view controller. (Not giving code examples here, as they might be quite long and depend on your needs.)
Instead of modifying the default implementation of UITableView, why not create a custom UITableViewController which has your default settings.
class DefaultTableViewController: UITableViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return nil
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return CGFloat.zero
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return CGFloat.zero
}
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return nil
}
}
When you need a modified version, just create a new controller using the default class:
class CustomTableViewController: DefaultTableViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 18
}
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return CustomView()
}
}

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

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
}

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
}

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