UIViewController status bar top inset - ios

I'm using SCPageViewController in my app:
class RootPageViewController: UIViewController {
var pageViewController : SCPageViewController = SCPageViewController()
var viewControllers = [UIViewController]()
......
in viewDidLoad I'm configuring it:
self.pageViewController.setLayouter(SCPageLayouter(), animated: false, completion: nil)
self.pageViewController.easingFunction = SCEasingFunction(type: SCEasingFunctionType.linear)
self.pageViewController.dataSource = self;
self.pageViewController.delegate = self;
self.addChildViewController(self.pageViewController)
self.pageViewController.view.frame = self.view.bounds
self.view.addSubview(self.pageViewController.view)
self.pageViewController.didMove(toParentViewController: self)
I'm setting size of content view controllers in SCPageViewController data source method to be size of RootPageViewController. When app launches, I have my "status bar" that is just plain UIView with background color behind system status bar. But when I'm scrolling to next/previous page, it is offset by few pixels. How I can unify that? Why I'm getting this small offset on top?
I'm using Storyboards to model view controllers:
This is how it looks after app launches (as expected):
And when I scroll to next page:
This white area shouldn't be there... Everything is fine after I rotate device.

You could change the originY for the view to move it up a bit:
yourView.frame.origin.y = -5
BUT you should take a look at your constraints and see what happens to them when you change view and see if you could solve it that way instead. Because the solution provided above will move up the view, so you will have - 5 pixels at the bottom.

Related

What is a good way to add UIPageViewController to parent UIViewController without considering status bar height?

Currently, I have a UIViewController, with its top component consists of a horizontal UICollectionView (MenuTabsView.swift)
Now, I would like to add a UIPageViewController, just below the MenuTabsView.
I have tried the following few approaches.
Programatically without taking status bar height into consideration
func presentPageVCOnView() {
self.pageController = storyboard?.instantiateViewController(withIdentifier: "PageControllerVC") as! PageControllerVC
self.pageController.view.frame = CGRect.init(x: 0, y: menuBarView.frame.maxY, width: self.view.frame.width, height: self.view.frame.height - menuBarView.frame.maxY)
self.addChildViewController(self.pageController)
self.view.addSubview(self.pageController.view)
self.pageController.didMove(toParentViewController: self)
}
Here's the outcome.
From 1st glance, it seems that UIPageViewController's view need to offset by Y status bar distance. (But why?)
Programatically by taking status bar height into consideration
func presentPageVCOnView() {
let statusBarHeight = CGFloat(20.0)
self.pageController = storyboard?.instantiateViewController(withIdentifier: "PageControllerVC") as! PageControllerVC
self.pageController.view.frame = CGRect.init(x: 0, y: menuBarView.frame.maxY + statusBarHeight, width: self.view.frame.width, height: self.view.frame.height - menuBarView.frame.maxY - statusBarHeight)
self.addChildViewController(self.pageController)
self.view.addSubview(self.pageController.view)
self.pageController.didMove(toParentViewController: self)
}
Now, it looks way better.
Use container view without status bar offset
But, I don't feel comfortable, on why we need to manually consider status bar height, during programatically way. I was thinking, maybe I can add a ContainerView to UIViewController, and "attach" the UIPageViewController's view to it?
(I am not sure why during adding Container View to storyboard, an additional UIViewController will be added along. Anyhow, I just manually delete the additional UIViewController)
Then, I use the following code to "attach" the UIPageViewController's view to new container view.
func presentPageVCOnView() {
self.pageController = storyboard?.instantiateViewController(withIdentifier: "PageControllerVC") as! PageControllerVC
self.pageController.view.frame = containerView.frame
self.addChildViewController(self.pageController)
self.view.addSubview(self.pageController.view)
self.pageController.didMove(toParentViewController: self)
}
But, the outcome is not what as expected. Y offset still happen!!!
Use container view with status bar offset
I try to make sure, there are space of 20, between the top component MenuTabsViews and UIPageViewController's view.
I was wondering, is there any good practice/ solution, to ensure we can add UIPageViewController's view below another component, without affecting by status bar height?
You can do this all without any code -- it just takes an understanding of how UIContainerView works.
There's no real UIContainerView class... it is an automated way of adding a child view controller via Storyboard / Interface Builder. When you add a UIContainerView, IB automatically creates a "default" view controller connected to the container view with an Embed segue. You can change that default controller.
Here's step-by-step (images are large, so you'll probably want to click them to see the details)...
Start with a fresh UIViewController:
Add your "Menu Bar View" - I have it constrained Top/Leading/Trailing to safe-area, Height of 60:
Drag a UIContainerView onto the view - note that it creates a default view controller at the current size of the container view. Also note that it shows a segue. If you inspect that segue, you'll see it is an Embed segue:
Constrain the Top of the container view to the Bottom of your Menu Bar View, and Leading/Trailing/Bottom to safe-area. Notice that the size of the embedded view controller automatically takes the new size of the container view:
Select that default controller... and delete it:
Drag a new UIPageViewController onto your Storyboard and set its Custom Class to PageControllerVC:
Now, Ctrl-Click-Drag from the Container view to the newly added page view controller. When you release the mouse button, select Embed from the popup:
You now have an Embed segue from the container view to your page view controller. Notice that it automatically adjusted its size to match the container view size:
Since the Menu Bar View top is constrained to the safe-area, it will behave as expected.
Since the container view top is constrained to the bottom of the Menu Bar View, it will stay there, and should give you what you want.
No Code Needed :)
Edit
The most likely reason you ran into trouble with loading via code is with you frame setting.
If you try to set frames in viewDidLoad(), for example, auto-layout has not configured the rest of the view hierarchy... so framing will not be what you expect.
You're much better off using auto-layout / constraints, rather than setting explicit frames anyway.
Here is how I would do it from code (assumes you have your "Menu Bar View" connected via #IBOutlet):
class ViewController: UIViewController {
#IBOutlet var menuBarView: UIView!
var pageControllerVC: PageControllerVC?
override func viewDidLoad() {
super.viewDidLoad()
guard let vc = storyboard?.instantiateViewController(withIdentifier: "PageControllerVC") as? PageControllerVC else {
fatalError("Could not instantiate PageControllerVC!!!")
}
guard let v = vc.view else {
fatalError("loaded PageControllerVC had no view ????")
}
addChild(vc)
view.addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
v.topAnchor.constraint(equalTo: menuBarView.bottomAnchor),
v.leadingAnchor.constraint(equalTo: g.leadingAnchor),
v.trailingAnchor.constraint(equalTo: g.trailingAnchor),
v.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])
vc.didMove(toParent: self)
self.pageControllerVC = vc
}
}
You should remove safeArea pinning for pageVC.
Safe area includes status bar and iPhone 11+ top space
tabBar.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor)
// to this
tabBar.topAnchor.constraint(equalTo: self.view.topAnchor)
And in storyboards change Safe Area to SuperView

Content behind custom UINavigationBar?

I created a UINavigationController and a UIViewController. With this setup the view looks like this:
This is what I want. The content "Test Text" does not appear behind the status bar.
Now I created my own UINavigationBar in the UIViewControllers:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: false)
let newNavBar:UINavigationBar = UINavigationBar(frame: CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 64))
newNavBar.tintColor = UIColor.whiteColor()
newNavBar.translucent = true
self.view.addSubview(newNavBar)
}
The result is the following:
The content is behind the new navigation bar.
How can I prevent the content from being behind the new navigation bar?
When you add Navigation Bar in view controller then NavigationBar will take 64pixel ( 20 pixel status bar and 44 pixel navigation bar ) so you need to start your layout after 64pixel from top.
If your content is a UIScrollView, use the contentInsets property to adjust where your content starts, this makes the scrollview be able to scroll the specified amount of pixels "too far", and thus show your content immediately below your custom UINavigationBar.
If your content is not a UIScrollview, then just offset the content with a constraint (you could link it to an IBOutlet, so you can change it if you dynamically change from the old to the new UINavigationBar).

Views not displaying correctly with storyboard

I created a page based application which already has several views. RootViewController is responsible for setting up the app layout, and it does everything well except for one thing, the view size.
Here's part of what I have
// Configure the page view controller and add it as a child view controller
self.pageViewController = UIPageViewController(
transitionStyle: .Scroll,
navigationOrientation: .Horizontal,
options: nil
);
self.pageViewController.delegate = self;
// Setup the initial page
let startingViewController = self.modelController.viewControllerWithProfileProvider(
self.profileProviderPreference(),
storyboard: self.storyboard
);
self.pageViewController.setViewControllers(
[startingViewController as BrowsingViewControllerBase] as [AnyObject],
direction: UIPageViewControllerNavigationDirection.Forward,
animated: false,
completion: { done in }
);
self.pageViewController.dataSource = self.modelController;
self.addChildViewController(self.pageViewController);
self.view.addSubview(pageViewController.view);
// Set the page view controller's bounds using an inset rect so that self's view is visible around the edges of the pages
self.pageViewController.view.frame = self.view.bounds;
// Notify the page view controller that we just moved to it. This is a framework imposition. See the docs.
self.pageViewController.didMoveToParentViewController(self);
// Add the page view controller's gesture recognizers to the view controller's view so that the gestures are started more easily.
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
All other code in the controllers handles behaviour, not aspect, so I setup everything else using Storyboard. The viewControllerWithProfileProvider: method is returning the proper view, which gets presented on screen, but not correctly. Here's what I have in storyboard:
I then change the colour and label for the button according to the provider given (ie., Facebook, Twitter, LinkedIn...). But I get this:
Notice how the button is off the screen. I would assume that self.pageViewController.view.frame = self.view.bounds; is the key line for this, but I'm not sure what to use. How can I have my views display correctly?
Control Click the box and drag up and leave from the popup select Top space to top layout.
do the same on both right and left side and select leading space and trailing space respectively. Run your app hopefully this helps
I think you need to use , I suffered with the same But Autolayout Rescue me from this.

Content pushed down in a UIPageViewController with UINavigationController

UPDATE 2
I've been running and testing my app in the iOS Simulator using a 4-inch device. If I run using a 3.5-inch device the label doesn't jump. In my .xib, under Simulated Metrics, I have it set as Retina 4-inch Full Screen. Any idea why I'm only seeing this problem on a 4-inch device?
UPDATE 1
In IB, if I choose "Navigation Bar" in Simulated Metrics, my label still jumps. The only way I can get my label to render correctly on the first screen is to not set a navigation controller as my window's root view controller.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
My window's rootViewController is being set to a UINavigationController whose rootViewController has a UIPageViewController embedded.
When my app loads, the initial view is presented with it's content pushed down a bit, roughly the same size as a navigation bar. When I scroll the pageViewController, the content jumps up to where it was placed in the nib, and all other viewControllers loaded by the pageViewController are fine.
In my appDelegate:
self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[ContainerViewController new]];
In ContainerViewController:
- (void)viewDidLoad {
[super viewDidLoad];
self.pvc = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options:nil];
self.pvc.dataSource = self;
self.pvc.delegate = self;
DetailViewController *detail = [DetailViewController new];
[self.pvc setViewControllers:#[detail]
direction:UIPageViewControllerNavigationDirectionForward
animated:false
completion:nil];
[self addChildViewController:self.pvc];
[self.view addSubview:self.pvc.view];
[self.pvc didMoveToParentViewController:self];
}
So I'm adding another answer after further development and I finally think I figured out what's going on. Seems as though in iOS7, UIPageViewController has its own UIScrollView. Because of this, you have to set automaticallyAdjustsScrollViewInsets to false. Here's my viewDidLoad now:
- (void)viewDidLoad
{
[super viewDidLoad];
self.automaticallyAdjustsScrollViewInsets = false;
DetailViewController *detail = [[DetailViewController alloc] init];
[self setViewControllers:#[detail]
direction:UIPageViewControllerNavigationDirectionForward
animated:false
completion:nil];
}
No need to put anything in viewWillLayoutSubviews (as one of my previous answers suggested).
This is definitely being caused by automaticallyAdjustsScrollViewInsets, as other posters (including #djibouti33). However, this property is strange in two ways:
It must be set on a UINavigationController. If you set it on a child controller that's managed by a UINavigationController, it won't have any effect. 1
It only applies when a scroll view is at index zero in a controller's subviews. 2
These two caveats should explain the intermittent problems experienced by others in the thread.
TLDR: A workaround that I went with is adding a dummy view to the UIPageViewController at index zero, to avoid the setting applying to the scrollView within the page controller, like this:
pageViewController.view.insertSubview(UIView(), atIndex: 0) // swift
[pageViewController.view insertSubview: [UIView new] atIndex: 0]; // obj-c
Better would be to set the contentInset on the scroll view yourself, but unfortunately the UIPageViewController doesn't expose the scroll view.
Just uncheck Under Top Bars for both: UIPageViewController and your custom PageContentViewController:
My original answer solved the problem for the time being, but after a while the same problem came back to bite me.
Using the following viewController hierarchy:
-- UINavigationController
-- MyPageViewController
-- MyDetailViewController
Here's what I did to solve it:
In MyPageViewController.m
#interface MyPageViewController () <delegates>
#property (strong) MyDetailViewController *initialViewController;
#end
- (void)viewDidLoad
{
...
// set this once, because we're going to use it in viewWillLayoutSubviews,
// which gets called multiple times
self.initialViewController = [MyDetailViewController new];
}
// the problem seemed to stem from the fact that a pageViewController couldn't
// properly lay out it's child view controller initially if it contained a
// scroll view. by the time we're in layoutSubviews, pageViewController seems to
// have gotten it's bearings and everything is laid out just fine.
- (void)viewWillLayoutSubviews
{
[self setViewControllers:#[self.initialViewController]
direction:UIPageViewControllerNavigationDirectionForward
animated:false
completion:nil];
}
I had a similar problem but none of the solutions here worked. My problem was that whenever I would scroll to the next page, the content would jump down, ending in the correct position, but starting 20 pixels too high (clearly something to do with the status bar). My container VC was not a nav VC. After pulling my hair out for a while, the solution that ended up working for me was just to make sure that none of the constraints in my content VC were connected to the top layout guide. This may or may not be feasible in your case, but in mine it was, and it was the only thing that solved the content jump. Also very curiously, this problem only manifested when the transition style was set to scroll. Just changing it to page curl made the issue disappear. But I needed scroll. Hope this helps someone else.
I have the same problem. I solve it by putting setViewControllers for the first page in UIPageViewController's viewDidLoad instead of setting it when I make a instance of UIPageViewController. Also, I need to set automaticallyAdjustsScrollViewInsets to NO.
Try unchecking these 2 options on your storyboard
Try to select PageViewController in storyboard and uncheck "Under Bottom Bars" and "Under Opaque Bars" in Attributes Inspector.
Initially my view controller hierarchy looked like this:
-- UINavigationController
-- MyContainerViewController
-- UIPageViewController
-- MyDetailViewController
I set it up this way so MyContainerViewController could manage a toolbar. I narrowed my problem down to MyContainerViewController, and then it occurred to me that I don't even need it if I subclass UIPageViewController. Now my hierarchy looks like this:
-- UINavigationController
-- MyPageViewController
-- MyDetailViewController
MyPageViewController manages it's toolbar, and everything works as expected, both on a 4-inch and 3.5-inch device.
As stated by "Bo:": Putting self.edgesForExtendedLayout = UIRectEdgeNone; in the viewDidLoad of MyPageViewController solved the problem. – Bo
this is my first time posting on stack overflow, but I have searching for a solution to this problem for over a week now.
Here is a solution I came up with, I hope this works for anyone else with the same issue.
I'm not sure how you are initializing your frame for your detail view controller, but I am going to assume you might use: self.view.frame.size.height;
try using:
self.view.frame.size.height -= self.navigationController.navigationBar.bounds.size.height;
Hope this helps
I'm seeing the same issue as described by #Danny on iOS 9. I tried updating all my constraints to that they are not constrained to the margins, but it didn't fix the issue. I ended up having to adopt a hack similar to this one as follows;
For each content page to be displayed in the UIPageViewController, find the top-most constraint, the one between the Top of a view and the bottom of the top layout guide, and add an outlet for it to the view controller.
In each view controller with such an outlet, add another property for the preferred top distance. The two outlets look like this (in Swift):
#IBOutlet weak var topGuideConstraint: NSLayoutConstraint!
var topDistance: CGFloat!
In viewDidLoad(), set topDistance to the value assigned to the constraint in the storyboard:
override func viewDidLoad() {
super.viewDidLoad()
topDistance = topGuideConstraint.constant
}
In viewWillLayoutSubviews(), make sure the constraint has the proper value, adjusting for the height of the status bar when the topLayoutGuide.length is zero, which seems to be the case during the transition, but not once it's complete:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
topGuideConstraint.constant = topDistance + (
topLayoutGuide.length == 0
? UIApplication.sharedApplication().statusBarFrame.size.height
: 0
)
}
Repeat for every content view controller displayed in the UIPageViewController. Adjust the offset as appropriate if you're also displaying a UINavigation bar.
This is an unfortunate hack, and I hate having to do it, but after many hours trying different things, I'm at least happy to have something that works so I can move on.
As #djibouti33 already posted:
a pageViewController couldn't
properly lay out it's child view controller initially if it contained a
scroll view. by the time we're in layoutSubviews, pageViewController seems to have gotten it's bearings and everything is laid out just fine
By waiting for layoutSubViews to load before setting any viewControllers to the UIPageViewController was the only thing that worked for me.
override func viewDidLoad() {
super.viewDidLoad()
self.pageViewController = self.storyboard?.instantiateViewController(withIdentifier: "yourPageViewController") as? UIPageViewController
self.pageViewController?.dataSource = self
pageViewController?.automaticallyAdjustsScrollViewInsets = false
self.pageViewController?.view.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.height)
self.addChildViewController(self.pageViewController!)
self.view.addSubview((self.pageViewController?.view)!)
self.pageViewController?.didMove(toParentViewController: self)
}
override func viewDidLayoutSubviews() {
let startVC = self.viewControllerAtIndex(index: 0) as infoDataViewController
let viewControllers = NSArray(object: startVC)
self.pageViewController?.setViewControllers(viewControllers as? [UIViewController], direction: .forward, animated: true, completion: nil)
}
None of above worked for me
Here I found the solution
var pageMenu : CAPSPageMenu?
Instead of adding like this
self.view.addSubview(pageMenu!.view)
Add your CAPSPageMenu like below
addChildViewController(pageMenu!)
self.view.addSubview(pageMenu!.view)
pageMenu!.didMove(toParentViewController: self)
Reference: iOS Swift : SlidingMenuView wrong Position after presenting imagePicker
Happy Coding!
Swift's version of djibouti33's answer (excluding the line that is not part of the problem resolution)
Swift 3.0
override func viewDidLoad() {
super.viewDidLoad()
self.automaticallyAdjustsScrollViewInsets = false
}

Black bar flashes at top of UITableView when pushing to view with "Hides Bottom Bar When Pushed" in IB

This is a weird error that may just be an issue in Xcode for all I know. I have a tab bar controller where the first view is a UITableView with (obviously) a number of cells. When you select a cell, I've set up a segue on the MainStoryboard to go to a detail view controller. I want the tab bar to be hidden when I go to the detail view, so I went into the storyboard, chose my detail view, and clicked "Hides Bottom Bar on Push" in the editor screen that starts with "Simulated Metrics."
Everything works just fine, except that when I tap on a cell, a black bar flashes at the top of the UITableView screen, dropping the tableview cells down (as if the cells are falling down below the tab bar at the bottom), just before the screen pushes over to the detail view. The effect isn't harmful at all, but it's very disconcerting, and I'd like to smooth that out.
The only fix I've found is to uncheck the "Hides Bottom Bar when Pushed" option on the storyboard. That indeed does get rid of that black bar flash, but of course the tab bar stays on the screen when I go to the detail view, which is what I don't want.
Any ideas?
Just for completeness' sake, I went ahead and ran
[self.navigationController setToolbarHidden:YES animated: YES];
on the detail view controller's viewWillAppear method (and even tried it with the storyboard option both on and off), but there was no difference. The toolbar did indeed hide just fine, but I still got that black line at the top. So weird.
I know it is too late !!! I ran into same issue. It seems like the Auto resizing mask for the view was incorrect to be exact the UIViewAutoresizingFlexibleTopMargin. I checked this on in the xib file. If you are trying to do it in code make sure this flag -UIViewAutoresizingFlexibleTopMargin - is not included in the autoresizing mask.
Hope this will help some one in the future
I know it is a bit late, but I have same problem and I can't solve it with any of the previous answers. (I suppose this is the reason non was accepted).
The problem is that view size of the SecondViewController is same as view size of a previous ViewController, so too small to fit in a ViewController with Toolbar hidden. Thats why black background of a UITabBarController is visible at the top when transition is happening, and on a viewDidAppear view will stretch on right size.
For me it help to subclass root UITabBarController and set background color to same background color as SecondViewController has.
class RootViewController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = Style.backgroundColor
}
}
Then you can leave checkbox checked inside storyboard and it will look ok.
P.S.
If you have some views, that is position on the bottom part of the view, you need to set bottom constraints so they are smaller by 49 (because this is the height of the toolbar), and then on viewDidAppear set the right constraint.
For example:
I have view that need to be position 44 px from bottom edge. Before, I have constraint set to 44 and I have some strange behaviour of that view. It was placed to height and then jump on the right place.
I fix this with setting constraint to -5 (44-49), and then in viewDidAppear set the constraint back to 44. Now I have normal behaviour of that view.
Wow I just had the same issue now, very painful, and no info on the net about it.
Anyway, a simple workaround for me was to change the current view's Frame moving the y coordinates up and making the height bigger by the height of the tab bar. This fixed the problem if done straight after pushing the new view onto the navigation controller. Also, there was no need to fix the Frame afterwards (it must be updated when the view is shown again).
MonoTouch code:
UIViewController viewControllerToPush = new MyViewController();
viewControllerToPush.HidesBottomBarWhenPushed = true; // I had this in the MyViewController's constructor, doesn't make any difference
this.NavigationController.PushViewController(viewControllerToPush, true);
float offset = this.TabBarController.TabBar.Frame.Height;
this.View.Frame = new System.Drawing.RectangleF(0, -offset, this.View.Frame.Width, this.View.Frame.Height + offset);
Objective C code (untested, just a translation of the monotouch code):
UIViewController *viewControllerToPush = [MyViewController new];
viewControllerToPush.hidesBottomBarWhenPushed = YES; viewControllerToPush.hidesBottomBarWhenPushed = YES;
float offset = self.tabBarController.tabBar.frame.size.height; float offset = self.tabBarController.tabBar.frame.size.height;
self.view.frame = CGRectMake(0, -offset, self.view.frame.width, self.view.frame.height + offset); self.view.frame = CGRectMake(0, -offset, self.view.frame.size.width, self.view.frame.size.height + offset);
Do this in viewWillAppear of detailViewController, it should work fine
subclass your navigation controller, or just find the navigation bar
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let backdropEffectView = navigationBar.subviews[0].subviews[0].subviews[0] //_UIBackdropEffectView
let visualEffectView: UIVisualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .Light))
visualEffectView.frame = backdropEffectView.frame
backdropEffectView.superview?.insertSubview(visualEffectView, aboveSubview: backdropEffectView)
backdropEffectView.removeFromSuperview()
}

Resources