I know this question has been asked several times and the solutions I have seen have been very helpful. But since i have 2 conflicting requirements, I am a little stranded and hoping to find some help.
So here are the requirements:
We have multiple View controllers out of which only one needs to be full screen (without status bar on the top).
The other view controllers need to show a black status bar with a dark gray navigation bar
The First View controller is embedded in a navigation controller.
As recommended in some of the other posts, I did the following
Set UIViewControllerBasedStatusBarAppearance to NO
Added this code in app delegate
CGRect frame = [[UIScreen mainScreen] bounds];
self.window.frame = CGRectMake(0,20,frame.size.width, frame.size.height-20);
self.window.bounds = self.window.frame;
It works fine if I only stay in those View controllers that have the status bar.
The moment I open the FULL screen view controller, that VC is cut off on the top as shown here.
Additionally when I come back to the Main view controller, now thats shifted up as well and the title bar is where the status bar was showing.
I have tried to push the views back down by resetting the view.frame and requesting layout but it doesnt take effect.
Any suggestions on how to resolve this?
Don't change self.window.bounds in app delegate. Instead, in your view controllers try something like this:
-(void)viewWillAppear:(BOOL)animated
{
[self.navigationController setNavigationBarHidden:YES/NO animated:YES];
[self setNeedsStatusBarAppearanceUpdate]; // For showing/hiding status bar
[super viewWillAppear:animated];
}
- (BOOL)prefersStatusBarHidden {
return YES/NO;
}
You will have different frames for the view in ViewDidLoad according to whether status bar and navigation bar are there.
In my app, I do not intend to show the status bar. So, my app covers the whole screen view. I could manage doing it inserting the
- (BOOL) prefersStatusBarHidden{
return YES;
}
method in my main viewController and also adding to my app delegate
if ([self respondsToSelector:#selector(setNeedsStatusBarAppearanceUpdate)]) {
// iOS 7
[self performSelector:#selector(setNeedsStatusBarAppearanceUpdate)];
} else {
// iOS 6
[application setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide];
}
This all works well when I open the app in the portrait mode. However, when opened in the landscape mode, while the status bar gets hidden, I see a black band of background image on top of my view. Indeed my view is vertically offset by the height of status bar height (see picture below).
This does not happen if I open the app first in portrait mode and then rotate. So, I am doing something wrong in the initialization of the view frame I thought, which I do by the usual
CGRect screenRect = [[UIScreen mainScreen] applicationFrame];
[self.view setFrame:screenRect];
Any suggestions on this would be really appreciated.
Thanks in advance,
Nikhil
I seem to have fixed the issue (at least for now). I did not fix in the info-plist the options
I hate this. To do one single setting one has to add so many things, that too in different places: one in the appDelegate, other in the main view controller and yet another completely outside the code :(.
I have a view controller in my app that has a navigation bar dragged on it in the storyboard. It was working fine in the iOS 6 but in iOS 7 it look like this:
The status bar and the navigation bar should no collide with each other. I have seen a lot of such questions on the stack overflow but they didn't of much help to me.
Some questions say that i should use this "self.edgesForExtendedLayout = UIRectEdgeNone;" but it didn't work. Some say i should remove the navigation bar and embed it inside the navigation controller that i cannot do due to the way my program is implemented. Some solutions suggests to use the view bounds and all but it didn't work for me as well.
What is the one thing that can help me resolve this issue. Thanks in advance!
UPDATE: I have embedded the view controller inside a uinavigation controller. Removed the navigation bar that was earlier manually added in it. Now it looks ok in the storyboard but when i run it, it shows the following:
It is showing text from another view controller that is currently behind it that is its parent view controller. Means its transparent now. Can anyone point out what i am doing wrong?
The latest version of the iOS has brought many visual changes and from a developer's point of view, the navigation and status bar are two noticeable changes.
The status bar is now transparent and navigation bar behind it shows through. The navigation bar image can even be extended behind the status bar.
First of all, if you are a beginner and have just started iOS development and are confused the way status bar and navigation bar is working, you can simply go through a blog post HERE that i found very useful. It has all the information related to navigation and status bar in iOS 7.
Now coming to the answer of your question. First of all i can see two different problems. One is that your status bar and navigation bar are both kind of colliding with each other as shown by you in the question with an image.
PROBLEM: Well the problem is that your have earlier dragged a navigation bar in your view controller which was working in iOS 6 correctly but with the arrival of iOS 7 SDK, this approach is resulting in status bar and navigation bar overlapping with each other.
SOLUTION to First Problem: You can either use UIBarPositionTopAttached or you can use view bounds and frames, i can also suggest and link you to Apple's documentation and bla bla bla but that would take some time for you to solve the issue.
The best and the most easiest way to solve this issue is to just embed your view controller inside a navigation controller and thats it. You can do it by just selecting the view controller and going to Editor > Embed In > Navigation Controller. (If there is any content on your old navigation bar, you can first drag it down, embed the view controller in navigation controller and then move the bar buttons on the new navigation bar and then delete the old navigation bar)
SOLUTION to Second Problem: This solution is for your specific question that you have mentioned in the update and is not for the general public reading this. As you can see that navigation and status bar is not visible and a transparent area is showing the parent view controller. I am not really use why you are facing this issue but most probably because of some third party library like ECSlidingView or any other is involved. You can select this view controller in your storyboard and set the background color of the view to be the same as your navigation bar. This will stop showing the parent view controller behind and your navigation bar and status bar will start showing. Now you can cover the rest of your view controller with text view or what ever your are using in it.
Hope this helps!
The navigation bar is too close to the status bar because starting in iOS 7, the status bar is more of an overlay over the whole view controller's view. Since your navigation bar is at (0, 0), the status bar will show on top of the navigation bar. To solve this, simply move the navigation bar down (or, as others have said), create a constraint between the navigation bar and the topLayoutGuide.
When you do that, you will see that there is now a 20 point gap between the navigation bar and the top of the screen. That's because you just moved the navigation bar down 20 points. "But UINavigationController can do it right!" Absolutely, and it does so by implementing UIBarPositioningDelegate on your view controller. This is a one-method protocol that should be implemented like this:
- (UIBarPosition)positionForBar:(id<UIBarPositioning>)bar {
return UIBarPositionTopAttached;
}
After adding your view controller as the delegate for the navigation bar, you'll notice the navigation bar is still shifted down 20 points, but its background will extend up underneath the status bar, just like in UINavigationController.
Another thing you're seeing is that the navigation bar is translucent, meaning anything underneath the navigation bar will be visible to some extent. The translucent property on UINavigationBar is set to YES by default on iOS 7. Before iOS 7, the default was NO.
you can simply do this:
1) add a constrain between the Navigation Bar and Top Layout Guide (select navigationBar, hold ctrl key and go to Bottom Layout Guide, unhold ctrl key)
2) select vertical spacing:
3) set constant to 0:
Result:
UPDATE
In your AppDelegate file you can add this:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
// Prevent Navigationbar to cover the view
UINavigationBar.appearance().translucent = false
}
I suggest you in your viewDidLoad method you try:
self.navigationController.navigationBar.translucent = NO;
(by default it is yes now)
https://developer.apple.com/library/ios/documentation/uikit/reference/UINavigationBar_Class/Reference/UINavigationBar.html#//apple_ref/occ/instp/UINavigationBar/translucent
This works for me i hope you also have same luck :).
Add below code in your view.
-(void) viewDidLayoutSubviews
{
CGRect tmpFram = self.navigationController.navigationBar.frame;
tmpFram.origin.y += 20;
self.navigationController.navigationBar.frame = tmpFram;
}
It basically change location of navigation bar.
This is new feature with IOS7. Instead of staring at 20 px navigation bar in IOS7 staring at 0 px. As a solution shift the whole view downwards to 20 px or you can use image for navigation bar with height 64px.
In case it still helps someone, this is what worked for me for moving the Navigation Bar little bit down in ios 7 and above:
-(void)viewWillLayoutSubviews
{
float iosVersion = 7.0;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= iosVersion) {
// iOS 7+
CGRect viewFrame = self.view.frame;
viewFrame.origin.y += 10;
self.view.frame = viewFrame;
}
}
On a device with ios 6.1 and below the Navigation Bar will be unchanged, as it was before.
And this is what I used to make the contents of the Status Bar lighter:
-(UIStatusBarStyle)preferredStatusBarStyle{
return UIStatusBarStyleLightContent;
}
If your UIViewController is NOT in a UINavigationController and you're using UIStoryBoard, you can set the "iOS 6/7 Deltas" to 20 for the delta Y, for every subview that needs to be offset from the UIStatusBar.
Using Swift:
As #Scott Berrevoets said in his answer you need to implement the method positionForBar in the protocol UIBarPositioningDelegate, but as the UINavigationBarDelegate protocol implements this protocol :
public protocol UINavigationBarDelegate : UIBarPositioningDelegate {
...
}
You only need to set the delegate of the UINavigationBar you set using Storyboard and implement the method and it's done, like in this way:
class ViewController: UIViewController, UINavigationBarDelegate {
#IBOutlet weak var navigationBar: UINavigationBar!
override func viewDidLoad() {
super.viewDidLoad()
self.navigationBar.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func positionForBar(bar: UIBarPositioning) -> UIBarPosition {
return UIBarPosition.TopAttached
}
}
NOTE:
It's worth to mention if you set the position of the y-axis of the navigation bar, let's say to 40 from the top, then it will extend underneath to the top from this position, to simulate the behaviour of the UINavigationController you need to set to 20 from the top.
I hope it will help you.
First, set UIViewControllerBasedStatusBarAppearance to NO in Info.plist.
Then, in AppDelegate's application:didFinishLaunchingWithOptions: method add:
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7) {
[application setStatusBarStyle:UIStatusBarStyleLightContent];
self.window.clipsToBounds = YES;
self.window.frame = CGRectMake(0, 20, self.window.frame.size.width, self.window.frame.size.height-20);
self.window.bounds = CGRectMake(0, 20, self.window.frame.size.width, self.window.frame.size.height);
}
return YES;
In iOS 7 app occupies 100 % of screen size.This not a problem .
http://www.doubleencore.com/2013/09/developers-guide-to-the-ios-7-status-bar/
in earlier iOS window start after statusbar and in iOS 7 window starts from 0px in earlier version view height is 460 (iPhone 4s and earlier) and 548 (iPhone 5) but in iOS 7 view height is 480 (iPhone 4s and earlier) and 568 (iPhone 5 and later) so you have to start view arrangement after 2o px or you have to start view from 20px.
you can write below code in rootviewcontroller or in all viewcontroller for set view from 20px
#define IOS7_HEIGHT 64
- (void)viewDidLayoutSubviews {
NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
if ([currSysVer compare:#"7.0" options:NSNumericSearch] != NSOrderedAscending)
{
CGRect frame=[self.view frame];
if (frame.origin.y!=IOS7_HEIGHT) {
frame.origin.y = IOS7_HEIGHT;
frame.size.height -= IOS7_HEIGHT;
[self.view setFrame:frame];
[self.view layoutSubviews];
}
}
}
here height is 64 because 20 for statusbar and 44 for navigationbar.
try below code it will help you. and your problem will be solved.
For the ones who are having problems implementing #Masterfego 's solution (which is also the official, but I have had problems with Xcode 6.3 and automatic constraints), this is what I did:
I have a UIViewController with an added Navigation Bar. I selected the NAvigation bar and added a height constraint of 64px. We later see a warning that the navbar will be higher (but this is what we do). Finally, you can see that the Status bar looks nice and has the same color as the navbar. :)
PS: I can't post images yet.
You can probably create constraints that are attached to the top layout guide to specify the navigation bar's position relative to the status bar. See the iOS 7 UI Transition Guide: Appearance and Behavior section for more information about using the layout guides.
it's the best answer.
But I wanted know how to use a Storyboard and dragged UINavigationBar on it.
When I implemented the delegate method, and set the return result to UIBarPositionTopAttached, it did not work.
- (void)viewDidLoad{
self.navigationbar.delegate = self;
}
- (UIBarPosition)positionForBar:(id<UIBarPositioning>)bar{
NSLog(#"Got it");
//
// CGRect frame = self.navigaitonBar.frame;
//
// frame = CGRectMake(0, 20, CGRectGetWidth(frame), CGRectGetHeight(frame));
// self.navigaitonBar.frame = frame;
//
// NSLog(#"frame %f",frame.origin.y);
return UIBarPositionTopAttached;
}
If you use Xcode 6 and Swift, you can make it:
Open to info.plist file of your app.
Add a ViewControllerBasedStatusBarAppearance Boolean key if it is not existing and assign value “NO”.
Add “Status bar style” key if it is not existing and select “Opaque black style” value to it.
I was facing issue when full screen ModalViewController was opening from my MainViewController, NavigationBar position was getting changed when user was coming back to MainViewController from ModalViewController.
Issue which I noticed is status bar height was not getting included when user came back to MainViewController. Please debug and check origin of your NavigationBar before and after coming back to your ViewController.
// This method will adjust navigation bar and view content.
private func adjustNavigationControllerIfNeeded() {
var frame = self.view.frame
let navigationBarHeight = self.navigationController!.navigationBar.frame.size.height
if(frame.origin.y == navigationBarHeight && !UIApplication.shared.isStatusBarHidden) {
// If status bar height is not included but it is showing then we have to adjust
our Navigation controller properly
print("Adjusting navigation controller")
let statusBarHeight = UIApplication.shared.statusBarFrame.height
frame.origin.y += statusBarHeight // Start view below navigation bar
frame.size.height -= statusBarHeight
self.view.frame = frame
self.navigationController!.navigationBar.frame.origin.y = statusBarHeight // Move navigation bar
}
}
And call it from viewWillAppear method -
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.adjustNavigationControllerIfNeeded()
}
I have an application using PKRevealController which implements a slide-out menu similar to the ones in the popular Facebook and GMAIL apps on iOS. The app is built in XCode 5, and runs on iOS 6 and iOS 7. I need to figure out how to have it work sanely in both places, so a simple .XIB hack that makes it look okay in iOS 7 but makes it look worse in iOS 6 is not okay.
The code works great for iOS 6, where the status bar is opaque and the top view is not alpha-blended with the status bar.
However, on iOS 7, just for example, I have created this view in my .xib file, here is how it appears running in ioS 6 simulator, shown here with the slide out menu opened:
The same .xib file running on ios 7, when the slide-out menu open, the top of the slide out menu's .xib content is now under the status bar, as Apple said it would be in their ios 7 transition guide:
The class I need to modify in PKRevealController is probably the presenting view controller that is creating and presenting the contained view, the contained view is called PKRevealControllerContainerView, I think. I think I probably need to create
some kind of view hierarchy like this:
[ Outermost View container
[ some kind of blob to occupy the header area ]
[ the client view I want to appear the way it did in iOS 6]
]
I've been reading around, and there may be much simpler approaches, but I don't quite understand them, approaches like adding properties to my info.plist, like View controller-based status bar appearance = YES. I tried that it did not have the desired effect.
How do I go about fixing this? I have read the Fine Guide published by Apple and it has not provided code, only general guidance like this page on the status bar.
It's easy to replicate this problem, just clone the git repo https://github.com/pkluz/PKRevealController, build and run.
The code that brings up the pop-up view looks like this:
- (void)addLeftViewControllerToHierarchy
{
if (self.leftViewController != nil && ![self.childViewControllers containsObject:self.leftViewController])
{
[self addChildViewController:self.leftViewController];
self.leftViewContainer.viewController = self.leftViewController;
if (self.leftViewContainer == nil)
{
self.leftViewContainer = [[PKRevealControllerContainerView alloc] initForController:self.leftViewController shadow:NO];
self.leftViewContainer.autoresizingMask = [self autoresizingMaskForLeftViewContainer];
}
self.leftViewContainer.frame = [self leftViewFrame];
[self.view insertSubview:self.leftViewContainer belowSubview:self.frontViewContainer];
[self.leftViewController didMoveToParentViewController:self];
}
}
The above is invoked by PKRevealController.m, like this:
- (void)showLeftViewControllerAnimated:(BOOL)animated
completion:(PKDefaultCompletionHandler)completion
{
__weak PKRevealController *weakSelf = self;
void (^showLeftViewBlock)(BOOL finished) = ^(BOOL finished)
{
[weakSelf removeRightViewControllerFromHierarchy];
[weakSelf addLeftViewControllerToHierarchy]; // HELLO LEFT Slide-out menu.
....
Is there a better approach than my idea? Did Apple provide some way to make this easy or does trying to support iOS 6 and iOS 7 in a single codebase leave me doing hacks like the above I'm considering?
Here, for instance, is a really ugly hack where I don't bother placing any view underneath the apple system status bar, leaving a black bar at the top, which is no good, but it shows I'm modifying the right area in the code, at least:
- (void)addLeftViewControllerToHierarchy
{
CGRect lvFrame;
if (self.leftViewController != nil && ![self.childViewControllers containsObject:self.leftViewController])
{
[self addChildViewController:self.leftViewController];
self.leftViewContainer.viewController = self.leftViewController;
if (self.leftViewContainer == nil)
{
self.leftViewContainer = [[PKRevealControllerContainerView alloc] initForController:self.leftViewController shadow:NO];
self.leftViewContainer.autoresizingMask = [self autoresizingMaskForLeftViewContainer];
}
lvFrame = [self leftViewFrame];
lvFrame.origin.y += 20; // ugly hack demo code only! don't really do it this badly!
lvFrame.size.height -= 20;
self.leftViewContainer.frame = lvFrame;
[self.view insertSubview:self.leftViewContainer belowSubview:self.frontViewContainer];
[self.leftViewController didMoveToParentViewController:self];
}
}
The above hack is almost enough, if I also add this to UIViewController+PKRevealController.m:
-(UIStatusBarStyle)preferredStatusBarStyle{
return UIStatusBarStyleBlackOpaque;
}
The above code, when added, causes the following hint/warning:
Category is implementing a method that will also be implemented by its primary class.
I'm including the above notes to show what I've tried, and I welcome some idea of how the real experts are doing this.
My own modified copy of the PKRevealController code, including the hack above, in a slightly improved form, is found here: https://github.com/wpostma/PKRevealController
I've been struggling with PKRevealController as well. While I'm still looking for better solutions I will share what I came up with until now.
My two problems were:
Status bar style was always the same and I wanted a different style for the front view and the menu;
The menu view top cell (it's a table view controller) showed up behind the status bar.
1. Dynamic status bar style
First I had my own PKRevealController subclass where I was having a custom initialiser and some custom methods to load new view controllers into the front view navigation view controller. But that's not relevant for now.
I used this subclass to implement preferredStatusBarStyle as follows so that the reveal controller can provide the right style for each state:
- (UIStatusBarStyle)preferredStatusBarStyle {
switch (self.state) {
case PKRevealControllerFocusesLeftViewController:
return [self.leftViewController preferredStatusBarStyle];
break;
case PKRevealControllerFocusesRightViewController:
return [self.rightViewController preferredStatusBarStyle];
break;
case PKRevealControllerFocusesFrontViewController:
return [self.frontViewController preferredStatusBarStyle];
break;
case PKRevealControllerFocusesLeftViewControllerInPresentationMode:
return [self.leftViewController preferredStatusBarStyle];
break;
case PKRevealControllerFocusesRightViewControllerInPresentationMode:
return [self.rightViewController preferredStatusBarStyle];
break;
default:
return UIStatusBarStyleDefault;
break;
}
}
This alone doesn't work however. You still have to say that the status bar style needs to change with setNeedsStatusBarAppearanceUpdate. As Apple says this should be called from inside an animation loop and you can find one in PKRevealController's setFrontViewFrameLinearly method. This is how it looks after I've modified it:
- (void)setFrontViewFrameLinearly:(CGRect)frame
animated:(BOOL)animated
duration:(CGFloat)duration
options:(UIViewAnimationOptions)options
completion:(PKDefaultCompletionHandler)completion
{
[UIView animateWithDuration:duration delay:0.0f options:options animations:^
{
self.frontViewContainer.frame = frame;
if ([[[UIDevice currentDevice] systemVersion] compare:#"7.0" options:NSNumericSearch] != NSOrderedAscending) {
[self setNeedsStatusBarAppearanceUpdate];
}
}
completion:^(BOOL finished)
{
safelyExecuteCompletionBlockOnMainThread(completion, finished);
}];
}
If you try it out at this point the styles will be mixed up. You can quickly conclude that by the time preferredStatusBarStyle is called the reveal controller state is still not changed. For that go to every method that sets the state, e.g. enterPresentationModeForRightViewControllerAnimated and set the state before it calls any change to the frame (the one is going to trigger the animation loop). I did it in 5 different places.
2. Left/Right menu with inset
For this one I have to say I used a workaround: I've just set a header view on the table view (tableHeaderView property).
Put this in viewDidLoad of your UITableViewController:
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0.f, 0.f, self.tableView.frame.size.width, 20.f)];
headerView.backgroundColor = [UIColor whiteColor];
self.tableView.tableHeaderView = headerView;
Don't forget to add some condition so it doesn't get executed in iOS 6. Use this other answer to know how to do it.
If you don't need iOS 5- support, you could use autolayout and align topmost views to topLayoutGuide.
So, for example, if your left view controller is a UIViewController with a UITableView in it, you could snap UITableView's top edge to the topLayoutGuide.
You can do it in (1) IB (storyboard) or (2) from code.
I personally prefer the first approach, as far as it removes the need of unnecessary code. You just open your storyboard and snap your table view's top edge to topLayoutGuide. In iOS 7 you'll end up with topLayoutGuide constraint, in iOS6 topLayoutGuide constraint is converted to a common to-container-view constant.
If you use second approach, you'll have to make sure you don't use topLayoutGuide in iOS6, something like this:
// assume you'r in your UIViewController subclass
if (![self respondsToSelector:#selector(topLayoutGuide)])
{
// topLayoutGuide is not supported, probably iOS6
// add constraints to snap tableview's top edge to superview's top edge
}
else
{
// cool, topLayoutGuide is supported, probably iOS7
// add constraints to snap tableview's top edge to topLayoutGuide
}
I have attached how UI looks on the different iOS versions.
This is my code below:
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:contactsViewController];
[self.viewController presentModalViewController:navController animated:YES];
iOS 6
iOS 7
First problem is status bar in iOS 6 I have not status bar. Second problem is overdrawing two views. How to solve it?
You may wish to use a resizable image:
[image resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0) resizingMode:UIImageResizingModeStretch];
Or, as AliSoftware suggested, set the extended layout edges to UIRectEdgeNone (be sure to check if edgesForExtendedLayout is supported (your app will crash if it ties to assign this property running on iOS 6.x device):
if ([self respondsToSelector:#selector(setEdgesForExtendedLayout:)]) {
self.edgesForExtendedLayout = UIRectEdgeNone;
}
I would strongly invite you to read Apple's "UI Transition Guide" which explains all these differences between iOS6 and iOS7 and how to adapt your code accordingly.
The easiest way if you want your view to still be under your statusBar even in iOS7 is to set your UIViewController's self.edgesForExtendedLayout = UIRectEdgeNone in its viewDidLoad.
You can hide the status bar in the presented viewcontroller by writing this method
- (BOOL)prefersStatusBarHidden {
return YES;
}