Programmatically get height of navigation bar - ios

I know that the presence of the more view controller (navigation bar) pushes down the UIView by its height. I also know that this height = 44px. I have also discovered that this push down maintains the [self.view].frame.origin.y = 0.
So how do I determine the height of this navigation bar, other than just setting it to a constant?
Or, shorter version, how do I determine that my UIView is showing with the navigation bar on top?
The light bulb started to come on. Unfortunately, I have not discovered a uniform way to correct the problem, as described below.
I believe that my whole problem centers on my autoresizingMasks. And the reason I have concluded that is the same symptoms exist, with or without a UIWebView. And that symptom is that everything is peachy for Portrait. For Landscape, the bottom-most UIButton pops down behind the TabBar.
For example, on one UIView, I have, from top to bottom:
UIView – both springs set (default case) and no struts
UIScrollView - If I set the two springs, and clear everything else (like the UIView), then the UIButton intrudes on the object immediately above it. If I clear everything, then UIButton is OK, but the stuff at the very top hides behind the StatusBar Setting only the top strut, the UIButton pops down behind the Tab Bar.
UILabel and UIImage next vertically – top strut set, flexible everywhere else
Just to complete the picture for the few that have a UIWebView:
UIWebView - Struts: top, left, right Springs: both
UIButton – nothing set, i.e., flexible everywhere
Although my light bulb is dim, there appears to be hope.
Please bear with me because I needed more room than that provided for a short reply comment.
Thanks for trying to understand what I am really fishing for ... so here goes.
1) Each UIViewController (a TabBar app) has a UIImage, some text and whatever on top. Another common denominator is a UIButton on the bottom. On some of the UIViewControllers I have a UIWebView above the UIButton.
So, UIImage, text etc. UIWebView (on SOME) UIButton
Surrounding all the above is a UIScrollView.
2) For those that have a UIWebView, its autoresizingMask looks like:
—
|
—
^
|
|
|—| ←----→ |—|
|
|
V
The UIButton's mask has nothing set, i.e., flexible everywhere
Within my -viewDidLoad, I call my -repositionSubViews within which I do the following:
If there is no UIWebView, I do nothing except center the UIButton that I placed with IB.
If I do have a UIWebView, then I determine its *content*Height and set its frame to enclose the entire content.
UIScrollView *scrollViewInsideWebView = [[webView_ subviews] lastObject];
webViewContentHeight = scrollViewInsideWebView.contentSize.height;
[webView_ setFrame:CGRectMake(webViewOriginX, webViewOriginY,
sameWholeViewScrollerWidth, webViewContentHeight)]
Once I do that, then I programmatically push the UIButton down so that it ends up placed below the UIWebView.
Everything works, until I rotate it from Portrait to Landscape.
I call my -repositionSubViews within my -didRotateFromInterfaceOrientation.
Why does the content height of my UIWebView not change with rotation?.
From Portrait to Landscape, the content width should expand and the content height should shrink. It does visually as it should, but not according to my NSLog.
Anyway, with or without a UIWebView, the button I've talked about moves below the TabBar when in Landscape mode but it will not scroll up to be seen. I see it behind the TabBar when I scroll "vigorously", but then it "falls back" behind the TabBar.
Bottom line, this last is the reason I've asked about the height of the TabBar and the NavigationBar because the TabBar plants itself at the bottom of the UIView and the NavigationBar pushes the UIView down.
Now, I'm going to add a comment or two here because they wouldn't have made sense earlier.
With no UIWebView, I leave everything as is as seen by IB.
With a UIWebView, I increase the UIWebView's frame.height to its contentHeight and also adjust upward the height of the surrounding UIScrollView that surrounds all the sub-views.
Well there you have it.

Do something like this ?
NSLog(#"Navframe Height=%f",
self.navigationController.navigationBar.frame.size.height);
The swift version is located here
UPDATE
iOS 13
As the statusBarFrame was deprecated in iOS13 you can use this:
extension UIViewController {
/**
* Height of status bar + navigation bar (if navigation bar exist)
*/
var topbarHeight: CGFloat {
return (view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0.0) +
(self.navigationController?.navigationBar.frame.height ?? 0.0)
}
}

With iPhone-X, height of top bar (navigation bar + status bar) is changed (increased).
Try this if you want exact height of top bar (both navigation bar + status bar):
UPDATE
iOS 13
As the statusBarFrame was deprecated in iOS13 you can use this:
extension UIViewController {
/**
* Height of status bar + navigation bar (if navigation bar exist)
*/
var topbarHeight: CGFloat {
return (view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0.0) +
(self.navigationController?.navigationBar.frame.height ?? 0.0)
}
}
Objective-C
CGFloat topbarHeight = ([UIApplication sharedApplication].statusBarFrame.size.height +
(self.navigationController.navigationBar.frame.size.height ?: 0.0));
Swift 4
let topBarHeight = UIApplication.shared.statusBarFrame.size.height +
(self.navigationController?.navigationBar.frame.height ?? 0.0)
For ease, try this UIViewController extension
extension UIViewController {
/**
* Height of status bar + navigation bar (if navigation bar exist)
*/
var topbarHeight: CGFloat {
return UIApplication.shared.statusBarFrame.size.height +
(self.navigationController?.navigationBar.frame.height ?? 0.0)
}
}
Swift 3
let topBarHeight = UIApplication.sharedApplication().statusBarFrame.size.height +
(self.navigationController?.navigationBar.frame.height ?? 0.0)

Swift version:
let navigationBarHeight: CGFloat = self.navigationController!.navigationBar.frame.height

iOS 14
For me, view.window is null on iOS 14.
extension UIViewController {
var topBarHeight: CGFloat {
var top = self.navigationController?.navigationBar.frame.height ?? 0.0
if #available(iOS 13.0, *) {
top += UIApplication.shared.windows.first?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
} else {
top += UIApplication.shared.statusBarFrame.height
}
return top
}
}

Swift 5
If you want to get the navigation bar height, use the maxY property that considers the safeArea size as well, like this:
let height = navigationController?.navigationBar.frame.maxY

Support iOS 13 and Below:
extension UIViewController {
var topbarHeight: CGFloat {
if #available(iOS 13.0, *) {
return (view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0.0) +
(self.navigationController?.navigationBar.frame.height ?? 0.0)
} else {
let topBarHeight = UIApplication.shared.statusBarFrame.size.height +
(self.navigationController?.navigationBar.frame.height ?? 0.0)
return topBarHeight
}
}
}

Did you try this?
let barHeight = self.navigationController?.navigationBar.frame.height ?? 0

UIImage*image = [UIImage imageNamed:#"logo"];
float targetHeight = self.navigationController.navigationBar.frame.size.height;
float logoRatio = image.size.width / image.size.height;
float targetWidth = targetHeight * logoRatio;
UIImageView*logoView = [[UIImageView alloc] initWithImage:image];
// X or Y position can not be manipulated because autolayout handles positions.
//[logoView setFrame:CGRectMake((self.navigationController.navigationBar.frame.size.width - targetWidth) / 2 , (self.navigationController.navigationBar.frame.size.height - targetHeight) / 2 , targetWidth, targetHeight)];
[logoView setFrame:CGRectMake(0, 0, targetWidth, targetHeight)];
self.navigationItem.titleView = logoView;
// How much you pull out the strings and struts, with autolayout, your image will fill the width on navigation bar. So setting only height and content mode is enough/
[logoView setContentMode:UIViewContentModeScaleAspectFit];
/* Autolayout constraints also can not be manipulated since navigation bar has immutable constraints
self.navigationItem.titleView.translatesAutoresizingMaskIntoConstraints = false;
NSDictionary*metricsArray = #{#"width":[NSNumber numberWithFloat:targetWidth],#"height":[NSNumber numberWithFloat:targetHeight],#"margin":[NSNumber numberWithFloat:20]};
NSDictionary*viewsArray = #{#"titleView":self.navigationItem.titleView};
[self.navigationItem.titleView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|-(>margin=)-H:[titleView(width)]-(>margin=)-|" options:NSLayoutFormatAlignAllCenterX metrics:metricsArray views:viewsArray]];
[self.navigationItem.titleView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[titleView(height)]" options:0 metrics:metricsArray views:viewsArray]];
NSLog(#"%f", self.navigationItem.titleView.width );
*/
So all we actually need is
UIImage*image = [UIImage imageNamed:#"logo"];
UIImageView*logoView = [[UIImageView alloc] initWithImage:image];
float targetHeight = self.navigationController.navigationBar.frame.size.height;
[logoView setFrame:CGRectMake(0, 0, 0, targetHeight)];
[logoView setContentMode:UIViewContentModeScaleAspectFit];
self.navigationItem.titleView = logoView;

Handy Swift 4 extension, in case it's helpful to someone else. Works even if the current view controller does not display a navigation bar.
import UIKit
extension UINavigationController {
static public func navBarHeight() -> CGFloat {
let nVc = UINavigationController(rootViewController: UIViewController(nibName: nil, bundle: nil))
let navBarHeight = nVc.navigationBar.frame.size.height
return navBarHeight
}
}
Usage:
UINavigationController.navBarHeight()

The light bulb started to come on. Unfortunately, I have not discovered a uniform way to correct the problem, as described below.
I believe that my whole problem centers on my autoresizingMasks. And the reason I have concluded that is the same symptoms exist, with or without a UIWebView. And that symptom is that everything is peachy for Portrait. For Landscape, the bottom-most UIButton pops down behind the TabBar.
For example, on one UIView, I have, from top to bottom:
UIView – both springs set (default case) and no struts
UIScrollView -
If I set the two springs, and clear everything else (like the UIView), then the UIButton intrudes on the object immediately above it.
If I clear everything, then UIButton is OK, but the stuff at the very top hides behind the StatusBar
Setting only the top strut, the UIButton pops down behind the Tab Bar.
UILabel and UIImage next vertically – top strut set, flexible everywhere else
Just to complete the picture for the few that have a UIWebView:
UIWebView -
Struts: top, left, right
Springs: both
UIButton – nothing set, i.e., flexible everywhere
Although my light bulb is dim, there appears to be hope.

My application has a couple views that required a customized navigation bar in the UI for look & feel, however without navigation controller. And the application is required to support iOS version prior to iOS 11, so the handy safe area layout guide could not be used, and I have to adjust the position and height of navigation bar programmatically.
I attached the Navigation Bar to its superview directly, skipping the safe area layout guide as mentioned above. And the status bar height could be retrieved from UIApplication easily, but the default navigation bar height is really a pain-ass...
It struck me for almost half a night, with a number of searching and testing, until I finally got the hint from another post (not working to me though), that you could actually get the height from UIView.sizeThatFits(), like this:
- (void)viewWillLayoutSubviews {
self.topBarHeightConstraint.constant = [UIApplication sharedApplication].statusBarFrame.size.height;
self.navBarHeightConstraint.constant = [self.navigationBar sizeThatFits:CGSizeZero].height;
[super viewWillLayoutSubviews];
}
Finally, a perfect navigation bar looking exactly the same as the built-in one!

Here is the beginning of my response to your update:
Why does the content height of my UIWebView not change with rotation?.
Could it be that because your auto resize doesn't have the autoresizingMask for all directions?
Another suggestion before I come back for this, could you use a toolbar for your needs. It's a little simpler, will always be on the bottom, auto-rotates/positions. You can hide/show it at will etc. Kind of like this: http://cdn.artoftheiphone.com/wp-content/uploads/2009/01/yellow-pages-iphone-app-2.jpg
You may have looked at that option, but just throwing it out there.
Another idea, could you possibly detect what orientation you are rotating from, and just place the button programmatically to adjust for the tab bar. (This is possible with code)

I have used:
let originY: CGFloat = self.navigationController!.navigationBar.frame.maxY
Working great if you want to get the navigation bar height AND its Y origin.

If you want to get the navigationBar height only, it's simple:
extension UIViewController{
var navigationBarHeight: CGFloat {
return self.navigationController?.navigationBar.frame.height ?? 0.0
}
}
However, if you need the height of top notch of iPhone you don't need to get the navigationBar height and add to it the statusBar height, you can simply call safeAreaInsets that's why exist.
self.view.safeAreaInsets.top

Swift : programmatically adding a web view right under the navigation bar
From iOS11 the key to position the view below the navigation bar is to use safeAreaLayoutGuide
From the Apple docs (link):
The layout guide representing the portion of your view that is unobscured by bars and other content.
So in code I will put the top constraint using view.safeAreaLayoutGuide.topAnchor
Again the whole thing will be for example:
import WebKit
class SettingsViewController: UIViewController {
let webView = WKWebView()
let url = URL(string: "https://www.apple.com")
override func viewDidLoad() {
super.viewDidLoad()
configureWebView()
}
override func viewWillAppear(_ animated: Bool) {
follow(url: url)
}
func follow(url: URL?) {
if let url = url {
let request = URLRequest(url: url)
webView.load(request)
}
}
func configureWebView() {
view.addSubview(webView)
webView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
}

Related

Swift: Moving a tab bar controller to top of view controller

Currently my tab bar controller is at the bottom of the view controller. I was wondering if there is a way to move it to the top of the view controller as I cant seem to find any documentation on it.
Swift 3
let rect = self.tabBar.frame;
self.tabBar.frame = rect.insetBy(dx: 0, dy: -view.frame.height + self.tabBar.frame.height + (self.navigationController?.navigationBar.frame.height)!)
Swift 5 | Xcode 11
Try overriding viewWillLayoutSubviews() in your class that extends UITabBarController and then put following:-
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
self.tabBar.invalidateIntrinsicContentSize()
var tabSize: CGFloat = 44.0;
var orientation: UIInterfaceOrientation = UIApplication.shared.statusBarOrientation
if (orientation.isLandscape) {
tabSize = 32.0;
}
var tabFrame: CGRect = self.tabBar.frame;
tabFrame.size.height = tabSize;
tabFrame.origin.y = self.view.frame.origin.y;
self.tabBar.frame = tabFrame;
// Set the translucent property to false then back to true to
// force the UITabBar to reblur, otherwise part of the
// new frame will be completely transparent if we rotate
// from a landscape orientation to a portrait orientation.
self.tabBar.isTranslucent = false;
self.tabBar.isTranslucent = true;
}
Reference: https://stackoverflow.com/a/29580094/6117565
You can't change position on UITabbar. Read Apple documentation for tabbar.
If you want to set effect like tabbar on top of viewController then You can manage that by using one uiview of same size of tabbar and multiple uibuttons in that view which works as tabs. And set that view at top or any position at which you want to show tabbar. You have to manage viewcontrollers displays on button click and manage that view to every view controller. So it is very difficult task, so it is better to use default tabbarController provided by system.
You can try using UIViewController instead of UITabBarController and then place TabBar control on top in your view by constraints.
Another way is create your own tabbar. (:

Get frame height without navigation bar height and tab bar height in deeper view hierarchy

I have a ViewController (B) which is handled by a PageViewController which is inside another ViewController (A) and open it modal. I have centered the ViewController (B) exactly in the middle of the screen. This works fine.
But when I push the ViewController (A) from a NavigationController the frame of ViewController (B) is to big and extends below the NavigationBar and the TabBar. But I want it centered between the NavigationBar and the TabBar.
I know how I can get the height of the navbar and the tabbar so I could resize ViewController (B):
var topBar = self.navigationController?.navigationBar.frame.height
var bottomBar = self.tabBarController?.tabBar.frame.height
But this does not work deep down in the vier hierarchy in ViewController (B). self.navigationController and self.tabBarController are nil. So how can I get the height of the navbar and tabbar deeper down in the view hierarchy? Or do I just have to pass it down from one ViewController to another till I reach ViewController (B)? Or is there another more obvious way to center the view? Thanks.
(I have tried to post screenshots for better understanding but I miss the needed reputation points to post images, sorry)
Use this :
let height = UIApplication.sharedApplication().statusBarFrame.height +
self.navigationController!.navigationBar.frame.height
this support portrait and landscape state.
Swift 5
let height = UIApplication.shared.statusBarFrame.height +
self.navigationController!.navigationBar.frame.height
For UITabBar, this code work :
self.tabBarController?.tabBar.frame.height
For Swift 3
navBarHeight = (self.navigationController?.navigationBar.intrinsicContentSize.height)!
+ UIApplication.shared.statusBarFrame.height
As for iOS 13, since the statusBarFrame property of a UIApplication object was deprecated, one can use:
let statusBarHeight = navigationController?.view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
let navigationBarHeight = navigationController?.navigationBar.frame.height ?? 0
Now, it may seem that doesn't help you #OP since your navigationController is nil, but you could try to use view.window?.windowScene?.statusBarManager?.statusBarFrame.height for the statusBar height and figure out a way to find the navBar height as well? Hope that helps :)
iOS 13 solution:
extension UIViewController {
/**
* Height of status bar + navigation bar (if navigation bar exist)
*/
var topbarHeight: CGFloat {
let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
return (window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0) +
(self.navigationController?.navigationBar.frame.height ?? 0.0)
}
}
To call it you can just use: self.topbarHeight in viewController
Navigation bar height status bar height and tab bar height is always constant :
Navigation bar - 44pts
Status bar - 20pts
Tab bar - 49pts.
You can directly subtract these constants from view height:
self.view.frame.size.height- (44+20+49)

UITableView goes under translucent Navigation Bar

I am trying to have a transparent navigation bar in IOS 7 app. There is a full screen image in my application. I am also having a UITableView over that image. When I use the code below, image fits the screen as I want but UITableView goes under navigation bar.
in viewDidLoad
i use
self.navigationController.navigationBar.shadowImage = [UIImage new];
self.navigationController.navigationBar.translucent = YES;
self.navigationController.view.backgroundColor = [UIColor clearColor];
it is being ok when I change to self.navigationController.navigationBar.translucent = NO; but then I lose transparency at navigation bar.
You could set the contentInsets of your tableView so it is initially below the navigation bar, but would scroll behind it (content would be overlapping)
self.tableView.contentInset = UIEdgeInsetsMake(44,0,0,0);
Or you could offset the frame of the tableview. Then the scrolling content would be cut off below the navigation bar (which wouldn't look good, too)
I my case helped this one (modified version of Bill Chan's code):
Objective C version:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
CGRect rect = self.navigationController.navigationBar.frame;
float y = rect.size.height + rect.origin.y;
self.tableView.contentInset = UIEdgeInsetsMake(y, 0, 0, 0);
}
The point is that table have to be pushed down for the height of navigationBar (rect.size.height) plus status bar height (rect.origin.y);
Swift version (also compatible with Swift 2):
override func viewDidLayoutSubviews() {
if let rect = self.navigationController?.navigationBar.frame {
let y = rect.size.height + rect.origin.y
self.tableView.contentInset = UIEdgeInsetsMake( y, 0, 0, 0)
}
}
I had the similar problem for iOS 9. When I first open viewController, tableView is under top bar. Then after scrolling tableView everything works fine.
Select your view controller
Click the 'Attributes Inspector' tab
Uncheck 'Under Top Bars'
Set the y-position of tableview to height of the navigation bar plus height of the status bar (let it be height)
i.e,
height = 64; // height of navigation bar = 44(In portait), height of status bar = 20
tableView.frame = CGRectMake(tableView.frame.origin.x, height , tableView.frame.size.width, tableView.frame.size.height);
If you are using autolayout just change the update the tableView top constraint instead of changing frame.
and also change viewController automaticallyAdjustsScrollViewInsets to NO
self.automaticallyAdjustsScrollViewInsets = NO;
If you are supporting different orientation update frame and contentInset to (52) because navigation bar height in landscape mode is 32.
check this Sample
This is working in both landscape mode and portrait mode in iOS8:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
CGRect rect = self.navigationController.navigationBar.frame;
float y = -rect.origin.y;
self.tableView.contentInset = UIEdgeInsetsMake(y ,0,0,0);
}
Better not to hardcode the Inset values as it might based on the orientation of the device.
Code:
func setTableViewContentInset() {
let contentInsetHeight = topLayoutGuide.length
let contentInset = UIEdgeInsetsMake(contentInsetHeight, 0, 0, 0)
tableView.contentInset = contentInset
tableView.scrollIndicatorInsets = contentInset
}
func scrollToTop() {
if tableView.indexPathsForVisibleRows?.count > 0 {
let topIndexPath = NSIndexPath(forRow: 0, inSection: 0)
tableView.scrollToRowAtIndexPath(topIndexPath, atScrollPosition: .Top, animated: false)
}
}
func scrollToTopOfVisibleCells() {
if let visibleIndexPaths = tableView.indexPathsForVisibleRows where tableView.indexPathsForVisibleRows?.count > 0 {
let topMostVisibleIndexPath = visibleIndexPaths[0]
tableView.scrollToRowAtIndexPath(topMostVisibleIndexPath, atScrollPosition: .Top, animated: false)
}
}
//MARK: Load Views
override func viewDidLoad() {
super.viewDidLoad()
setTableViewContentInset()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
scrollToTop()
}
//MARK: Trait collection change
override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
setTableViewContentInset()
scrollToTopOfVisibleCells()
}
Solutions that introduce a magic constant don't scale most of the time. For example, if the next iPhone introduces a different navigation bar height we'll have to update our code.
Fortunately, Apple provided us cleaner ways of overcoming this issue, for example topLayoutGuide:
The topLayoutGuide property comes into play when a view controller is
frontmost onscreen. It indicates the highest vertical extent for
content that you don't want to appear behind a translucent or
transparent UIKit bar (such as a status or navigation bar)
Programmatically you can achieve with the following code snippet (the same can be achieved via IB too):
override func viewDidLoad() {
super.viewDidLoad()
automaticallyAdjustsScrollViewInsets = false
tableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.topAnchor.constraint(equalTo:
topLayoutGuide.bottomAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
Note: topLayoutGuide is deprecated in iOS 11, we should use the safeAreaLayoutGuide property of UIView instead.
Introduction
I am new to both iOS development and Stack Overflow, so forgive me if my post isn't perfect.I also had this issue, and when I used the content insets for my UITableView it worked perfectly upon loading first, or when visiting it from my other tabs; however, if I navigated back to the view, it would have the extra "padding". I figured out a work around, so that my UITableView will be correctly placed every time.
The Issue
When you first load the UITableView, or tab to it, it needs the insets to correctly start the table below the navigation bar, but when you navigate back it does not need the insets, because for some reason, it correctly calculates for the placement of the UITableView. This is why you can get the extra padding.
The Solution
The solution involves using a boolean to determine whether you have navigated away, so that it can correctly determine whether it needs the content insets or not.In -(void)viewDidLoad I set hasNavigatedFurther = NO. Then:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (!hasNavigatedFurther) {
self.tableView.contentInset = UIEdgeInsetsMake(64, 0, 0, 0);
} else {
self.tableView.contentInset = UIEdgeInsetsZero;
//In order to allow visiting between tabs and retaining desired look
hasNavigatedFurther = NO;
}
}
In order to make this work, you need to set hasNavigatedFurther = YES just before your code that pushes another view onto the navigation stack.
-(void)btnTouched:(id)sender {
hasNavigatedFurther = YES;
NextViewController* nvc = [NextViewController new];
[self.navigationController pushViewController:nvc animated:YES];
}
I came up with the following solution, which, on navigating to the view controller for the first time, adjusts the table view's contentInset for the navigation bar's height, taking into account any padding that the top cell might have. When returning to this view controller after pushing another view controller onto the stack, I then re-adjust the contentInset to UIEdgeInsetsZero:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self adjustEdgeInsetsForTableView];
}
- (void)adjustEdgeInsetsForTableView {
if(self.isMovingToParentViewController) {
self.tableViewForm.contentInset = UIEdgeInsetsMake(self.navigationController.navigationBar.frame.size.height + padding, 0, 0, 0);
} else {
self.tableViewForm.contentInset = UIEdgeInsetsZero;
}
}
I combined #Adam Farrell and #Tash Pemhiwa 's solutions, and finally the code below works for me:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self adjustEdgeInsetsForTableView];
}
- (void)adjustEdgeInsetsForTableView
{
if(self.isMovingToParentViewController) {
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);
} else {
self.tableView.contentInset = UIEdgeInsetsMake(64, 0, 0, 0);
}
}
Hope this will help people who waste couple of hours on this weird UI behavior.
Constrain the table view to the bottom of the navigation bar. The table view will automatically be offset by 44, but then in code we can just do this:
tableView.contentInset = UIEdgeInsets(top: -44, left: 0, bottom: 0, right: 0)
The bar is transparent and has no color, but the table view does not overlap it at all. Notice the word "Hook" gets cut off despite the navigation bar being transparent. This will only work of you constrain the table view top edge to be 0 from the navigation bar. NOT 0 from the top view.
All you need is love this:
assert(tableView.contentInsetAdjustmentBehavior == .automatic)
there is zero need to do ugly magic constants beardance from iOS 11 onwards
I did not even need to set contentInsetAdjustmentBehavior to .none
to fix navbar underlapping.
.automatic
worked automagically
try to use layoutguide to fix
var constraints = [NSLayoutConstraint]()
let guide = view.safeAreaLayoutGuide
constraints.append(self.tableView.leadingAnchor.constraint(equalTo: guide.leadingAnchor))
constraints.append(self.tableView.trailingAnchor.constraint(equalTo: guide.trailingAnchor))
constraints.append(self.tableView.topAnchor.constraint(equalTo: guide.topAnchor))
constraints.append(self.tableView.bottomAnchor.constraint(equalTo: guide.bottomAnchor))

How to get the height of the Navigation bar

I am developing my first iPhone application. I am quite new to iOS. I am trying to get the height of the UI Navigation bar using the code below below the viewDidLoad method
float navBarHeight = self.navigationController.navigationBar.frame.size.height;
But the navBarHeight is returning 0. Not sure why that is.
Might be your using custom UIView for navigation or your hiding NavigationBar in your code i.e.,why it showing height is 0.If you set below code in your viewWillAppear you will get height of NavigationBar.
[self.navigationController setNavigationBarHidden:NO];
Try this extension (considering iPhone-X)
extension UIViewController {
/**
* Height of status bar + navigation bar (if navigation bar exist)
*/
var topbarHeight: CGFloat {
return UIApplication.shared.statusBarFrame.size.height +
(self.navigationController?.navigationBar.frame.height ?? 0.0)
}
}
Try :
print(self.navigationController?.navigationBar.frame.height)

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