[ios] iOS 7: UITableView shows under status bar

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

This question is related to ios ios7 uistatusbar

The answer is


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

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.


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)

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.


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).


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

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;
}

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.


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.


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 View Center 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 Cell Reuse 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 Outlet tableView

  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!


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


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).


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
}

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


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...


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


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.


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.


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.


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.


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


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


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

enter image description here


override func viewDidLoad() {
 // your code

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

I got it work by setting size to freeform

enter image description here


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