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

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()
}
}

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
}

Error after cell insertion: invlalid number of sections

After receiving a new object, I call this func to insert a cell:
private func addCellToTheTop(recipe: Recipe) {
guard let recipeTableView = self.recipeTableView else { return }
recipesForShow.insert(recipe, at: 0)
recipeTableView.beginUpdates()
recipeTableView.insertRows(at: [IndexPath.init(row: 0, section: 0)], with: .automatic)
recipeTableView.endUpdates()
}
But I get an error
Why the number of section does not match?
If it is important:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func numberOfSections(in tableView: UITableView) -> Int {
return recipesForShow.count
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return TableCellConfig.spaceBetweenCells
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
headerView.backgroundColor = UIColor.clear
return headerView
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//...
}
You are getting this exception because you are appending a new element to the array that is being used to determine the numberOfSection whereas you are inserting a new row into the tableView. To fix this you need to insert section instead of inserting new row, here's how:
recipeTableView.insertSections([0], with: .automatic)

iOS Swift: Adding bottom insets between section in Table View

I would like to add some space between the last cell of a section and the header of the next section if the table is a plain type
How can I do that?
You can try
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 20
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return UIView()
}
You can use a Footer View
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return UIView()
}
To avoid sticky footer, use allowsFooterViewsToFloat() function
public extension UITableView {
func allowsHeaderViewsToFloat() -> Bool {
return true
}
func allowsFooterViewsToFloat() -> Bool {
return false
}
}
You can do it with just add empty cell at the end of section.

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
}

Swift - header for section does not show

This is my code
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 61.0
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCellWithIdentifier("CustomHeaderCell") as! CustomHeaderCell
headerCell.titleLabel.text = "user data"
return headerCell
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 2
}
Unfortunately, custom header view does not show up.
However, when I use just this:
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "user data"
}
section headers are present, what is wrong with my custom header view code?
My bad, I blindly put these functions into dataSource although they belong to tableViewDelegate
to make it work:
extension UserProfileDelegate: UITableViewDelegate {
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCellWithIdentifier("CustomHeaderCell") as! CustomHeaderCell
headerCell.titleLabel.text = "user data"
return headerCell
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 61.0
}
}

Resources