My app has a tabBarController as main controller and I need to get the height of tabBar from any view controller in order to calculate proper position for the rest of elements. How to get it programmatically? Thank you.
I strongly suggest setting up your view hierarchy such that outer views are autosized by the container view and then more specific inner views can check the bounds of the outer layout view and lay themselves out appropriately at the appropriate times.
However, if your requirement makes that impossible (or extremely complicated and you really do need to specify points for everything), you can get the height of the tab bar with UIViewController's tabBarController property:
// from inside the view controller
CGSize tabBarSize = [[[self tabBarController] tabBar] bounds].size;
For Swift use this:
let tabBarHeight = tabBarController?.tabBar.bounds.size.height ?? 0
It returns tabBar height when tabBarController is not nil, otherwise 0.
Related
I have a navigation setup where at the top there is a UITabBarController. I then have a tab, which is instantiated by creating a UIViewController placed into a UINavigationController like so:
UIViewController *testVC = [UIViewController new]; // Has UITableView as subview
UINavigationController *testNavVC = [[UINavigationController alloc] initWithRootViewController:testVC];
[self setViewControllers:#[testNavVC]];
The problem that arrises is with the UITableView inside the testVC UIViewController. The table displays properly at the top and is correctly situated underneath the UINavController's nav bar. When you scroll the table view to the bottom, however, the final rows in the table view will be cut off at the bottom of the screen. I found out that I can set the bottom content inset to 100(value will differ based on row height) to correctly display the content. I don't feel like I should need to do that though, and am looking for a better solution.
How can I properly add a UITableView that is nested in this way?
As a side note this all works correctly when using a UITableViewController rather than a UIViewController with the added UITableView. In my case I am needing to use the latter option.
You can try to adjust UITableView bottom inset without hardcoding, by using bottomLayoutGuide property:
tableView.contentInset = UIEdgeInsetsMake(0.0, 0.0, self.bottomLayoutGuide.length, 0.0);
It indicates lowest vertical extent for content, and can be used from iOS 7.
As an alternative you can create bottom NSLayoutConstraint for UITableView with this value.
All of my code was done programmatically and the problem ended up being that I setup the UITableView with the views frame. I switched it over to use autolayout instead and it worked great!
I started building a TableView in my app by using a TableViewController in a storyboard. When you do this, you have a very cool effect when you scroll down your list : the cells moving behind the nav bar get blurred.
Some time later, I had to move from this TableViewController to a ViewController with a TableView inside (I had to add other views at the bottom of the table).
In order to avoid having the first cells hidden by the navigation bar (being over it), I added constraints to the Top and Bottom Layout Guides, and to the left and right edges of the view.
This works fine, but I lost the cool blurred scrolling effect : the cells seem to be disappearing before going behind the navigation bar.
I've seen workarounds with people not using constraints and putting magic numbers in interface builder. I cannot do this, first because I dislike it, and second because I have to be iOS 6 compatible.
What did I miss to be able to benefit again from the blurred navigation bar effect ?
You have to manually adjust the contentInset of the table view and make sure the table view frame origin is 0, 0.
In this way the table view will be below the navigation bar, but there will be some margin between the content and the scroll view edges (the content gets shifted down).
I advise you to use the topLayoutGuide property of the view controller to set the right contentInsets, instead of hard coding 64 (status bar + navigation bar).
There's also bottomLayoutGuide, which you should use in case of UITapBars.
Here is some sample code (viewDidLoad should be fine):
// Set edge insets
CGFloat topLayoutGuide = self.topLayoutGuide.length;
tableView.contentInset = UIEdgeInsetsMake(topLayoutGuide, 0, 0, 0);
By the way, this properties of UIViewController might help you (you should not need to change their default values, but I don't know what your view hierarchy is):
automaticallyAdjustsScrollViewInsets
edgesForExtendedLayout
extendedLayoutIncludesOpaqueBars
The tableView needs to be full screen. That is underneath the top and bottom bars. Note don't use the top and bottom layout guides as they are used for positioning relative to the bars not underneath.
Then you need to manually set the content inset of the tableview. This sets the initial scroll position to under the top bar.
Something like:
CGSize statusBarSize = [[UIApplication sharedApplication] statusBarFrame].size;
CGFloat h=MIN(statusBarSize.width, statusBarSize.height);
UIEdgeInsets e = UIEdgeInsetsMake(self.navigationController.navigationBar.bounds.size.height + h,
0.0f,
0.0f,
0.0f);
self.tableView.contentInset = e;
Not you get this functionality for free when using a tableView controller and the "Automatically Adjust content inset" settings
You probably have the coordinates of your tableView not set to (0, 0) to map to those of the viewController.view.frame or viewController.view.bounds. If you have done that, try setting
self.navigationController.navigationBar.translucent = YES;
UIViewController property edgesForExtendedLayout does the trick. If you are using storyboards just make sure Extended Edges Under Top Bars is on (and it is by default).
If you are creating your view controller programmatically try this:
- (void)viewDidLoad
{
[super viewDidLoad];
self.edgesForExtendedLayout = UIRectEdgeAll;
}
And of course, your table view needs to have proper autoresizing mask/layout constraints
edgesForExtendedLayout is not what you want here, as this will limit the table view underneath the navigation bar. In iOS 7, the view controllers uses fullscreen by default, and the property controlling where the tableview content starts is automaticallyAdjustsScrollViewInsets. This should be YES by default, so check if it is somehow set to NO, or try setting it explicitly.
Check this answer for a good explanation on how this works:
https://stackoverflow.com/a/19585104/1485715
I have been reading a lot about iOS7 UI transition.
I am not able to get what these three properties automaticallyAdjustsScrollViewInsets, extendedLayoutIncludesOpaqueBars, edgesForExtendedLayout??
For example I am trying to make my view controllers start below the status bar but I am not able to achieve it.
Starting in iOS7, the view controllers use full-screen layout by default. At the same time, you have more control over how it lays out its views, and that's done with those properties:
edgesForExtendedLayout
Basically, with this property you set which sides of your view can be extended to cover the whole screen. Imagine that you push a UIViewController into a UINavigationController. When the view of that view controller is laid out, it will start where the navigation bar ends, but this property will set which sides of the view (top, left, bottom, right) can be extended to fill the whole screen.
Let see it with an example:
UIViewController *viewController = [[UIViewController alloc] init];
viewController.view.backgroundColor = [UIColor redColor];
UINavigationController *mainNavigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
Here you are not setting the value of edgesForExtendedLayout, therefore the default value is taken (UIRectEdgeAll), so the view extends its layout to fill the whole screen.
This is the result:
As you can see, the red background extends behind the navigation bar and the status bar.
Now, you are going to set that value to UIRectEdgeNone, so you are telling the view controller to not extend the view to cover the screen:
UIViewController *viewController = [[UIViewController alloc] init];
viewController.view.backgroundColor = [UIColor redColor];
viewController.edgesForExtendedLayout = UIRectEdgeNone;
UINavigationController *mainNavigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
And the result:
automaticallyAdjustsScrollViewInsets
This property is used when your view is a UIScrollView or similar, like a UITableView. You want your table to start where the navigation bar ends, because you wont see the whole content if not, but at the same time you want your table to cover the whole screen when scrolling. In that case, setting edgesForExtendedLayout to None won't work because your table will start scrolling where the navigation bar ends and it wont go behind it.
Here is where this property comes in handy, if you let the view controller automatically adjust the insets (setting this property to YES, also the default value) it will add insets to the top of the table, so the table will start where the navigation bar ends, but the scroll will cover the whole screen.
This is when is set to NO:
And YES (by default):
In both cases, the table scrolls behind the navigation bar, but in the second case (YES), it will start from below the navigation bar.
extendedLayoutIncludesOpaqueBars
This value is just an addition to the previous ones. By default, this parameter is set to NO. If the status bar is opaque, the views won't be extended to include the status bar, even if you extend your view to cover it (edgesForExtendedLayout to UIRectEdgeAll).
If you set the value to YES, this will allow the view to go underneath the status bar again.
If something is not clear, write a comment and I'll answer it.
How does iOS know what UIScrollView to use?
iOS grabs the first subview in your ViewController's view, the one at index 0, and if it's a subclass of UIScrollView then applies the explained properties to it.
Of course, this means that UITableViewController works by default (since the UITableView is the first view).
Not sure if you are using storyboards, but if you are, to make your view controllers start below the status bar (and above the bottom bar):
Select the view controller in IB,
In the attributes inspector, deselect 'Extend Edges - Under Top Bars' and 'Extend Edges - Under Bottom Bars'.
I am using storyboards and using the above advice worked however I wasn't exactly sure how to implement it. Below is a short example in swift of how it cleared up the problem by putting the recommended solution into the ViewController.
import Foundation
import UIKit
// This ViewController is connected to a view on a storyboard that
// has a scrolling sub view.
class TheViewController: UIViewController {
// Prepares the view prior to loading. Putting it in viewDidAppear didn't work.
override func viewWillAppear(animated: Bool) {
// this method is an extension of the UIViewController
// so using self works as you might expect.
self.automaticallyAdjustsScrollViewInsets = false
// Default is "true" so this sets it to false tells it to use
// the storyboard as you have it placed
// and not how it thinks it should place it.
}
}
My Problem:
Auto Adjust set to true by default causing a difference between storyboard design and simulator
Resolved:
Code above applied, turning off the auto-adjust.
I solved this problem by adding this line, but my problem was related to a UIView, not UIScrollView
self.navigationController.navigationBar.translucent = NO;
Just bare in mind that
automaticallyAdjustsScrollViewInsets
property works only if some kind of scroll view (table view, collection view,...) is either
The view of VC, or
First subview of this view
Other suggested, that it doest work even if it is the first subview, but there are other scroll views in the view hierarchy.
EDIT (extension DIY)
If you want similar behaviour even if you can't fulfil these conditions (e.g. you have a background image below the scroll view), you can adjust the scroll view insets manually. But please don't set it to constant like 44 or 64 or even 20 like many suggest around SO. You can't know the size ever. There can be the incall/gps/audio notification, navigation bar doesn't have to be always 44 pts etc.
I think the best solution is to use layoutGuide length in didLayoutSubviews:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.contentInset = UIEdgeInsets(top: topLayoutGuide.length, left: 0, bottom: 0, right: 0)
scrollView.scrollIndicatorInsets = scrollView.contentInset
}
You can use the bottomLayoutGuide in the same way.
I'm a bit confused by the proper frame sizing of a table view to fit within my screen.
Here's my setup of view controllers within view controllers:
UITabBarController
UINavigationController as one of the tab bar viewcontrollers; title bar hidden
ViewController - a container view controller because I need the option to place some controls beneath the UITableView, sometimes (but not in the current scenario)
UITableViewController
Now, my question is what the proper frame dimensions of the UITableview should be. Here's what I've got in the ViewController viewDidLoad method. I used subtracted 49.0 (the size of the tab bar) from 480.0. However, this leaves a black bar at the bottom. 20.0 appears to do it (coincidentally?) the size of the status bar, but I don't understand why that would be. Wouldn't the true pixel dimensions of the tableview be 480-49?
// MessageTableViewController is my subclass of UITableViewController
MessagesTableViewController *vcMessagesTable = [[MessagesTableViewController alloc] init];
CGRect tableViewFrame = CGRectMake(0, 0, 320.0, 480.0 - 49.0);
[[vcMessagesTable view] setFrame:tableViewFrame];
self.tableViewController = vcMessagesTable;
[self addChildViewController:vcMessagesTable];
[[self view] addSubview:vcMessagesTable.view];
Here's how it looks:
I've run into this problem also -- I think it would be best not to hard code the size but to refer to the size of one its ancestor controllers. In this case, the UINavigationController, that's the direct child of the tabBar controller should have the right frame to fill the whole screen minus the tabBar. So I would subtract (if you need to) from that frame height if you want space at the bottom, otherwise, just use that frame. I think that self.navigationController finds the nearest nav controller above your controller of interest.
I've found out that the height of a UITabBar is 49px (or 50px, depending on the source).
Because I don't like to use too much hard-coded values in my code I was wondering if it is possible to retrieve the height of the tabbar programmatically.
Kind regards,
Niels R.
PS: The reason I'm asking is because I have a view controller (with a list view containing text fields) that is either simply pushed by the navigationcontroller (pushViewController) or presented as a modal (presentModalViewController). As soon as the keyboard show up, the size of the view is reduced, but I have to take into account that the tabbar is only visible when the view controller is pushed and not presented as a modal.
I don't totally understand your P.S., but you can do:
tabBarController?.tabBar.frame.size.height
If you want to get the standard height for the UITabBar control then you can do:
UITabBarController *tabBarController = [UITabBarController new];
CGFloat tabBarHeight = tabBarController.tabBar.frame.size.height;
prettier
CGRectGetHeight(self.tabBarController.tabBar.frame)
check this in your vc
bottomLayoutGuide.length
In Swift 3, you can retrieve the height of the UITabBar with this code below.
self.tabBarController!.tabBar.frame.size.height
Which will return a CGFloat value of the height
If an object is based on UIView (which most visual elements in the library are), you can get the size from the ivar "frame".
UITabBar is inherited from UIView as long as you mhave access to your UITabBar instance you can access and modify the frame of UITabBar,
CGRect myRect = myTabBar.frame;
Try to use the intrinsicContentSize property of the tab bar like this
let tabBarHeight = self.tabBarController.tabBar.intrinsicContentSize.height