Why the 20px gap at the top of my UIViewController?
The behaviour you are seeing is not a bug at all but merely a side effect of your misuse of adding views into a hierarchy.
When you add the tableView into a view you need to tell UIKit how you want that tableView to size relative to its parent. You have two options: Auto-Layout or Autoresizing Masks. Without describing how you want your view to layout UIKit
simply pops it onto the hierarchy and the default implementation will lay your view under the top layout guide (which just happens to be the height of the status bar). Something as simple as this would do the trick:
tableVC.View.Frame = rootVC.View.BoundstableVC.View.Autoresizingmask = UIViewAutoresizing.FlexibleWidthtableVC.View.TranslatesAutoresizingMaskIntoConstraints = true// always nice to explicitly use auto layout
This behavior is not actually exclusive to UITableViewController
but also to UICollectionViewController
. I believe their default viewLoading implementation insets the view under the status bar. If we make our childController a simple UIViewController
subclass none of this behaviour is exhibited. Don't see this as a bug though, if you explicitly declare how you want their respective views to be laid out you won't have this issue. Naturally, this is the primary function of a container controller.
Heres what your appDelegate should look like:
Xamarin
public override bool FinishedLaunching(UIApplication app, NSDictionary options){ window = new UIWindow(UIScreen.MainScreen.Bounds); var tableVC = new UITableViewController(); var rootVC = new UIViewController(); var navigationVC = new UINavigationController(intermediateView); navigationVC.View.BackgroundColor = UIColor.Red; rootVC.View.BackgroundColor = UIColor.Green; tableVC.View.BackgroundColor = UIColor.Blue; rootVC.AddChildViewController(tableVC); rootVC.View.AddSubview(tableVC.View); //YOU NEED TO CONFIGURE THE VIEWS FRAME //If you comment this out you will see the green view under the status bar tableVC.View.Frame = rootVC.View.Bounds tableVC.View.Autoresizingmask = UIViewAutoresizing.FlexibleWidth tableVC.View.TranslatesAutoresizingMaskIntoConstraints = true tableVC.DidMoveToParentViewController(rootVC); window.RootViewController = navigationVC; window.MakeKeyAndVisible(); return true;}
Swift
var window: UIWindow?var navigationControlller: UINavigationController!func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { let rootVC = UIViewController(nibName: nil, bundle: nil) let tableVC = UITableViewController(style: .Plain) let navVC = UINavigationController(rootViewController: rootVC) navVC.navigationBarHidden = true navVC.view.backgroundColor = UIColor.redColor() rootVC.view.backgroundColor = UIColor.greenColor() rootVC.view.addSubview(tableVC.view) //YOU NEED TO CONFIGURE THE VIEWS FRAME //If you comment this out you will see the green view under the status bar tableVC.view.frame = rootVC.view.bounds tableVC.view.autoresizingMask = .FlexibleWidth | .FlexibleHeight tableVC.view.setTranslatesAutoresizingMaskIntoConstraints(true) rootVC.addChildViewController(tableVC) tableVC.didMoveToParentViewController(rootVC) window = UIWindow(frame: UIScreen.mainScreen().bounds) window?.rootViewController = navVC window?.makeKeyAndVisible() return true}
Note I wrote a post recently describing the ways you can configure a view to fill its superview
Your problem seems to relate to some kind of fundamental issues with UITableViewController
being directly added as a child controller of another.
Looking around this is not a new issue: iOS 7: UITableView shows under status bar
Relative to that article, I took your code and tried the following options (once I realized it was in C# 8^)):
Instead of using a
UITableViewController
, add aUIViewController
and then add aUITableView
as a child of theUIViewController
.If you do this then there is no 20pt issue. This highlights that theproblem is with theUITableViewController
class.This approach is a relatively clean workaround and only a couple ofextra steps on what you have already.
I did try adding constraints in your original code to force theUITableViewController frame to the top, but could not get this towork. Again this could be down to the table controller overridingthings itself.
If you build your demo using storyboards, everything works. This Ibelieve is down to the fact that IB itself uses a container view toembed the new view controller. So if you use storyboard, the wayapple does it is to add a view which you can set the frame of usingconstraints and it then embeds the
UITableViewController
insidethat view via an embed Segue.Hence as per 1), using a view in the middle seems to solve the issueand again it seems that having control over the middle views
frame
is key.I notice in your only viable workaround, that changing the frame wasthe answer. However post iOS7, changing the frame does not seem tobe recommended due to the issues it can have clashing withconstraints which also want to manipulate the frame.
- Trying other options like
edgesForExtendedLayout
all seemed tofail. These seem to be hints for container view controllers andUITableViewController is ignoring them.
IMHO I think option 1) seems the safest approach as you have total control over the layout and are not fighting the system with frame overrides which may cause you issues later. Option 2) only really works if you use storyboards. You could try doing the same thing manually yourself, but who knows what goes on in an embed Segue.
EDIT
It would seem that there was a missing step as highlighted by Arkadiusz Holko in his answer and setting the frame explicitly for the table view does fix the issue.
You missed one step when adding a child view controller – setting up its view's frame.
See the second step:
- (void) displayContentController: (UIViewController*) content;{ [self addChildViewController:content]; // 1 content.view.frame = [self frameForContentController]; // 2 [self.view addSubview:self.currentClientView]; [content didMoveToParentViewController:self]; // 3}