I need to resize some elements in relation to the height of the iPhone's Status Bar. I know that the status bar is usually 20 points high but this isn't the case when it's in tethering mode. It gets doubled to 40. What is the proper way to determine it's height? I've tried
[[UIApplication sharedApplication] statusBarFrame]
but it gives me 20 x 480 in landscape which is correct but then it gives me 320 x 40 in portrait. Why isn't it giving me the opposite of that (40 x 320)?
The statusBarFrame returns the frame in screen coordinates. I believe the correct way to get what this corresponds to in view coordinates is to do the following:
- (CGRect)statusBarFrameViewRect:(UIView*)view
{
CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
CGRect statusBarWindowRect = [view.window convertRect:statusBarFrame fromWindow: nil];
CGRect statusBarViewRect = [view convertRect:statusBarWindowRect fromView: nil];
return statusBarViewRect;
}
Now in most cases the window uses the same coordinates as the screen, so [UIWindow convertRect:fromWindow:] doesn't change anything, but in the case that they might be different this method should do the right thing.
Did you do it like this:
CGRect rect;
rect = [[UIApplication sharedApplication] statusBarFrame];
NSLog(#"Statusbar frame: %1.0f, %1.0f, %1.0f, %1.0f", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
EDIT
The iOS 11 way to work out where to put the top of your view content is UIView's safeAreaLayoutGuide See UIView Documentation.
DEPRECATED ANSWER
If you're targeting iOS 7+, The documentation for UIViewController advises that the viewController's topLayoutGuide property gives you the bottom of the status bar, or the bottom of the navigation bar, if it's also visible. That may be of use, and is certainly less hack than many of the previous solutions.
You could test which is the lesser of the two values, that will be the real height.
This method works for portrait & landscape orientation.
-(float) statusBarHeight
{
CGSize statusBarSize = [[UIApplication sharedApplication] statusBarFrame].size;
return MIN(statusBarSize.width, statusBarSize.height);
}
// example call
float statusBarHeight = [self statusBarHeight];
Swift 2:
let statusBarHeight = UIApplication.sharedApplication().statusBarFrame.height
Swift 3 or Swift 4:
let statusBarHeight = UIApplication.shared.statusBarFrame.height
Advice: Don't forget to inject the UIApplication.shared do not just use the singleton in your code.
Here is the Swift version if anyone needs it:
var screenStatusBarHeight: CGFloat {
return UIApplication.sharedApplication().statusBarFrame.height
}
This is included as a standard variable in:
https://github.com/goktugyil/EZSwiftExtensions
Disclaimer: Its my repo
Related
I've got an app that uses a UIWebView. Currently I create the web view using a storyboard but I want to create it programatically.
This needs to work across all devices and iOS 5 - 7.
Currently (using the storyboard) in iOS 6 it looks like this:
But in iOS 7 you don't get that white bar at the top:
How can I create the UIWebView programatically so that it fills available window space (i.e. doesn't go over tab bar or status bar) across all devices?
Thanks
//EDIT
Here's my storyboard for the UIWebView
//Edit 2
As per a suggestion I am doing this but it overlaps the status bar and tab bar. Is there any way to take them into account?
float width = [UIScreen mainScreen].bounds.size.width;
float height = [UIScreen mainScreen].bounds.size.height;
try to set your webview's frame programmatically like this
float width = [UIScreen mainScreen].bounds.size.width;
NSLog(#"width: %f", width);
float height = [UIScreen mainScreen].bounds.size.height;
NSLog(#"height: %f", height);
so your webview's frame should be
objWeb.frame = CGRectMake(0,0,width,height);
Okay, self.view will be resized at somewhere between viewDidLoad and viewWillAppear.
So, if you wanted to adjust the size, you should do it in viewWillApear.
But, my suggestion is use AutoResizeMask, or |[webView]| for auto layout.
AutoReszie
add your webView to self.view as subView, set the frame to self.view.boudse, and then
webView.autoResizeMask = UIViewAutoResizeMaskFliexbleHeight|UIViewAutoResizeMaskFliexbleWidth;
let me know if you need autoLayout version.
This is what worked for me based on others answers
-(void)createWebView
{
float width = self.view.bounds.size.width;
float height = self.view.bounds.size.height;
UIWebView *wv = [[UIWebView alloc] initWithFrame:CGRectMake(0, 20, width, height)];
wv.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
wv.scalesPageToFit = YES;
wv.delegate = self;
[self.view addSubview:wv];
}
The width of a UITabBarItem varies, depending on how many there are.
How do I determine how wide my tab bar items are?
I'm looking for a property if possible, as opposed to a mathematical formula, since on the iPad, there is also an issue of padding on either side of the tab bar. Consider these screenshots. Notice the padding on either side of the tab bar items on the iPad (highlighted with the red boxes). This padding does not exist on the iPhone.
The iPad:
The iPhone:
Edit: It has been noted in the comments below that this solution did not work in a beta version of iOS 5, so be prepared to modify it to meet your needs.
I think in order to do this the right way, you have to be able to get to the frame of each tab bar item. Fortunately, this is possible:
CGFloat tabBarTop = [[[self tabBarController] tabBar] frame].origin.y;
NSInteger index = [[self tabBarController] selectedIndex];
CGFloat tabMiddle = CGRectGetMidX([[[[[self tabBarController] tabBar] subviews] objectAtIndex:index] frame]);
The above code gives you the y coordinate of the top of the tab bar and the x coordinate of the middle of the selected tab item. From here, you can add your indicator view to the tab bar controller's view relative to this point.
Disclaimer: The danger with this approach is that it peeks under the hood of the UITabBar class by accessing its view hierarchy and the frame property of instances of the private UITabBarButton class. It is possible (however unlikely) that this approach could be affected/broken by a future iOS update. However, you're not accessing any private APIs, so this shouldn't get your app rejected from the App Store.
I made a simple iPad project demonstrating how it works, complete with animations.
Swift 3
I created a shared instance in UIApplication then pulled my subviews width
tabBarController?.tabBar.subviews[1].frame.width
UITabBarItem inherits from UIBarItem, which inherits from NSObject. Since it doesn't inherit from UIView, I don't know that you're going to be able to get the information you want just with a property. I know you don't want a mathematical way of doing it, but it would seem that getting the frame of the tab bar and dividing by the # of tabs would be a reasonable option that should work regardless of the hardware (iphone vs ipad). Padding shouldn't affect this, right? So you'd have:
tabSize = tabbar.frame.size.width / [tabbar.items count];
tabBarStart = tabbar.frame.origin.x;
// Assume index of tabs starts at 0, then the tab in your pic would be tab 4
// targetX would be the center point of the target tab.
targetX = tabBarStart + (tabSize * targetTabIndex) + (tabSize / 2);
I, as well, am interested to know if someone finds a simple property to give this information.
Looking at the answer above:
Items in the UITabBar work different in the iPhone and the iPad. In the iPhone they fill whole width, in the iPad they're centered horizontally.
There is a spacing between the items in the iPad.
You can set the values for both width and spacing using tabBar.itemWidth or tabBar.itemSpacing. You are not able to read system spacing or width from it - you'll receive 0 unless you set it.
So here's my sample how to get frames of all UITabBarItems:
// get all UITabBarButton views
NSMutableArray *tabViews = [NSMutableArray new];
for (UIView *view in self.tabBar.subviews) {
if ([view isKindOfClass:[UIControl class]]) {
[tabViews addObject:[NSValue valueWithCGRect:view.frame]];
}
}
// sort them from left to right
NSArray *sortedArray = [tabViews sortedArrayUsingComparator:^NSComparisonResult(NSValue *firstValue, NSValue *secondValue) {
CGRect firstRect = [firstValue CGRectValue];
CGRect secondRect = [secondValue CGRectValue];
return CGRectGetMinX(firstRect) > CGRectGetMinX(secondRect);
}];
Now you have a table of frames, corresponding to your tabbar items. You can eg. place an imageView on selected index button frame.
CGRect frame = self.tabBar.bounds;
CGSize imageSize = CGSizeMake(CGRectGetWidth(frame) / self.tabBar.items.count, self.imageView.image.size.height);
CGRect selectedRect = sortedArray.count > self.selectedIndex ? [sortedArray[self.selectedIndex] CGRectValue] : CGRectZero;
[self.imageView setFrame:CGRectIntegral(CGRectMake(CGRectGetMinX(selectedRect), CGRectGetMaxY(frame) - imageSize.height,
CGRectGetWidth(selectedRect), imageSize.height))];
I tried these 2 properties of UITabBar:
#property(nonatomic) CGFloat itemWidth NS_AVAILABLE_IOS(7_0) UI_APPEARANCE_SELECTOR;
#property(nonatomic) CGFloat itemSpacing NS_AVAILABLE_IOS(7_0) UI_APPEARANCE_SELECTOR;
But get zero value as Vive said. So I wrote some codes to get the right width:
CGFloat tabBarItemWidth = 0;
for (UIView *view in [self.tabBarController.tabBar subviews]) {
if ([view isKindOfClass:NSClassFromString(#"UITabBarButton")]) {
if (tabBarItemWidth == 0) {
tabBarItemWidth = view.frame.origin.x;
} else {
tabBarItemWidth = view.frame.origin.x - tabBarItemWidth;
break;
}
}
}
Why not use the width of first UITabBarButton? Because there are spaces between tab bar buttons. :)
You can get tabBarItem view using private property view. Then just get view frame.
- (CGRect)rectForTabBarItem:(UITabBarItem *)tabBarItem {
UIView *itemView = [tabBarItem valueForKey:#"view"];
return itemView.frame;
}
Loop through your tabBar's subviews and find the views with the class "UITabBarButton". These are the views for each tab item.
for (UIView *view in self.tabBar.subviews) {
if ([view isKindOfClass:NSClassFromString(#"UITabBarButton")]) {
// view.frame contains the frame for each item's view
// view.center contains the center of each item's view
}
}
I'm writing an app that uses UITabBar for parts of the navigation. I'm also using UIScrollView for presenting more information than what the screen can typically handle. Because of this, I'm needing to set the scroll view to take into account the height of the UITabBar so that all of the information is displayed.
Is there a way to calculate the height of the UITabBar?
If the view controller has an ancestor that is a tab bar controller, you can retrieve the height from that tab bar.
CGFloat tabBarHeight = self.tabBarController.tabBar.frame.size.height;
It is 320 x 49.
If you want to test, open Interface Builder, add a UITabBar, go into the ruler, you will see it
UITabBar is inherited from UIVIew so you can use the frame.size.height to get the height
In Swift:
let height = self.tabBarController?.tabBar.frame.height ?? 49.0
Relying on the actual height of the tab-bar, and using the magic number as a fallback.
Swift 3+
let tabBarHeight = tabBarController?.tabBar.frame.size.height
print(tabBarHeight ?? "not defined")
It should print 49.0 (Type CGFloat)
I was looking to do something similar with centering a label in the VISIBLE portion of a ViewController's view. This ViewController belonged to a UITabBarController.
Here's the code I used to center my label:
UILabel *roomLabel = [[UILabel alloc] init];
CGRect frame = [[self view] bounds];
float tabBarHeight = [[[super tabBarController] tabBar] frame].size.height;
frame.size.height -= tabBarHeight;
[roomLabel setFrame:frame];
[[self view] addSubview:roomLabel];
[roomLabel release];
Notice that I used [[self view] bounds] not [[self view] frame] because the latter includes the 20 pixel top bar as the Y offset (which throws off the vertical centering).
Hope this helps someone!
By the way: I'm using iOS 4.3 and XCode 4 and the "hard-code" value for the TabBar's height is still 49 for me!
I know this isn't ideal, but I really didn't want to have a magic number constant anywhere. What I did was create a throwaway UITabBarController, and get the height from there.
I did this also because [UITabBar initWithFrame:] works as desired, but doing a [bar setFrame:] doesn't. I needed the frame to be correct at creation.
UITabBarController *dtbc = [[[UITabBarController alloc] init] autorelease];
CGRect tabRect = [[[self navigationController] view] frame];
tabRect.origin.y = tabRect.size.height - [[dtbc tabBar] frame].size.height;
tabRect.size.height = [[dtbc tabBar] frame].size.height;
tabBar_ = [[UITabBar alloc] initWithFrame:tabRect];
What I like about this is that it will correctly place the tab bar at the bottom of the parent regardless of the parents size.
This should work in most cases on any instance of UIViewController:
bottomLayoutGuide.length
In swift 4 and 5. self.tabBarController?.getHeight()
extension UITabBarController{
func getHeight()->CGFloat{
return self.tabBar.frame.size.height
}
func getWidth()->CGFloat{
return self.tabBar.frame.size.width
}
}
Others can also try to get the height using the intrinsicContentSize property of the tab bar.
let tabBarHeight = self.tabBarController.tabBar.intrinsicContentSize.height
This is how I got it to work in swift 4.1
let tabBarHeight = CGFloat((self.tabBarController?.tabBar.frame.size.height)!)
Swift 5
if let tabBarController = tabBarController {
let tabBarSafeAreaHeight = tabBarController.tabBar.frame.size.height -
tabBarController.tabBar.safeAreaInsets.bottom
}
This calculates the height of the UITabBar taking into account the safeAreaInsets (UIEdgeInsets)
At the time of writing this equals 49 on iPhone portrait
let screenHeight = UIApplication.shared.statusBarFrame.height +
self.navigationController!.navigationBar.frame.height + (tabBarController?.tabBar.frame.size.height)!
This works perfectly, based it of a few ppls answer here
SWIFT 5 UPDATE :
AS this thread is old, I am posting here the update from another thread: https://stackoverflow.com/a/25550871/14559220. To sum things up,
in portrait and regular landscape, the height is still 49 points. In
compact landscape, the height is now 32 points.
On iPhone X, the height is 83 points in portrait and 53 points in
landscape.
Having trouble getting the correct bounds for my iPad application when launching it in landscape mode. I have the proper keys set in my Info.plist file, and my view controllers launch properly in landscape (and portrait, natch).
In my applicationDidFinishLaunching: method I'm calling a selector after a 3 second delay, and that method makes a call to [[UIScreen mainScreen] applicationFrame], but it's returning me a portrait frame (ie height > width).
Does anyone know how to fix this? It smells like a bug to me (if so I'll file a radar), but if it's intended behaviour, where is it documented?
I never rely on [[UIScreen mainScreen] applicationFrame], especially during app launch.
When creating views in code, use the superview to set your frame.
If you're using xibs with "simulated interface elements" they will be correctly sized and everything will work great.
UINavigationController based apps
In the case of a UINavigationController based app, grab the frame directly from self.navigationController.view, don't try to use [self loadView] and self.view.superview. UINavigationController uses "hidden" subviews to do it's job--so the direct superview will not work.
UINavigationController is special because during app launch, the navigation controller resizes your views after loadView gets called. When autoresizing kicks in you end up with a small margin at the bottom of the screen.
Why not UIScreen
[[UIScreen mainScreen] applicationFrame] doesn't work reliably (especially during app launch in landscape). My experience is that the viewcontroller's interfaceOrientation property will not match the applicationFrame orientation.
CGRect bounds = [[UIScreen mainScreen] bounds]; // portrait bounds
if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) {
bounds.size = CGSizeMake(bounds.size.height, bounds.size.width);
}
When you are holding the iPad in landscape orientation and launch an app, the view controller initially sees bounds for a portrait view (even though orientation reports landscape). The view controller will then get a message to rotate to landscape orientation before it appears.
This is the way I get the correct CGRect when the view controller is on landscape:
CGRect landscapeBounds;
if (self.view.frame.size.width < self.view.frame.size.height)
landscapeBounds = CGRectMake(self.view.frame.origin.y, self.view.frame.origin.x, self.view.frame.size.height, self.view.frame.size.width);
else
landscapeBounds = CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y, self.view.frame.size.width, self.view.frame.size.height);
This is as designed. You should query the size of your superview and adjust as necessary.
[[UIScreen mainScreen] applicationFrame] will always return the portrait rectangle even if the app is in landscape mode. This is related to the fact that UIWindow never actually rotates, but just changes the transform of rootViewController.view instead.
To make sure, you can print the root view object in portrait and landscape modes, and you'll see something like this:
Portrait:
<UIView: 0x96290e0; frame = (0 20; 768 1004); ...
Landscape:
<UIView: 0x96290e0; frame = (0 20; 768 1004); transform = [0, 1, -1, 0, 0, 0]; ...
So, add a launch image and give it the suffix -568h, according to Apple's guides.
I don't understand why anyone with a sound mind would make a system setting depend on a graphic; but I just tested and it worked.
Here is the spot that taught me after a quick search I didn't see this answer above, figured it'd be useful to someone.
S
I got into same problem when dismissing view with dismissViewControllerAnimated in Cordova plugin.
I modified singingAtom code for viewWillAppear method in MainViewController, which got resized after dismissing modal view:
- (void)viewWillAppear:(BOOL)animated
{
CGRect appFrame = [[UIScreen mainScreen] applicationFrame];
UIDeviceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if (UIDeviceOrientationLandscapeLeft == orientation ||
UIDeviceOrientationLandscapeRight == orientation)
{
if (appFrame.size.width < appFrame.size.height)
{
appFrame = CGRectMake(appFrame.origin.y, appFrame.origin.x, appFrame.size.height, appFrame.size.width);
}
}
self.view.frame = appFrame;
[super viewWillAppear:animated];
}
I'm developing an app in objective-c and in that app set the navigationBar to translucent(through IB). But the problem is that the view displayed behind the navigation bar.
Anybody else tried working with translucent navigation bars?
regards
Jayaraj
The [navigationController view] automatically resizes to "underlap" translucent navigation bars as of OS 3.0
You can simply add 44 pixels to the y value of the origin property to overcome this.
You can use 44 if you know you're in portrait (not landscape, in which the height of the navBar is less than 44)
You can also do:
// applicationFrame subtracts the height of the statusBar if it's visible.
// bounds doesn't take into account the statusBar.
CGRect navFrame = [[UIScreen mainScreen] applicationFrame];
NSLog(#"navFrame: %f x %f", navFrame.size.width, navFrame.size.height);
navFrame.size.height -= self.navigationController.navigationBar.frame.size.height;
NSLog(#"navFrame: %f x %f", navFrame.size.width, navFrame.size.height);
UIImageView *imageView = [[UIImageView alloc] initWithFrame:navFrame];
NSLog(#"imageView: %#", imageView);
I learned this from reading three20's source code. You can find it on github.com
Matt