ChatGPT解决这个技术问题 Extra ChatGPT

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.

https://i.stack.imgur.com/6JcDv.png

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

M
Mark A. Donohoe

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

Create a new iOS project Open the main storyboard and delete the default/initial UIViewController Drag out a new UITableViewController from the Object Library Set it as the initial view controller 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.


FYI, option 1 (embedding in a nav controller) results in the cells being visible behind the translucent statusbar when you scroll through the table.
@RyanR I noticed that too, but I think it's OK at some point. Apple did the same thing in mobile Safari's opened tabs view, tabs will be displayed underneath status bar. The navigation controller solution is the simplest workaround, I root for it. However, the translucent status bar is a stupid idea.
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...
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 :( :( )
@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...
J
Jay Lindelycke

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.


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.
Hardcoding height value isn't the best idea.
Yes, use topLayoutGuide length instead.
I'd use UIApplication.sharedApplication.statusBarFrame.size.height instead of 20.
That's been updated to UIApplication.shared.statusBarFrame.size.height these days.
S
Stunner

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


Interesting... I was able to call it like so in my project without any problem... I may have a simpler view layout structure than you did however. Your suggestion is correct, I'll make that change in my answer.
I happened to stumble across the issue when I rotated my app. If you had rotation disabled, you likely wouldn't have an issue.
I don't have rotation enabled in my project and I came across the problem, but yeah, still a good point.
For anyone implementing their own Container controller, this is the correct way of adjusting for the status or navigation bar underlapping!
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)
E
ErikAGriffin

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

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

https://i.stack.imgur.com/1MUxA.png

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.

https://i.stack.imgur.com/bdqtv.png

Create your custom subclass of UIViewController, and add the protocols. Don't forget to set your storyboard's ViewController to this class in the Identity Inspector. Create an outlet for your TableView in your implementation file, and name it "tableView"

https://i.stack.imgur.com/QraFO.png

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

https://i.stack.imgur.com/uoxoL.png

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

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

https://i.stack.imgur.com/RmFgN.png

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

https://i.stack.imgur.com/ECOJ6.png

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

https://i.stack.imgur.com/qsWRy.png

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


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


switch <= to > and remove the empty if block
Reading self.topLayoutGuide.length from viewDidLayoutSubviews will screw up the layout of child views.
@OLzh while doing so as you describes, self.tableView cannot scroll.
P
Pau Ballada

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


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.
The only one solution that works for me, ty. (Note you have to "shorten" table view under the status bar in IB and update constraints).
Worked for me on Xcode 8 with a UITableViewController.
u
uplearned.com

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)

Isn't swift allowed in here?
Only solution that worked for me, that doesn't make me create a UIVIewController
This is less than ideal because it keeps the top inset at 20, even in landscape where there is no default status bar in ios8
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); } }
D
David T

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

https://i.stack.imgur.com/NcxZF.png


Simplest, quickest, easiest, safest? solution. Don't know why this has so few upvotes compared to everything else.
E
Elijah

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

H
Hot Licks

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.


b
bhr

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


W
Wesh RaphLola

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.


P
Phong Le

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.


l
lilislilit

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


M
MaxEcho

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

q
qix

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

S
Sam T

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


f
fengd

I got it work by setting size to freeform

https://i.stack.imgur.com/sJdQZ.png


The "Simulated Metrics" applies only to seeing your creation in Xcode, not reality. [See: stackoverflow.com/questions/16279379/…
J
Jack Bellis

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.


s
sfr14

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.


This is the only answer that has worked for me in iOS 8!
T
Tobi Nary
override func viewDidLoad() {
 // your code

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

D
Darkwonder

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


D
DareDevil

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.

https://i.stack.imgur.com/VewVs.png


L
Lee Irvine

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
}

N
NSCoder

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.

https://i.stack.imgur.com/jBnU1.png


p
phnix

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.


C
Community

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.


Setting the frame down doesn't change it unfortunately.
What doesn't work about it? The table view doesn't move down?
Yeah, it remains in the same position. I've tried self.view.frame and self.tableView.frame, in both viewWillAppear and viewDidLoad. To be safe I also set setContentNeedsDisplay to force a redraw.
Is auto-layout enabled?
Yeah, it's autolayout and Storyboard.