219
votes

The first screen of my application is a UITableViewController without a navigation bar, which means that the content flows under the status bar so there's a lot of text collisions. I've adjusted both the properties for Under top bars and Adjust scroll view insets which do actually stop it from scrolling under, but at the cost of keeping the top of the table view under. I've attempted to set the UITableView frame to offset by 20 pixels, but it doesn't appear to take effect and as I currently need the app to be compatible with iOS 6 I can't jump to iOS 7 Storyboards to force autolayout to use the top height guide. Has anyone found a solution that works for both versions?

Things I've tried: setting edgesForExtendedLayout, changing the settings within Storyboard for Under top bars and Adjust scroll view, forcing the frame to a new area.

A picture is worth a thousand words: Status bar flow under

27
A quick work-around might be to add a blank 20-pixel header to the table when running on iOS 7.EricS
@EricS: I already have a UITableView header in there, it also flows under the status bar.Nicholas Smith
Why not use the auto layout guide on iOS 6? It works.Steven Fisher

27 Answers

366
votes

For anyone interested in replicating this, simply follow these steps:

  1. Create a new iOS project
  2. Open the main storyboard and delete the default/initial UIViewController
  3. Drag out a new UITableViewController from the Object Library
  4. Set it as the initial view controller
  5. Feed the table some test data

If you follow the above steps, when you run the app, you will see that nothing, including tweaking Xcode's checkboxes to "Extend Edges Under {Top, Bottom, Opaque} Bars" works to stop the first row from appearing under the status bar, nor can you address this programmatically.

E.g. In the above scenario, the following will have no effect:

// These do not work
self.edgesForExtendedLayout=UIRectEdgeNone;
self.extendedLayoutIncludesOpaqueBars=NO;
self.automaticallyAdjustsScrollViewInsets=NO;

This issue can be very frustrating, and I believe it is a bug on Apple's end, especially because it shows up in their own pre-wired UITableViewController from the object library.

I disagree with everyone who is trying to solve this by using any form of "Magic Numbers" e.g. "use a delta of 20px". This kind of tightly coupled programming is definitely not what Apple wants us to do here.

I have discovered two solutions to this problem:

  • Preserving the UITableViewController's scene:
    If you would like to keep the UITableViewController in the storyboard, without manually placing it into another view, you can embed the UITableViewController in a UINavigationController (Editor > Embed In > Navigation Controller) and uncheck "Shows Navigation Bar" in the inspector. This solves the issue with no extra tweaking needed, and it also preserves your UITableViewController's scene in the storyboard.

  • Using AutoLayout and embedding the UITableView into another view (I believe this is how Apple wants us to do this):
    Create an empty UIViewController and drag your UITableView in it. Then, Ctrl-drag from your UITableView towards the status bar. As the mouse gets to the bottom of the status bar, you will see an Autolayout bubble that says "Top Layout Guide". Release the mouse and choose "Vertical Spacing". That will tell the layout system to place it right below the status bar.

I have tested both ways on an empty application and they both work. You may need to do some extra tweaking to make them work for your project.

86
votes

If you are doing things programatically and are using a UITableViewController without a UINavigationController your best bet is to do the following in viewDidLoad:

Swift 3

self.tableView.contentInset = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)

Earlier Swift

self.tableView.contentInset = UIEdgeInsetsMake(20.0f, 0.0f, 0.0f, 0.0f);

The UITableViewController will still scroll behind the status bar but won't be under it when scrolled to the top.

24
votes

Please note: This worked for me for the following configuration:

  • No navigation bar at the top of the screen (table view meets status bar)
  • Table view is non-scrollable

If the above two requirements aren't met your milage may vary.

Original Post

I created my view programmatically and this ended up working for me:

- (void) viewDidLayoutSubviews {
    // only works for iOS 7+
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) {
        CGRect viewBounds = self.view.bounds;
        CGFloat topBarOffset = self.topLayoutGuide.length;

        // snaps the view under the status bar (iOS 6 style)
        viewBounds.origin.y = topBarOffset * -1;

        // shrink the bounds of your view to compensate for the offset
        viewBounds.size.height = viewBounds.size.height + (topBarOffset * -1);
        self.view.bounds = viewBounds;
    }
}

Source (in topLayoutGuide section at bottom of pg.39).

20
votes

Adding to the top answer:

after the 2nd method did not initially seem to work I did some additional tinkering and have found the solution.

TLDR; the top answer's 2nd solution almost works, but for some versions of xCode ctrl+dragging to "Top Layout Guide" and selecting Vertical Spacing does nothing. However, by first adjusting the size of the Table View and then selecting "Top Space to Top Layout Guide" works


  1. Drag a blank ViewController onto the storyboard. View Controller

  2. Drag a UITableView object into the View. (Not UITableViewController). Position it in the very center using the blue layout guides.

Table ViewCenter Image

  1. Drag a UITableViewCell into the TableView. This will be your prototype reuse cell, so don't forget to set it's Reuse Identifier under the Attributes tab or you'll get a crash.

Add Table View CellReuse Identifier

  1. Create your custom subclass of UIViewController, and add the <UITableViewDataSource, UITableViewDelegate> protocols. Don't forget to set your storyboard's ViewController to this class in the Identity Inspector.

  2. Create an outlet for your TableView in your implementation file, and name it "tableView"

Create OutlettableView

  1. Right click the TableView and drag both the dataSource and the delegate to your ViewController.

dataSource delegate

Now for the part of not clipping into the status bar.

  1. Grab the top edge of your Table View and move it down to one of the dashed blue auto-layout guides that are near the top

resize

  1. Now, you can control drag from the Table View to the top and select Top Space to Top Layout Guide

Top Space to Top Layout Guide

  1. It will give you an error about ambiguous layout of TableView, just Add Missing Constraints and your done.

Add Missing Constraints

Now you can set up your table view like normal, and it won't clip the status bar!

11
votes
- (void) viewDidLayoutSubviews {
    if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1) {
        self.navigationController.navigationBar.barStyle = UIBarStyleBlackOpaque;
        if ([self respondsToSelector:@selector(edgesForExtendedLayout)])
            self.edgesForExtendedLayout = UIRectEdgeNone;   // iOS 7 specific
        CGRect viewBounds = self.view.bounds;
        CGFloat topBarOffset = self.topLayoutGuide.length;
        viewBounds.origin.y = topBarOffset * -1;
        self.view.bounds = viewBounds;
        self.navigationController.navigationBar.translucent = NO;
    }
}

https://developer.apple.com/library/ios/documentation/userexperience/conceptual/TransitionGuide/SupportingEarlieriOS.html#//apple_ref/doc/uid/TP40013174-CH14-SW1

8
votes

Select UIViewController on your storyboard an uncheck option Extend Edges Under Top Bars. Worked for me. : )

8
votes

This is how to write it in "Swift" An adjustment to @lipka's answer:

tableView.contentInset = UIEdgeInsetsMake(20.0, 0.0, 0.0, 0.0)
5
votes

For Xcode 7, un-ticking the 'translucent' check mark for the Navigation Bar worked for me.

enter image description here

3
votes

This will fix it for a UITableViewController (without any magic numbers). The only thing I couldn't get it to fix is if you are on a phone call, in which case the top of the tableView is pushed down too much. If anyone knows how to solve that, please let us know.

class MyTableViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()    
        configureTableViewTop()
    }

    override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
        coordinator.animateAlongsideTransition({ (context) -> Void in
            }, completion: { (context) -> Void in
                self.configureTableViewTop()
        })
    }

    func configureTableViewTop() { 
        tableView.contentInset.top = UIApplication.sharedApplication().statusBarFrame.height
    }
}
2
votes

I don't know how Kosher it is, but I found that this scheme moves the ViewController's view down and provides the status bar with a solid background:

- (void)viewDidLoad {
    [super viewDidLoad];

    // Shove everything down if iOS 7 or later
    float systemVersion = [[[UIDevice currentDevice] systemVersion] floatValue];
    if (systemVersion >= 7.0f) {

        // Move the view down 20 pixels
        CGRect bounds = self.view.bounds;
        bounds.origin.y -= 20.0;
        [self.view setBounds:bounds];

        // Create a solid color background for the status bar
        CGRect statusFrame = CGRectMake(0.0, -20.0, bounds.size.width, 20);
        UIView* statusBar = [[UIView alloc] initWithFrame:statusFrame];
        statusBar.backgroundColor = [UIColor redColor];
        [self.view addSubview:statusBar];
    }

Of course, replace redColor with whatever color you want for the background.

You must separately do one of the swizzles to set the color of the characters/symbols in the status bar. I use View controller-based status bar appearance = NO and Status bar style = Opaque black style in the plist, to do this globally.

Seems to work, and I'd be interested to hear of any bugs or issues with it.

2
votes

chappjc's answer works great when working with XIBs.

I found the cleanest solution when creating TableViewControllers programmatically is by wrapping the UITableViewController instance in another UIViewController and setting constraints accordingly.

Here it is:

UIViewController *containerLeftViewController = [[UIViewController alloc] init];
UITableViewController *tableViewController = [[UITableViewController alloc] init];

containerLeftViewController.view.backgroundColor = [UIColor redColor];

hostsAndMoreTableViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
[containerLeftViewController.view addSubview:tableViewController.view];

[containerLeftViewController addChildViewController:tableViewController];
[tableViewController didMoveToParentViewController:containerLeftViewController];

NSDictionary * viewsDict = @{ @"tableView": tableViewController.view ,
                              @"topGuide": containerLeftViewController.topLayoutGuide,
                              @"bottomGuide": containerLeftViewController.bottomLayoutGuide,
                              };
[containerLeftViewController.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[tableView]|"
                                                                                         options:0
                                                                                         metrics:nil
                                                                                           views:viewsDict]];
[containerLeftViewController.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topGuide][tableView][bottomGuide]"
                                                                                         options:0
                                                                                         metrics:nil
                                                                                           views:viewsDict]];

Cheers, Ben

2
votes

Works for swift 3 - In viewDidLoad();

let statusBarHeight = UIApplication.shared.statusBarFrame.height

let insets = UIEdgeInsets(top: statusBarHeight, left: 0, bottom: 0, right: 0)
tableView.contentInset = insets
tableView.scrollIndicatorInsets = insets

This code: 1. Gets the height of the status bar 2. Gives the top of the table view a content inset equal to the height of the status bar. 3. Gives the scroll indicator the same inset, so it appears below the status bar.

1
votes

I think the approach to using UITableViewController might be a little bit different from what you have done before. It has worked for me, but you might not be a fan of it. What I have done is have a view controller with a container view that points to my UItableViewController. This way I am able to use the TopLayoutGuide provided to my in storyboard. Just add the constraint to the container view and you should be taken care of for both iOS7 and iOS6.

1
votes

I ended up using one extra view with desired background, added after TableView and placed under status bar:

    self.CoverView = [[UIView alloc]init];

    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) {

    self.CoverView.frame = CGRectMake(0,0,self.view.bounds.size.width,20);
    }

    self.CoverView.backgroundColor = [UIColor whiteColor];
    self.TableView = [[UITableView alloc]initWithFrame:CGRectMake(0,
    self.CoverView.bounds.size.height,XXX, YYY)];
    [self.view addSubview:self.TableView];
    [self.view addSubview:self.CoverView];

It's not very pretty, but it's rather simple solution, if you need work with xib-less views, and both IOS6 and IOS7

1
votes

I have done this for Retina/Non-Retina display as

BOOL isRetina = FALSE;

if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
    if ([[UIScreen mainScreen] scale] == 2.0) {
        isRetina = TRUE;
    } else {
        isRetina = FALSE;
    }
}

if (isRetina) {
    self.edgesForExtendedLayout=UIRectEdgeNone;
    self.extendedLayoutIncludesOpaqueBars=NO;
    self.automaticallyAdjustsScrollViewInsets=NO;
}
1
votes

The following solution works well enough in code without using magic constants, and accounts for the user changing the size class, e.g. through rotations or side-by-side apps on ipads:

- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
    [super traitCollectionDidChange:previousTraitCollection];
    // Fix up content offset to ensure the tableview isn't underlapping the status bar.
    self.tableView.contentInset = UIEdgeInsetsMake(self.topLayoutGuide.length, 0.0, 0.0, 0.0);
}
1
votes

For those like me who would rather not embed their UITableViewController in a UIViewController try this:

A custom UITableViewController subclass can append a mask view to the tableView's superview. Add the mask on viewDidAppear, and remove the mask on viewWillDisappear.

private var statusBarMaskView: UIView!

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    if let superview = self.tableView.superview {
        self.statusBarMaskView = UIView(frame: CGRect.zero)
        superview.insertSubview(self.statusBarMaskView, aboveSubview: self.tableView)
        self.statusBarMaskView.backgroundColor = self.tableView.backgroundColor

        // using a nice constraint layout library to set the frame
        self.statusBarMaskView.pinTop(to: superview)
        self.statusBarMaskView.pinLeft(to: superview)
        self.statusBarMaskView.pinRight(to: superview)
        self.statusBarMaskView.addHeightConstraint(with: 22.0)
        ////
    }
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    self.statusBarMaskView.removeFromSuperview()
    self.statusBarMaskView = nil
}
0
votes

I had a UISearchBar at the top of my UITableView and the following worked;

self.tableView.contentInset = UIEdgeInsetsMake(20, 0, 0, 0);
self.tableView.contentOffset = CGPointMake(0, -20);

Share and enjoy...

0
votes

I got it work by setting size to freeform

enter image description here

0
votes

Abrahamchez's solution https://developer.apple.com/library/ios/qa/qa1797/_index.html worked for me as follows. I had a single UITableviewcontroller as my initial view. I had tried the offset code and embedding in a navcon but neither solved the statusbar transparency.

Add a Viewcontroller and make it the initial view. This should show you critical Top & Bottom Layout Guides.

Drag the old Tableview into the View in the new controller.

Do all the stuff to retrofit the table into the new controller:

Change your old view controller.h file to inherit/subclass from UIViewController instead of UITableViewController.

Add UITableViewDataSource and UITableViewDelegate to the viewcontroller's .h.

Re-connect anything needed in the storyboard, such as a Searchbar.

The big thing is to get the constraints set up, as in the Apple Q&A. I didn't bother inserting a toolbar. Not certain the exact sequence. But a red icon appeared on the Layout Guides, perhaps when I built. I clicked it and let Xcode install/clean up the constraints.

Then I clicked everywhere until I found the Vertical Space constraint and changed its top value from -20 to 0 and it worked perfectly.

0
votes

I am using a UISplitViewController with a navigationcontroller and a tableviewcontroller. This worked for me in the master view after trying many solutions here:

float systemVersion = [[[UIDevice currentDevice] systemVersion] floatValue];
if (systemVersion >= 7.0f) {

    // Move the view down 20 pixels
    CGRect bounds = self.view.bounds;
    bounds.origin.y -= 20.0;
    [self.navigationController.view setBounds:bounds];

    // Create a solid color background for the status bar
    CGRect statusFrame = CGRectMake(0.0, -20.0, bounds.size.width, 20);
    UIView* statusBar = [[UIView alloc] initWithFrame:statusFrame];
    statusBar.backgroundColor = [UIColor whiteColor];
    [statusBar setAlpha:1.0f];
    [statusBar setOpaque:YES];
    [self.navigationController.view addSubview:statusBar];
}

It's similar to Hot Licks' solution but applies the subview to the navigationController.

0
votes
override func viewDidLoad() {
 // your code

 if let nc = self.navigationController {
   yourView.frame.origin.y = nc.navigationBar.frame.origin.y + nc.navigationBar.frame.height
  }
}
0
votes

Here is a Swift 2.3. (Xcode 8.0) solution. I have created a subclass of UITableView.

class MYCustomTableView: UITableView
{   
    override func drawRect(rect: CGRect) {
        super.drawRect(rect)
        contentInset    = UIEdgeInsetsZero
    }
}

The content Inset should always be Zero (by default). I am setting it Zero manually. You can also add a check method which makes the check and if it is anything other than what you want it to be just get the correct rect. The change will reflect only when the tableview is drawn (which does not happen often).

Don't forget to update the TableView in your IB (in the TableViewController or just TableView inside your ViewController).

0
votes

I was facing this issue in ios 11 but layout was correct for ios 8 - 10.3.3 . For my case I set a Vertical Space Constraint to Superview.Top Margin instead of Superview.Top which works for ios 8 - 11.

enter image description here

0
votes

I found the easiest way to do this, especially if you're adding your table view inside of tab bar is to first add a view and then add the table view inside that view. This gives you the top margin guides you're looking for.

enter image description here

-1
votes

see all solutions: my project is just use xib, so, the solution with storyboard not worked for me. self.edgesForExtendedLayout = UIRectEdgeNone; just works for controller if navigationbar is visible. but if your view is just have status bar, that will not work. so i combine two conditons.

- (void) viewDidLayoutSubviews {
float systemVersion = [[[UIDevice currentDevice] systemVersion] floatValue];
if (systemVersion >= 7.0f) {
    CGRect bounds = self.view.bounds;
    if(self.navigationController == nil || self.navigationController.isNavigationBarHidden == YES){
        bounds.origin.y -= 20.0;
        [self.view setBounds:bounds];
    }
    else{
        self.edgesForExtendedLayout = UIRectEdgeNone;
    }
}

help this works.

-2
votes

If you also need to support iOS 6, you'll have to conditionally move it down. That is, in iOS 7 you should just move it down 20 points (either through frame manipulation or using auto-layout), and in iOS 6 you leave it alone. I don't believe you can do this in IB, so you'll have to do it in code.

EDIT

You can actually do this in IB, by using the iOS6/iOS7 deltas. Set your position in iOS 7, then for iOS 6 set the delta Y to -20points. See this SO question for more information.