iOS 7: UITableView shows under status bar
Asked Answered
U

27

218

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

Urogenital answered 19/9, 2013 at 16:40 Comment(3)
A quick work-around might be to add a blank 20-pixel header to the table when running on iOS 7.Forgetmenot
@EricS: I already have a UITableView header in there, it also flows under the status bar.Urogenital
Why not use the auto layout guide on iOS 6? It works.Enchase
C
365

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.

Canice answered 23/9, 2013 at 4:28 Comment(5)
FYI, option 1 (embedding in a nav controller) results in the cells being visible behind the translucent statusbar when you scroll through the table.Dovekie
When I do the control-drag from the tableview up, I dont ever run into the status bar. There is no visible status bar in the view, not sure what you mean here...Coldshoulder
Option 2 no longer works in Xcode 5. The described action (ctrl-drag) should never have worked, since that is owned by the Outlets system, but even if you manually drag to the Scene Tree on left, top layout guide DOES NOT WORK (it should do, AFAICT - I believe this is yet another major bug in AutoLayout :( :( )Assimilate
@PrideChung it looks stupid when using a header with the tableview, the header stays fixed at the top and the elements will disappear behind - and reapear behind the transparent status bar...Kulsrud
@Assimilate It works in Xcode 6, you should choose "Top Space to Top Layout Guide", and change the constant to 0Fresnel
L
86

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.

Linesman answered 17/10, 2013 at 10:46 Comment(6)
You could add a check for OS version > 7 and support older OSes too (no inset for those). Otherwise this worked well for my purposes.Rodrich
Hardcoding height value isn't the best idea.Auer
Yes, use topLayoutGuide length instead.Linesman
I'd use UIApplication.sharedApplication.statusBarFrame.size.height instead of 20.Hamil
That's been updated to UIApplication.shared.statusBarFrame.size.height these days.Nonessential
i think -20 should be thereIsooctane
C
24

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

Cubby answered 24/9, 2013 at 8:21 Comment(5)
I happened to stumble across the issue when I rotated my app. If you had rotation disabled, you likely wouldn't have an issue.Bawdry
If you change the origin, but not the height of the view bounds, won't the bottom be cropped off by the bottom edge of the screen by the amount you changed the origin? (so it becomes impossible to see the last few rows of pixels)Erastatus
Yes, the bottom of the view will be cropped, merely shrink the height by topBarOffset * -1 as well.Cubby
I did this on a tableview controller inside a navigation controller and it does nothing for me but stutter, incorrectly position the initial scroll location, and not leave enough empty space at the topMarna
Further tests: Setting bounds.size.height = bounds.size.height - 20 will loop you to a negative height real quick. Don't know how this worked for anyone.Marna
R
20

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!

Rabin answered 27/12, 2014 at 23:27 Comment(0)
K
11
- (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

Knocker answered 26/10, 2013 at 20:8 Comment(3)
switch <= to > and remove the empty if blockLewison
Reading self.topLayoutGuide.length from viewDidLayoutSubviews will screw up the layout of child views.Spinescent
@OLzh while doing so as you describes, self.tableView cannot scroll.Brawl
T
8

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

Trahan answered 3/10, 2013 at 15:19 Comment(1)
Alas, didn't work for me. Completely default UITableViewController still overlaps status bar. I don't think the status bar counts as a Top Bar. Just nav bars and tab bars.Pisciform
P
8

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)
Pneumonectomy answered 12/1, 2015 at 21:12 Comment(2)
This is less than ideal because it keeps the top inset at 20, even in landscape where there is no default status bar in ios8Jule
This variation handles any configuration well for me: - (void)viewDidLayoutSubviews { if (self.tableView.contentInset.top != self.topLayoutGuide.length) { self.tableView.contentInset = UIEdgeInsetsMake(self.topLayoutGuide.length, 0.0, 0.0, 0.0); } }Cassycast
D
5

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

enter image description here

Dicho answered 20/2, 2016 at 11:33 Comment(0)
B
3

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
    }
}
Bivins answered 29/7, 2016 at 23:42 Comment(0)
W
2

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.

Whet answered 22/1, 2014 at 22:8 Comment(0)
N
2

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

Nobell answered 16/5, 2014 at 22:52 Comment(0)
W
2

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.

Women answered 11/2, 2017 at 11:7 Comment(0)
G
1

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.

Gasparo answered 22/9, 2013 at 22:3 Comment(0)
J
1

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

Jumbo answered 16/11, 2013 at 10:53 Comment(0)
T
1

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;
}
Told answered 26/11, 2013 at 11:51 Comment(0)
C
1

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);
}
Cassycast answered 21/7, 2016 at 2:21 Comment(0)
H
0

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

Hybrid answered 22/11, 2013 at 21:43 Comment(0)
M
0

I got it work by setting size to freeform

enter image description here

Merilynmeringue answered 22/12, 2013 at 16:9 Comment(1)
The "Simulated Metrics" applies only to seeing your creation in Xcode, not reality. [See: #16279879Caty
G
0

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.

Gastrectomy answered 4/7, 2014 at 17:58 Comment(0)
F
0

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.

Forthwith answered 23/1, 2015 at 6:3 Comment(0)
L
0
override func viewDidLoad() {
 // your code

 if let nc = self.navigationController {
   yourView.frame.origin.y = nc.navigationBar.frame.origin.y + nc.navigationBar.frame.height
  }
}
Libretto answered 24/2, 2016 at 10:41 Comment(0)
H
0

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

Hillock answered 6/10, 2016 at 10:9 Comment(0)
K
0

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

Ketose answered 26/9, 2017 at 19:27 Comment(0)
S
0

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
}
Sldney answered 15/10, 2018 at 1:7 Comment(0)
M
0

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

Micropaleontology answered 14/2, 2020 at 20:45 Comment(0)
D
-1

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.

Deathday answered 4/7, 2015 at 3:16 Comment(0)
W
-2

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.

Windermere answered 19/9, 2013 at 19:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.