How iOS UITableView under NavigationBar? - ios

I have set
NavigationController.NavigationBar.Translucent = true;
Then add table and set frame to RootView Frame, and:
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
float y = this.TopLayoutGuide.Length;
table.ContentInset = new UIEdgeInsets (y, 0, 0, 0);
}
But, I Have table Scroll Bar under NavigationBar (I use monotouch):

Just put
navigationBar.translucent = NO; you problem will solve :)
Other option is,,
Put following code.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if([self respondsToSelector:#selector(edgesForExtendedLayout)])
[self setEdgesForExtendedLayout:UIRectEdgeBottom];
}
Another option is ..
Why does UIViewController extend under UINavigationBar, while UITableViewController doesn't?

Try this:
if([self respondsToSelector:#selector(edgesForExtendedLayout)])
{
self.edgesForExtendedLayout = UIRectEdgeNone;
self.automaticallyAdjustsScrollViewInsets = NO;
}

I solved task with this simple code:
table.ScrollIndicatorInsets = new UIEdgeInsets(64, 0, 0, 0);

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 on iOS 11, we should use the safeAreaLayoutGuide property of UIView instead.

This worked for me
let yOffset = UIApplication.shared.statusBarFrame.height + self.navigationController!.navigationBar.frame.size.height
tableView.contentInset = UIEdgeInsetsMake(yOffset, 0, 0, 0)

Related

UISearchBarController iOS 11 issue - SearchBar and scope buttons overlap

Referred here and here. No answer in first link. In the second link, though the answer is not accepted, but the link to apple developer forum gives error.
Before iOS 11 :
iOS 11 :
Note : Same device same code.
Also, this would mean, all apps using this feature have to be republished ?
Adding these lines fixed it for me:
override func viewDidLayoutSubviews() {
self.searchController.searchBar.sizeToFit()
}
I can get the initial appearance to display correctly in iOS11 using the following code (as per greg's answer):
[self.searchController.searchBar sizeToFit];
if (#available(iOS 11.0, *)) {
self.navigationItem.searchController = self.searchController;
self.navigationItem.hidesSearchBarWhenScrolling = NO;
} else {
// Fallback on earlier versions
self.tableView.tableHeaderView = self.searchController.searchBar;
}
However, if the app is backgrounded then restored while the search bar was active, the appearance would end up overlapped as shown in Nitish's second screenshot above.
I was able to fix that with the following workaround:
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
self.searchController.searchBar.showsScopeBar = NO;
[self.searchController.searchBar sizeToFit];
self.searchController.searchBar.showsScopeBar = YES;
[self.searchController.searchBar sizeToFit];
}];
(I'm still working on how to workaround the layout issues following an interface orientation change while the search bar is active - that still ends up overlapped.)
In the radar that Ray Wenderlich filed, #benck posted this answer from WWDC, which, if I'm not mistaken, hasn't been posted yet.
Per your comments, your UISearchController's UISearchBar has been assigned to your UITableView's tableHeaderView. In iOS 11, you should instead be assigning your UISearchController to the searchController property of your view's navigationItem. You no longer need to assign the UISearchBar anywhere. See Apple's documentation on this new property.
I met the same issue on my app, my solution is in iOS 11, using apple suggested new way for searchBar which is in navigationItem, otherwise, using the old way. My code in viewDidLoad() as below:
if #available(iOS 11.0, *) {
navigationController?.navigationBar.prefersLargeTitles = false
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
searchViewHeight.constant = 0
} else {
searchView.addSubview(searchController.searchBar)
}
I have two IBOutlets: searchView and searchViewHeight:
#IBOutlet var searchView: UIView!
#IBOutlet var searchViewHeight: NSLayoutConstraint! // new added for iOS 11
Before iOS 11, my viewController's hierarchy as below:
I have a searchView which height is 44 to contains my searchController's searchBar view. It's under navigation bar.
In iOS 11, I add a new IBOutlet for searchView's height constraint, and set its constant to 0, hide this container view. And add searchController as a part of navigation item.
See apple's document:
https://developer.apple.com/documentation/uikit/uinavigationitem/2897305-searchcontroller
One more thing is under iOS 11, the searchBar's textField background color is little darker than navigation bar color by default. For consistency, you can change it to white, the below code will work both for iOS11 and its prior:
if let textField = searchController.searchBar.value(forKey: "searchField") as? UITextField {
if let backgroundView = textField.subviews.first {
// Search bar textField background color
backgroundView.backgroundColor = UIColor.white
// Search bar textField rounded corner
backgroundView.layer.cornerRadius = 10
backgroundView.clipsToBounds = true
}
}
I think that the solution is to add the Search Bar in the Navigation Bar:
navigationController?.navigationBar.prefersLargeTitles = true // Navigation bar large titles
navigationItem.title = "Contacts"
navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedStringKey.foregroundColor : UIColor.white]
navigationController?.navigationBar.barTintColor = UIColor(displayP3Red: 0/255, green: 150/255, blue: 136/255, alpha: 1.0)
let searchController = UISearchController(searchResultsController: nil) // Search Controller
navigationItem.hidesSearchBarWhenScrolling = false
navigationItem.searchController = searchController
You can find an example for UISearchBarController - SearchBar and scope buttons overlap here.
I had the same issue in iOS 11.
Contrary to some of the comments here, if I look at your screenshots you DONT want to set it as the navigationItem because you don't have a UINavigationController setup.
Neither do you want to add the searchBar in the header of the tableView because for some reason it can't cope with the scopeBar
So what I did to fix it:
To get a UISearchBar with scopes over your tableView, use a UIViewController in interface builder not a UITableViewController.
Place a UISearchBar and a UITableView inside the view controller and wire them up properly (delegates, dataSource, etc).
Don't forget to change your swift file to UIViewController instead of UITableViewController as well and change it accordingly. (add a tableView property and connect it via IBOutlet, change the delegates for the tableView etc)
Then in interface builder, use autoLayout guides so the searchBar sits on top of the tableView
In interface builder when you activate the scope bar it will look totally weird but don't panic, it will be fine. I guess Apple screwed the rendering n interface builder when they changed the behavior to work with UINavigationController... anyway...
Then everything works as it should and look like this (in my case I present it the vc in a popover but that doesn't matter)

Want to set a floating button in UItableview for scrolling up and down [duplicate]

I want to put buttons over UITableView which stays on same position of screen, won't scroll with UITableView. When I tried to addSubview buttons, they are displayed but scrolls with UITableView.
Reason to put buttons over UITableView is to let user operate some action on it. I'm not talking about having buttons in cell. They are floating buttons which never moves their position.
I'm using UITableViewController, and I don't want to use header or footer for this purpose. And also another some bar (UIToolbar for instance) is not an option for me.
Thanks in advance.
In case you are using navigation controller you can add the view to it:
[self.navigationController.view addSubview:yourView];
Example:
UIButton *goToTopButton = [UIButton buttonWithType:UIButtonTypeCustom];
goToTopButton.frame = CGRectMake(130, 70, 60, 20);
[goToTopButton setTitle:#"Scroll to top" forState:UIControlStateNormal];
[goToTopButton addTarget:self action:#selector(goToTop) forControlEvents:UIControlEventTouchUpInside];
[goToTopButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[goToTopButton.layer setBorderColor:[[UIColor whiteColor] CGColor]];
goToTopButton.titleLabel.font = [UIFont fontWithName:#"Helvetica-Bold" size:13];
[self.navigationController.view goToTopButton];
You can do it also with UITableViewController (no need for ordinary UIViewController, which nests your table or VC)
UPDATED iOS11+ using SafeAreas
you want to use autolayout to keep the view on bottom
{
[self.view addSubview:self.bottomView];
[self.bottomView setTranslatesAutoresizingMaskIntoConstraints:NO];
UILayoutGuide * safe = self.view.safeAreaLayoutGuide;
[NSLayoutConstraint activateConstraints:#[[safe.trailingAnchor constraintEqualToAnchor:self.bottomView.trailingAnchor],
[safe.bottomAnchor constraintEqualToAnchor:self.bottomView.bottomAnchor],
[safe.leadingAnchor constraintEqualToAnchor:self.bottomView.leadingAnchor]]];
[self.view layoutIfNeeded];
}
and to make sure view is always on top
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self.view bringSubviewToFront:self.bottomView];
}
THE PREVIOUS OLD SOLUTION
By scrolling the table, you need to adjust the position of the "floating" view and it will be fine
If you are in a UITableViewController, just
If you want to float the view at the top of the UITableView
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGRect frame = self.floatingView.frame;
frame.origin.y = scrollView.contentOffset.y;
self.floatingView.frame = frame;
[self.view bringSubviewToFront:self.floatingView];
}
If you want float the view on the bottom of the UITableView
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGRect frame = self.floatingView.frame;
frame.origin.y = scrollView.contentOffset.y + self.tableView.frame.size.height - self.floatingView.frame.size.height;
self.floatingView.frame = frame;
[self.view bringSubviewToFront:self.floatingView];
}
I believe thats the real answer to your question
If you want to show a static control view covering a table view, you have three options:
1) Don't use UITableViewController. Instead create a UIViewController with a UITableView subview and put your Table View delegate/datasource code in the UIViewController. This option may or may not work depending on the architecture of your app. For example, if you're trying to implement a subclass a UITableViewController with a lot of custom logic, this isn't ideal.
2) The WWDC 2011 method. See the WWDC 2011 Session 125 "UITableView Changes, Tips & Tricks" starting at 32:20. This method of adjusting the placement of the view whenever the table view scrolls was recommended by Apple before AutoLayout was introduced to iOS and before we had so many different screen sizes to deal with. I don't recommend it since you'll be managing positions for all the different screen sizes yourself.
3) I recommend using a UIViewController with a container view that will contain your UITableViewController. This way, you can keep the table view logic separate in the UITableViewController. The UIViewController hosts a 'dumb' container for the UITableViewController and it also holds the static control view. To do this, drag a UIViewController to your storyboard and drag a "Container View" inside it. Delete the UIViewController that comes automatically attached to the container view, and then attach your table view controller to be displayed in the container view by control dragging from the contrainer view to the table view controller and selecting "embed". Now, you can add static controls as subviews to the UIViewController that will be displayed on top of the table view. It will look something like this:
I prefer this method because UIViewController can handle the control logic and the UITableViewController handles the table view logic. Some other answers here recommend adding the static control as a subview to a Navigation Controller or Window. These only work as long as you never add another view controller to the navigation controller or window.
One solution to this is to extend UIViewController instead of UITableViewController. You will need to add your own table view and set everything up. Then you can add your button's to the view controller's view instead of the table view.
For swift :
override func scrollViewDidScroll(scrollView: UIScrollView){
var frame: CGRect = self.floatingView.frame
frame.origin.y = scrollView.contentOffset.y
floatingView.frame = frame
view.bringSubviewToFront(floatingView)
}
This function is called every time you scroll. Then inside you get the frame of your UIView and update its position depending on how much you scrolled. Your UIView is constantly moving with your UIScrollView so to the user it looks like its floating.
you can add buttons on UIWindow no need to extend it to UIViewController instead of UITableviewController.Just add buttons on window.
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
UIButton *btn =[UIButton buttonWithType:UIButtonTypeCustom];
[btn setFrame:CGRectMake(60, 200,40, 60)];
[btn addTarget:self action:#selector(btnClicked:) forControlEvents:UIControlEventTouchUpInside];
[window addSubview:btn];
I've create a library in order to make it easier to add a floating button on top of a UITableView/UICollectionView/UIScrollView. Is called MEVFloatingButton.
These are the steps for use it.
1- Import category file
#import "UIScrollView+FloatingButton.h"
2 - Add delegate and the optional delegate methods.
#interface ViewController () <MEVFloatingButtonDelegate>
#pragma mark - MEScrollToTopDelegate Methods
- (void)floatingButton:(UIScrollView *)scrollView didTapButton:(UIButton *)button;
2 - Create a MEVFloatingButtonobject.
MEVFloatingButton *button = [[MEVFloatingButton alloc] init];
button.animationType = MEVFloatingButtonAnimationFromBottom;
button.displayMode = MEVFloatingButtonDisplayModeWhenScrolling;
button.position = MEVFloatingButtonPositionBottomCenter;
button.image = [UIImage imageNamed:#"Icon0"];
button.imageColor = [UIColor groupTableViewBackgroundColor];
button.backgroundColor = [UIColor darkGrayColor];
button.outlineColor = [UIColor darkGrayColor];
button.outlineWidth = 0.0f;
button.imagePadding = 20.0f;
button.horizontalOffset = 20.0f;
button.verticalOffset = -30.0f;
button.rounded = YES;
button.hideWhenScrollToTop = YES;
4 - Assigned the button and delegate to the UITableView.
[self.tableView setFloatingButtonView:button];
[self.tableView setFloatingButtonDelegate:self]
Demo results
there is a better solution for this. you can do this by disabling the Auto Layout(button.translatesAutoresizingMaskIntoConstraints = false) property of the corresponding Button or any UIView
for floating button:
Swift 4
//create a button or any UIView and add to subview
let button=UIButton.init(type: .system)
button.setTitle("NEXT", for: .normal)
button.frame.size = CGSize(width: 100, height: 50)
self.view.addSubview(button)
//set constrains
button.translatesAutoresizingMaskIntoConstraints = false
if #available(iOS 11.0, *) {
button.rightAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.rightAnchor, constant: -10).isActive = true
button.bottomAnchor.constraint(equalTo: tableView.safeAreaLayoutGuide.bottomAnchor, constant: -10).isActive = true
} else {
button.rightAnchor.constraint(equalTo: tableView.layoutMarginsGuide.rightAnchor, constant: 0).isActive = true
button.bottomAnchor.constraint(equalTo: tableView.layoutMarginsGuide.bottomAnchor, constant: -10).isActive = true
}
I just saw WWDC 2011 TableView,Tips and Tricks videos.
Floating views are exactly the same thing.All you have to do is change frame back to original position on scrollViewDidScroll.
https://developer.apple.com/videos/wwdc/2011/
Check videos TableViews tips and tricks.
I want my class to be a UITableViewController subclass, and the subview needs to be a regular subview, using auto layout (if I want to).
So, here's what I did:
private weak var _tableView:UITableView?
override var tableView: UITableView!{
get {
return self._tableView
}
set {
self._tableView = newValue
}
}
override func viewDidLoad() {
// Re-root
let tableView = super.tableView! // keep a reference to the original tableview
self.tableView = tableView
view = UIView() // Create my own view
tableView.translatesAutoresizingMaskIntoConstraints = true
view.addSubview(tableView)
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
You can now add whatever you want to the view of the view controller, while tableView is the original tableview.
I know the question was in Objective C, but the code is fairly simple, and I just copy pasted from my own code.

after dismissing a viewController, the contentOffset from my tableView was changed to (0, -64)

The following code is not working.It makes tableview to move from (0,0) content offset to (0,-64) content offset.
-(BOOL)automaticallyAdjustsScrollViewInsets
{
return NO;
}
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:YES];
self.automaticallyAdjustsScrollViewInsets = NO;
self.extendedLayoutIncludesOpaqueBars = NO;
}
This issue is in iOS8 only
self.navigationcontroller.navigationbar.transculent = NO;
or
[[UINavigationBar appearance]setTransculent:NO];
we have to set either of the above statements to fix the tableview scroll issue.In xib we have to select navigation bar as infered and set the above appearance statement in AppDelegate.

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

Programmatically get height of navigation bar

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

Resources