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:
This question is related to
ios
ios7
uistatusbar
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
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.
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.
Create an outlet for your TableView in your implementation file, and name it "tableView"
Now for the part of not clipping into the status bar.
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:
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.
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
- (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;
}
}
Source: Stackoverflow.com