34

I am setting a footer view in the viewDidLoad method:

UIView *fView = [[UIView alloc] initWithFrame:CGRectMake(0, 718, 239, 50)];
fView.backgroundColor =[UIColor yellowColor];
self.table.tableFooterView = fView;

Unfortunately, the footer is not drawing in the specified (x,y) specified above, but it stick with the cells, so if the table view has 4 cells, the footer will be drawn in the 5th cell.

I even tried the protocol method, tableView:viewForFooterInSection

- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section{

UIView *fView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 239, 50)];
fView.backgroundColor =[UIColor yellowColor];
    return fView;
}

the problem is not resolved, I am sure tableFooterView property should fi the footer view at the bottom of the table view but I am not sure what I may be missing here? Thanx in advance.

Malloc
  • 15,434
  • 34
  • 105
  • 192
  • The footer is always shown after the last cell in the table. If you have 1,000 cells, the footer is after the 1,000 cell and won't be seen until you scroll all the way down. If you have 4 cells, it is shown after the 4th cell. – rmaddy Mar 28 '13 at 16:39
  • 4
    Neither `UITableView` nor `UITableViewController` provide a way to show a view that stays at the bottom of the screen, if that is your goal. – rmaddy Mar 28 '13 at 16:50
  • Its really sad :( to hear "We cann't fix Footer or Header view".. it always get scroll with rows. – swiftBoy Jan 12 '15 at 09:07
  • It's not sad at all :), a view below a table view that is fixed there is simply a separate view, placed below the table view. That doesn't belong in the table view. It's trivial to implement, and is just irrelevant to the table view object. – theLastNightTrain May 19 '16 at 15:01
  • self.navigationController?.view.addSubview(footerView) – Whitney Foster Sep 29 '16 at 00:07
  • Possible duplicate of [How to put buttons over UITableView which won't scroll with table in iOS](http://stackoverflow.com/questions/14689805/how-to-put-buttons-over-uitableview-which-wont-scroll-with-table-in-ios) – Peter Lapisu Oct 27 '16 at 15:08

9 Answers9

54

Since your goal is to have a footer that stays fixed at the bottom of the screen, and not scroll with the table, then you can't use a table view footer. In fact, you can't even use a UITableViewController.

You must implement your view controller as a UIViewController. Then you add your own table view as a subview. You also add your footer as a subview of the view controller's view, not the table view. Make sure you size the table view so its bottom is at the top of the footer view.

You will need to make your view controller conform to the UITableViewDataSource and UITableViewDelegate protocols and hook everything up to replicate the functionality of UITableViewController.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • 3
    rmaddy should i get some demo example to try above thing. i am beginner in swift. – Vikram Pote Nov 21 '15 at 05:22
  • @rmaddy Am I right that setting a value for y coordinate of footerView is pointless since the footer will always start from the last cell in a UITableView? – bibscy Apr 01 '17 at 03:04
34

A footer view will always be added to the bottom of content.
This means that a section footer will be added below the cells of a section, a table footer view to the bottom of all sections - regardless of the position you set in your view.

If you want to add a "static" content, you should consider adding a view outside of the table view (superview) - which isn't possible if you use UITableViewController - or you use [self.table addSubView:view] and adjust the position/transform to the table view's contentOffset property in the scrollViewDidScroll: delegate method (UITableView is a subclass of UIScrollView so you also get it's delegate calls) like in this code:

@implementation YourTableViewController {
    __weak UIView *_staticView;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    UIView *staticView = [[UIView alloc] initWithFrame:CGRectMake(0, self.tableView.bounds.size.height-50, self.tableView.bounds.size.width, 50)];
    staticView.backgroundColor = [UIColor redColor];
    [self.tableView addSubview:staticView];
    _staticView = staticView;
    self.tableView.contentInset = UIEdgeInsetsMake(0, 0, 50, 0);
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    _staticView.transform = CGAffineTransformMakeTranslation(0, scrollView.contentOffset.y);
}

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    // this is needed to prevent cells from being displayed above our static view
    [self.tableView bringSubviewToFront:_staticView];
}

...
Sahil Mahajan
  • 3,922
  • 2
  • 29
  • 43
Martin Ullrich
  • 94,744
  • 25
  • 252
  • 217
  • I solved the problem from Xcode itself by dragging a new UIVIew from Object Library and dropping it in the 'Table View'. Though it added below the "TableviewCell" - i could extend the UIView till the end. Now added my required views to the new UIView (at the bottom). – Praveen Kondapalli Apr 20 '17 at 10:13
  • this is the the best way and it worked well. because i have a big project with lot of table views, we can't redesign all the views again. this is the best option worked for me – Arun Kumar P Jul 23 '17 at 07:22
15

Another way is to use UITableViewController in a storyboard, and embed it within a UIViewController as a container view. Then you can use auto layout to set the relationship between the footer and the container view which contains the UITableView

cohen72
  • 2,830
  • 29
  • 44
9

If your table view or table view controller is wrapped by a navigation controller consider using the navigation controller's UIToolbar. It will always stick to the bottom.

[self.navigationController setToolbarHidden:NO];
Steve Moser
  • 7,647
  • 5
  • 55
  • 94
4

It looks like something similar to below works quite well:

import PlaygroundSupport
import UIKit

let testVC = UITableViewController(style: .grouped)
testVC.view.frame = CGRect(x: 0, y: 0, width: 400, height: 700)
testVC.view.backgroundColor = .white

class TableViewDataSourceDelegate : NSObject {
    var rows = 2
}

extension TableViewDataSourceDelegate : UITableViewDataSource, UITableViewDelegate {

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return rows
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
        cell.backgroundColor = .red
        return cell
    }

    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {

        let tableViewHeight = tableView.bounds.size.height
        let varticalMargin: CGFloat
        if #available(iOS 11.0, *) {
            varticalMargin = tableView.directionalLayoutMargins.bottom + tableView.directionalLayoutMargins.top
        } else {
            varticalMargin = tableView.layoutMargins.bottom + tableView.layoutMargins.top
        }

        let verticalInset: CGFloat
        if #available(iOS 11.0, *) {
            verticalInset = tableView.adjustedContentInset.bottom + tableView.adjustedContentInset.top
        } else {
            verticalInset = tableView.contentInset.bottom + tableView.contentInset.top
        }
        let tableViewContentHeight = tableView.contentSize.height - varticalMargin


        let height: CGFloat
        if #available(iOS 11.0, *) {
            let verticalSafeAreaInset = tableView.safeAreaInsets.bottom + tableView.safeAreaInsets.top
            height = tableViewHeight - tableViewContentHeight - verticalInset - verticalSafeAreaInset
        } else {
            height = tableViewHeight - tableViewContentHeight - verticalInset
        }

        if (height < 0) {
            return 0
        } else {
            return height
        }
    }

    func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
        let extraButtonSpace = UIView()
        extraButtonSpace.backgroundColor = .clear
        return extraButtonSpace
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if indexPath.row == 0 {
            tableView.beginUpdates()
            rows += 1
            tableView.insertRows(at: [indexPath], with: .automatic)
            tableView.endUpdates()
        } else if indexPath.row == 1 {
            tableView.beginUpdates()
            rows -= 1
            tableView.deleteRows(at: [indexPath], with: .automatic)
            tableView.endUpdates()
        } else {
            tableView.beginUpdates()
            tableView.endUpdates()
        }
    }
}


let controller = TableViewDataSourceDelegate()
testVC.tableView.delegate = controller
testVC.tableView.dataSource = controller
testVC.tableView.reloadData()

let extraButtonSpace = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 80))
extraButtonSpace.backgroundColor = .yellow
testVC.tableView.tableFooterView = extraButtonSpace

PlaygroundPage.current.liveView = testVC.view

Screen from playground

KaQu
  • 111
  • 5
  • I needed it to be in the bottom of the screen if the cells don't fulfill the window, and below the cells if it fills... I changed it a bit your code and it is working exactly as I needed, thanks for sharing it! You can check it out at this gist: https://gist.github.com/endy-s/fe9a463bb09e55e007256907545e5bd5 Btw, I'm not sure why I'm having an offset of 100... At least it is a stable offset (tested on iPhone XR and SE). Any help will be appreaciated! – Endy Silveira Aug 14 '19 at 21:41
3

I was able to get a label to be fixed to the bottom of my static UITableViewController. Not the perfect solution for all scenarios, but worked for my simple needs.

UIView* v = [[UIView alloc] initWithFrame:self.view.bounds];
CGFloat labelHeight = 30;
CGFloat padding = 5;
UILabel* l = [[UILabel alloc] initWithFrame:CGRectMake(0, v.frame.size.height - labelHeight - padding, self.view.frame.size.width, labelHeight)];
l.text = @"Hello World";
[v addSubview:l];
[self.tableView setBackgroundView:v];
JordanC
  • 1,303
  • 12
  • 28
1

If you want to make footer fixed at bottom, you should create custom footerView and change footer frame when tableView content size is changing:

-(void)changeCustomTableFooterYPositionWithTableFrame:(CGRect)tableFrame tableContentSize: (CGSize) tableContentSize {
    CGFloat originalTableViewTopEdgeInset = self.tableView.contentInset.top;
    CGFloat originalTableViewBottomEdgeInset = self.tableView.contentInset.bottom - self.tableFooterView.frame.size.height;

    CGFloat footerViewYPositionByContentSize = tableContentSize.height;
    CGFloat footerViewYPositionByTableSize = tableFrame.size.height - self.tableFooterView.frame.size.height - originalTableViewTopEdgeInset - originalTableViewBottomEdgeInset;
    CGFloat tableFooterViewYPosition = MAX(footerViewYPositionByContentSize, footerViewYPositionByTableSize);

    self.tableFooterView.frame = CGRectMake(self.tableFooterView.frame.origin.x, tableFooterViewYPosition, self.customTableFooterView.frame.size.width, self.customTableFooterView.frame.size.height);
}

To detect when contentSize was changed add observer to contentSize:

 [self addObserver: self forKeyPath: @"tableView.contentSize" options: NSKeyValueObservingOptionNew + NSKeyValueObservingOptionOld context: ContentSizeContext];

Do not forget to change tableView.edgeInsets when insert footer:

self.tableView.contentInset = UIEdgeInsetsMake(self.tableView.contentInset.top, self.tableView.contentInset.left, self.tableView.contentInset.bottom + self.customTableFooterView.frame.size.height, self.tableView.contentInset.right);

You can see inherited class and example at the link below: TableViewWithFooterAtBottom

basiliusic
  • 21
  • 4
0

You can use this to make the table look smaller according to how many rows do you have :

    let tblView =  UIView(frame: CGRectZero)
    tableView.tableFooterView = tblView
    tableView.tableFooterView!.hidden = true
    tableView.backgroundColor = UIColor.clearColor()

Another alternative would be to just change the height for row at index path depending on for what number minimum rows you have that problem.

Alex Zanfir
  • 573
  • 4
  • 13
0

The following is the solution for this footer problem, when we do NOT want the footer to stick in the bottom all the time, AKA. it only sticks to the bottom when there are not enough rows to fill the screen, or when the user scrolls all the way down of the screen.

Add your self.footerView to your self.tableView as a subview on -viewDidLoad: or somewhere like that, then set the delegate for self.tableView, update the content inset of the tableview to self.tableView.contentInset = UIEdgeInsetsMake(0, 0, CGRectGetHeight(self.footerView), 0); and set up the following methods:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
  [self updateFooterView];
}

- (void)updateFooterView
{
  CGRect sectionFrame = [self.tableView rectForSection:0];
  CGFloat bottomSpace = self.tableView.contentOffset.y + CGRectGetHeight(self.tableView.frame) - CGRectGetMaxY(sectionFrame);
  CGFloat footerHeight = CGRectGetHeight(self.footerView.frame);
  CGFloat transformY = self.tableView.contentOffset.y + footerHeight - MIN(bottomSpace,footerHeight);

  CGRect footerFrame = self.footerView.frame;
  footerFrame.origin.y = self.tableView.bounds.size.height - footerFrame.size.height + transformY;
  self.footerView.frame = footerFrame;
}

Whenever you need to update the footer (i.e. after adding a new row), just call -updateFooterView and you should be good

Lucas Chwe
  • 2,578
  • 27
  • 17