65

I want to put buttons over UITableView which stays on same position of screen, won't scroll with UITableView. When I tried to addSubview buttons, they are displayed but scrolls with UITableView.

Reason to put buttons over UITableView is to let user operate some action on it. I'm not talking about having buttons in cell. They are floating buttons which never moves their position.

I'm using UITableViewController, and I don't want to use header or footer for this purpose. And also another some bar (UIToolbar for instance) is not an option for me.

Thanks in advance.

jszumski
  • 7,430
  • 11
  • 40
  • 53
Plan B
  • 665
  • 1
  • 6
  • 4
  • show your `addSubView button` code , whats wrong with it ? do you get any errors? – SpaceDust__ Feb 04 '13 at 15:24
  • 1
    Add your buttons to your tableview's superview. – James Boutcher Feb 04 '13 at 15:28
  • 3
    @JamesBoutcher In a `UITableViewController` the table view doesn't have a superview. The view controller's view is the table view. – rmaddy Feb 04 '13 at 16:06
  • 2
    @rmaddy - I was about to tell you "No frickin way!", and I wrote a quick sample test to prove you wrong, and, well... You're right. :-) Thanks for the education. – James Boutcher Feb 04 '13 at 19:37
  • possible duplicate of [How to add a UIView above the current UITableViewController](http://stackoverflow.com/questions/4641879/how-to-add-a-uiview-above-the-current-uitableviewcontroller) – Senseful Jul 10 '14 at 23:31

10 Answers10

88

You can do it also with UITableViewController (no need for ordinary UIViewController, which nests your table or VC)

UPDATED iOS11+ using SafeAreas

you want to use autolayout to keep the view on bottom

{
    [self.view addSubview:self.bottomView];
    [self.bottomView setTranslatesAutoresizingMaskIntoConstraints:NO];
    UILayoutGuide * safe = self.view.safeAreaLayoutGuide;
    [NSLayoutConstraint activateConstraints:@[[safe.trailingAnchor constraintEqualToAnchor:self.bottomView.trailingAnchor],
                                              [safe.bottomAnchor constraintEqualToAnchor:self.bottomView.bottomAnchor],
                                              [safe.leadingAnchor constraintEqualToAnchor:self.bottomView.leadingAnchor]]];
    [self.view layoutIfNeeded];
}

and to make sure view is always on top

- (void)viewDidLayoutSubviews {

    [super viewDidLayoutSubviews];

    [self.view bringSubviewToFront:self.bottomView];

}

THE PREVIOUS OLD SOLUTION

By scrolling the table, you need to adjust the position of the "floating" view and it will be fine

If you are in a UITableViewController, just

If you want to float the view at the top of the UITableView

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    CGRect frame = self.floatingView.frame;
    frame.origin.y = scrollView.contentOffset.y;
    self.floatingView.frame = frame;

    [self.view bringSubviewToFront:self.floatingView];
}

If you want float the view on the bottom of the UITableView

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    CGRect frame = self.floatingView.frame;
    frame.origin.y = scrollView.contentOffset.y + self.tableView.frame.size.height - self.floatingView.frame.size.height;
    self.floatingView.frame = frame;

    [self.view bringSubviewToFront:self.floatingView];
}

I believe thats the real answer to your question

Peter Lapisu
  • 19,915
  • 16
  • 123
  • 179
  • 2
    This is the best answer/best practice. There's a WWDC video on scrollviews where they show this approach. – 3lvis Nov 30 '13 at 11:22
  • 4
    This was recommended as WWDC 2011 before autolayout, container views, and iPhones with different screen sizes. See my answer for the modern solution – GingerBreadMane Feb 25 '15 at 19:18
  • allright, but the OP was asking for a UITableViewController Solution, which is not exectly the same as putting the UITableViewController into a parent container – Peter Lapisu Feb 26 '15 at 14:49
  • 1
    This works, but last cell is behind floating view. To make it visible, add a footer or header view to your table view like this: self.tableView.tableFooterView = [self createFooterView]; – Denis Kutlubaev Aug 17 '16 at 16:36
87

In case you are using navigation controller you can add the view to it:

[self.navigationController.view addSubview:yourView];

Example:

UIButton *goToTopButton = [UIButton buttonWithType:UIButtonTypeCustom];
goToTopButton.frame = CGRectMake(130, 70, 60, 20);
[goToTopButton setTitle:@"Scroll to top" forState:UIControlStateNormal];
[goToTopButton addTarget:self action:@selector(goToTop) forControlEvents:UIControlEventTouchUpInside];
[goToTopButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[goToTopButton.layer setBorderColor:[[UIColor whiteColor] CGColor]];
goToTopButton.titleLabel.font = [UIFont fontWithName:@"Helvetica-Bold" size:13];
[self.navigationController.view goToTopButton];
Yossi
  • 2,525
  • 2
  • 21
  • 24
  • 2
    Another advantage here: the new view (if you make it full screen, that is) will "block" all buttons on the page AND all the buttons in the navigation bar. Perfect for "modal" effects. Sometimes it's quite difficult to "block" everything on the page for modal-like effects. What a great answer. – Fattie Nov 29 '13 at 15:09
  • 3
    This method will work as long as you never add/remove your UITableViewController from the navigation controller. If you remove UITableViewController, the goToTop button stays on the navigation controller. Once removed, tapping goToTop will cause a crash since it's target has been deallocated. See my answer for a better approach – GingerBreadMane Feb 25 '15 at 19:21
  • @GingerBreadMane, Maybe your answer is great, but the question was for specific situation, when using uitableviewcontroller. My answer is not perfect, but can help to many developers :) – Yossi Feb 25 '15 at 22:29
  • Perfect solution!! Thank you so much for sharing ;) – swiftBoy Apr 27 '16 at 13:24
  • How can you remove the view when leaving the page ? – user6520705 Feb 22 '17 at 14:36
  • 1
    @user6520705 use it as a property "self.goToTopButton" and remove it in viewDidDisappear [self.goToTopButton removeFromSuperview] – Yossi Feb 23 '17 at 13:24
43

If you want to show a static control view covering a table view, you have three options:

1) Don't use UITableViewController. Instead create a UIViewController with a UITableView subview and put your Table View delegate/datasource code in the UIViewController. This option may or may not work depending on the architecture of your app. For example, if you're trying to implement a subclass a UITableViewController with a lot of custom logic, this isn't ideal.

2) The WWDC 2011 method. See the WWDC 2011 Session 125 "UITableView Changes, Tips & Tricks" starting at 32:20. This method of adjusting the placement of the view whenever the table view scrolls was recommended by Apple before AutoLayout was introduced to iOS and before we had so many different screen sizes to deal with. I don't recommend it since you'll be managing positions for all the different screen sizes yourself.

3) I recommend using a UIViewController with a container view that will contain your UITableViewController. This way, you can keep the table view logic separate in the UITableViewController. The UIViewController hosts a 'dumb' container for the UITableViewController and it also holds the static control view. To do this, drag a UIViewController to your storyboard and drag a "Container View" inside it. Delete the UIViewController that comes automatically attached to the container view, and then attach your table view controller to be displayed in the container view by control dragging from the contrainer view to the table view controller and selecting "embed". Now, you can add static controls as subviews to the UIViewController that will be displayed on top of the table view. It will look something like this:

Storyboard For a UITableViewController inside a container view

I prefer this method because UIViewController can handle the control logic and the UITableViewController handles the table view logic. Some other answers here recommend adding the static control as a subview to a Navigation Controller or Window. These only work as long as you never add another view controller to the navigation controller or window.

GingerBreadMane
  • 2,630
  • 1
  • 30
  • 29
27

One solution to this is to extend UIViewController instead of UITableViewController. You will need to add your own table view and set everything up. Then you can add your button's to the view controller's view instead of the table view.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • 1
    Thank you for your answer. Let me try it and come back later to accept your answer. Please give me few days to give up UITableViewController. – Plan B Feb 05 '13 at 14:36
  • 3
    i believe my answer is the more suited one, because he explicitly said he is using a "UITableViewController" – Peter Lapisu Apr 29 '13 at 14:47
  • This can be done with a UITableViewController as well by using scrollViewDidScroll. Suggesting someone to change the UITableViewController to UIViewController just accomplish that is just wrong. I still don – Julio Bailon Aug 22 '13 at 14:21
  • @JulioBailon It is not wrong to suggest a perfectly good solution even if there might be other solutions. The problem with your suggested idea is that the buttons can be moved around before the scroll view delegate method has a chance to put them back into position. To me, this is undesirable. I stand by my answer as the better option. – rmaddy Aug 22 '13 at 17:01
  • 1
    I disagree @rmaddy. That is not a good solution, it is just a workaround that could make someone to undo perfectly written code and make it into something that is unnecessary. There are solutions that don't interfere with how the class was declared and take 2 seconds to implement. – Julio Bailon Aug 23 '13 at 10:51
11

For swift :

override func scrollViewDidScroll(scrollView: UIScrollView){
    var frame: CGRect = self.floatingView.frame
    frame.origin.y = scrollView.contentOffset.y
    floatingView.frame = frame

    view.bringSubviewToFront(floatingView)
}

This function is called every time you scroll. Then inside you get the frame of your UIView and update its position depending on how much you scrolled. Your UIView is constantly moving with your UIScrollView so to the user it looks like its floating.

Esqarrouth
  • 38,543
  • 21
  • 161
  • 168
  • A litte bit more explanation would probably give this answer a little bit more up votes. Is it enough to put this in the UITableViewController class? Do I need any additional setup? What is the advantages of this solution? – Johan Karlsson Mar 18 '16 at 05:46
  • Added more explanation – Esqarrouth Mar 18 '16 at 07:45
9

you can add buttons on UIWindow no need to extend it to UIViewController instead of UITableviewController.Just add buttons on window.

UIWindow *window = [[UIApplication sharedApplication] keyWindow];
UIButton *btn =[UIButton buttonWithType:UIButtonTypeCustom];
[btn setFrame:CGRectMake(60, 200,40, 60)];
[btn addTarget:self action:@selector(btnClicked:) forControlEvents:UIControlEventTouchUpInside];
[window addSubview:btn];
ManiaChamp
  • 849
  • 7
  • 22
  • 1
    If you dont want to restructure your code , means if you used the tableviewcontroller already then no need to redo your work and use this code. but if you haven't done yet then you can go by the approach of view controller then add tableview and buttons. As answered by @Maddy. – ManiaChamp Dec 01 '13 at 06:23
  • Manish, your idea is fantastic. Note also the fantastic idea from Yossi. It's fantastic because it takes care of the height calculation in an easier way. Again Manish your idea is fantastic! Thanks! – Fattie Dec 01 '13 at 10:50
  • This method will work as long as you never add/remove another UIViewController on the window. If you remove UITableViewController, the btm button stays on the window. Once removed, tapping goToTop will cause a crash since it's target has been deallocated. See my answer for a better approach – GingerBreadMane Feb 25 '15 at 19:24
7

I've create a library in order to make it easier to add a floating button on top of a UITableView/UICollectionView/UIScrollView. Is called MEVFloatingButton.

These are the steps for use it.

1- Import category file

#import "UIScrollView+FloatingButton.h"

2 - Add delegate and the optional delegate methods.

  @interface ViewController () <MEVFloatingButtonDelegate>

  #pragma mark - MEScrollToTopDelegate Methods

  - (void)floatingButton:(UIScrollView *)scrollView didTapButton:(UIButton *)button;

2 - Create a MEVFloatingButtonobject.

MEVFloatingButton *button = [[MEVFloatingButton alloc] init];
button.animationType = MEVFloatingButtonAnimationFromBottom;
button.displayMode = MEVFloatingButtonDisplayModeWhenScrolling;
button.position = MEVFloatingButtonPositionBottomCenter;
button.image = [UIImage imageNamed:@"Icon0"];
button.imageColor = [UIColor groupTableViewBackgroundColor];
button.backgroundColor = [UIColor darkGrayColor];
button.outlineColor = [UIColor darkGrayColor];
button.outlineWidth = 0.0f;
button.imagePadding = 20.0f;
button.horizontalOffset = 20.0f;
button.verticalOffset = -30.0f;
button.rounded = YES;
button.hideWhenScrollToTop = YES;

4 - Assigned the button and delegate to the UITableView.

[self.tableView setFloatingButtonView:button];
[self.tableView setFloatingButtonDelegate:self]

Demo results

DemoDemoDemo

Manuel Escrig
  • 2,825
  • 1
  • 27
  • 36
2

there is a better solution for this. you can do this by disabling the Auto Layout(button.translatesAutoresizingMaskIntoConstraints = false) property of the corresponding Button or any UIView for floating button:

Swift 4

//create a button or any UIView and add to subview
let button=UIButton.init(type: .system)
button.setTitle("NEXT", for: .normal)
button.frame.size = CGSize(width: 100, height: 50)
self.view.addSubview(button)

//set constrains
button.translatesAutoresizingMaskIntoConstraints = false
if #available(iOS 11.0, *) {
     button.rightAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.rightAnchor, constant: -10).isActive = true
     button.bottomAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.bottomAnchor, constant: -10).isActive = true
} else {
     button.rightAnchor.constraint(equalTo: tableView.layoutMarginsGuide.rightAnchor, constant: 0).isActive = true
     button.bottomAnchor.constraint(equalTo: tableView.layoutMarginsGuide.bottomAnchor, constant: -10).isActive = true
}
Apfelsaft
  • 5,766
  • 4
  • 28
  • 37
manukn
  • 2,608
  • 1
  • 13
  • 17
1

I just saw WWDC 2011 TableView,Tips and Tricks videos. Floating views are exactly the same thing.All you have to do is change frame back to original position on scrollViewDidScroll.

https://developer.apple.com/videos/wwdc/2011/

Check videos TableViews tips and tricks.

user1010819
  • 2,691
  • 3
  • 15
  • 14
0

I want my class to be a UITableViewController subclass, and the subview needs to be a regular subview, using auto layout (if I want to).
So, here's what I did:

    private weak var _tableView:UITableView?
    override var tableView: UITableView!{
        get {
            return self._tableView
        }
        set {
            self._tableView = newValue
        }
    }

    override func viewDidLoad() {
        // Re-root
        let tableView = super.tableView! // keep a reference to the original tableview
        self.tableView = tableView
        view = UIView() // Create my own view
        tableView.translatesAutoresizingMaskIntoConstraints = true
        view.addSubview(tableView)
        tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
    }

You can now add whatever you want to the view of the view controller, while tableView is the original tableview.
I know the question was in Objective C, but the code is fairly simple, and I just copy pasted from my own code.

Moshe Gottlieb
  • 3,963
  • 25
  • 41