In my application by default status bar is visible. And my app supports landscape as well as portrait orientations.
When I want to play the video, i'm hiding the status bar, so that my video will be shown full screen. And when video completes, i'm bringing the status bar back.
I'm using following code to show the video:
UIWindow* window = [[UIApplication sharedApplication] keyWindow];
[window addSubview:playerView];
(I can't use view controllers in my code. That's a restriction)
Now the problem:
My app is in landscape now, on click of a button, i'll hide the status bar and starts playing video. When video is playing, i change the orientation of the phone to portrait and I allowed video to complete. When video completed, the device is still in portrait mode, player view is removed and status bar is shown again. Now I noticed that my portrait view is moved 20 pixels up and over that the status bar is showing. But when I started the app for firs time, status bar is shown first and below it, my view is shown.
How should I handle this situation?
In simple use the following code in a view controller.
- (void)viewDidLoad {
[[UIApplication sharedApplication] setStatusBarHidden:YES];
[self performSelector:#selector(showStatusAgain) withObject:nil afterDelay:6.0];
[super viewDidLoad];
}
-(void)showStatusAgain
{
[[UIApplication sharedApplication] setStatusBarHidden:NO];
}
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
when u run the app with above code, it will start in portrait. now rotate it to landscape. and you can notice the issue.
I encountered a similar problem, which I solved with a category on UINavigationController.
UINavigationController+LayoutCorrecting.h:
#interface UINavigationController (LayoutCorrecting)
- (void)recalculateNavigationBarFrameRelativeToStatusBar;
#end
UINavigationController+LayoutCorrecting.m:
#import "UINavigationBar+LayoutCorrecting.h"
#import "UIViewAdditions.h"
#implementation UINavigationController (LayoutCorrecting)
- (void)recalculateNavigationBarFrameRelativeToStatusBar {
CGRect sbf = [[UIApplication sharedApplication] statusBarFrame];
if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
self.navigationBar.top = sbf.size.width;
else
self.navigationBar.top = sbf.size.height;
}
#end
If you tweak this category a bit by declaring it on UIViewController, and making sure it adjusts controller.view.frame rather than navigationBar.top, I think you'll be good to go. Just make sure to call the category method on your view controller after your movie finishes playing and you've already shown the status bar.
And just for the sake of staving off any confusion, it's probably worth mentioning you need to use the width of the status bar in landscape mode because UIWindow's coordinate system always works as though it's in portrait, despite how you hold the device. This is in contrast to subviews (UIView instances) that get managed by a UIViewController, which benefit from automatic coordinate transformations.
P.S.: Since you're going to have to tweak your view's frame in a window context, this function may also help.
CGRect CGRectOffsetRectForOrientation(CGRect rect, CGFloat dx, CGFloat dy, UIInterfaceOrientation orientation) {
CGRect newRect = rect;
switch (orientation) {
case UIInterfaceOrientationPortrait:
newRect.origin.x += dx;
newRect.origin.y += dy;
break;
case UIInterfaceOrientationPortraitUpsideDown:
newRect.origin.x -= dx;
newRect.origin.y -= dy;
break;
case UIInterfaceOrientationLandscapeLeft:
newRect.origin.y -= dx;
newRect.origin.x += dy;
break;
case UIInterfaceOrientationLandscapeRight:
newRect.origin.y += dx;
newRect.origin.x -= dy;
break;
default:
break;
}
return newRect;
}
Try to update the status bar style when you show it:
[[UIApplication sharedApplication] setStatusBarHidden:NO];
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefualt];
Related
iOS Developers will surely knows about the issue about status bar and the famous "slide/hamburger/drawer". The issue is well explained here: http://uxmag.com/articles/adapting-ui-to-ios-7-the-side-menu
I'm using MMDrawerController library and it has a nice hack that lets us to create a dummy status bar just above the container view controller. Unfortunately this doesn't work really good. What's the news? The news is that I stumbled upon an app (Tinder) that perfectly solve this mind blowing issue. I've created a gif that perfectly shows what Tinder does.
You need to wait a few seconds for seeing the gif because there's a bug in it and I don't know how to get rid of. Just wait one/two seconds and you will able to see the gif correctly.
Anyway, what Tinder does? When the user taps on the top left menu button and begin to swipe right the status bar fades out neatly. And when the view is revert to the original position the status bar will show up again.
I am both happy and a bit sad for this because this means that a way must be to do it but I really don't know how to implement it (perhaps hacking MMDrawerController). Any help will be so much appreciated.
IMPORTANT
Please pay attention to the fact that the method setStatusBarHidden: will completely hide the status bar, this means that the entire view is with a height -20px. This is obviously not the solution because as you can see from the gif the view is not stretched.
Your main problem is with MMDrawerController. If you'll digg into it you'll find a lot of methods statusbar related such as setShowsStatusBarBackgroundView setStatusBarViewBackgroundColor and more. Something in their code pushes the view up when the statusbar is hidden.
Alternatively you can use another drawer controller or use custom code.
Here's a simple way how to accomplishe this:
ViewControllerA:
-(BOOL)prefersStatusBarHidden
{
return _hidden;
}
- (void)statusHide
{
[UIView animateWithDuration:0.4 animations:^() {[self setNeedsStatusBarAppearanceUpdate];
}completion:^(BOOL finished){}];
}
ViewControllerB: (Container in ViewControllerA)
- (IBAction)move:(UIButton *)sender
{
parent = (ViewController*)self.parentViewController;
parent.hidden = !parent.hidden;
CGRect frame = parent.blueContainer.frame;
if(parent.hidden)
{
frame.origin.x = 150;
}
else
{
frame.origin.x = 0;
}
[UIView animateWithDuration:1 animations:^() {parent.blueContainer.frame = frame;}completion:^(BOOL finished){}];
[parent statusHide];
}
For iOS 6 compatieblty use:
[[UIApplication sharedApplication] setStatusBarHidden:_hidden withAnimation:UIStatusBarAnimationFade];
The table view and other subviews will stay in their location and won't be pushed up.
Edit:
Adding a NavigationBar:
UINavigationController will alter the height of its UINavigationBar to
either 44 points or 64 points, depending on a rather strange and
undocumented set of constraints. If the UINavigationController detects
that the top of its view’s frame is visually contiguous with its
UIWindow’s top, then it draws its navigation bar with a height of 64
points. If its view’s top is not contiguous with the UIWindow’s top
(even if off by only one point), then it draws its navigation bar in
the “traditional” way with a height of 44 points. This logic is
performed by UINavigationController even if it is several children
down inside the view controller hierarchy of your application. There
is no way to prevent this behavior.
Taken from here
You could very simply subclass UINavigationController and create your own navbar to avoid this annoyness.
i don't know if it will sove your problem but i got almost the same effect using the SWRevealViewController project. In the appDelegate I've set the delegate method from this class to do this:
- (void)revealController:(SWRevealViewController *)revealController willMoveToPosition:(FrontViewPosition)position {
#ifdef DEBUG
NSArray *teste = #[#"FrontViewPositionLeftSideMostRemoved",#"FrontViewPositionLeftSideMost",#"FrontViewPositionLeftSide",#"FrontViewPositionLeft",#"FrontViewPositionRight",#"FrontViewPositionRightMost",#"FrontViewPositionRightMostRemoved"];
NSLog(#"%# %d", teste[position], position);
#endif
if (position == FrontViewPositionRight)
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationFade];
UINavigationController *frontViewController = (id)revealController.frontViewController;
frontViewController.navigationBar.centerY += (position == FrontViewPositionRight) ? 20 : 0; // 20 == statusbar heihgt
}
- (void)revealController:(SWRevealViewController *)revealController didMoveToPosition:(FrontViewPosition)position {
if (position == FrontViewPositionLeft)
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
}
centerY is a category in the UIView which sets the center.y without dealing the boring part of setting frame variables.
Here is how you should do that in iOS 7:
#implementation ViewController
{
BOOL _hideStatusBar;
}
-(UIStatusBarStyle)preferredStatusBarStyle
{
return UIStatusBarStyleDefault;
}
-(UIStatusBarAnimation)preferredStatusBarUpdateAnimation
{
return UIStatusBarAnimationFade;
}
-(BOOL)prefersStatusBarHidden
{
return _hideStatusBar;
}
-(void)setStatusBarHidden:(BOOL)hidden
{
[UIView animateWithDuration:1.0 animations:^{
_hideStatusBar = hidden;
[self setNeedsStatusBarAppearanceUpdate];
}];
}
#end
Check out the method setStatusBarHidden:withAnimation: on UIApplication. It will allow you to show or hide the status bar and the animation can be none, fade, or slide. You just need to add a call to hide the bar and one to show the bar at the correct times and decide if you like the fade as you illustrated or if the slide works better for you.
https://developer.apple.com/library/ios/DOCUMENTATION/UIKit/Reference/UIApplication_Class/Reference/Reference.html#//apple_ref/occ/instm/UIApplication/setStatusBarHidden:withAnimation:
You can used -setStatusBarHidden:withAnimation: if you adjust your views frame in -viewDidAppear:, then you will not see any stretch.
Note that autolayout is disabled.
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
CGRect frame = self.view.frame;
// adjust root view frame
frame.origin.y -= 20;
frame.size.height += 20;
[self.view setFrame:frame];
// adjust subviews y position
for (UIView *subview in [self.view subviews])
{
CGRect frame = subview.frame;
frame.origin.y += 20;
[subview setFrame:frame];
}
}
- (IBAction)sliderChanged:(id)sender
{
UISlider *s = (UISlider *)sender;
if (s.value > .5)
{
UIApplication *app = [UIApplication sharedApplication];
if (![app isStatusBarHidden])
[app setStatusBarHidden:YES withAnimation:UIStatusBarAnimationFade];
}
else
{
UIApplication *app = [UIApplication sharedApplication];
if ([app isStatusBarHidden])
[app setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
}
}
I am interested to implement left side menu in my application I have used NVSlideMenuController for it. and it works fine.
But I want to modify it. I want to slide status bar with contentViewController and don't want status bar on MenuViewController.
currently it will look like below image
and I want to same as below image
Thanks In advance
You can try to turn off your 3G on second image, you will notice that statusBar didn't update.
It seems like a new api in iOS7
[[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO];
This may be the same question you ask, and there are a demo to show what you want.
Moving status bar in iOS 7
You may be able to do this by chaining the frame of the keyWindow (you can get this with [[UIApplication sharedApplication] keyWindow] (UIWindow is a subclass of UIView). This should move everything right, and you may need to make another UIWindow to bring other stuff on from the left.
You're not going to be able to move the status bar. The best you can do is hide it for that view controller:
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (animated)
{
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide];
}
else
{
[[UIApplication sharedApplication] setStatusBarHidden:YES];
}
}
- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (animated)
{
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
}
else
{
[[UIApplication sharedApplication] setStatusBarHidden:NO];
}
}
Try playing around with the different values of UIStatusBarAnimation to see what looks best. There are three values: UIStatusBarAnimationNone, UIStatusBarAnimationFade, and UIStatusBarAnimationSlide`.
This's not an answer but a reply to James's answer because the formatting breaks on comment.
The fact is that setting the frame of the keyWindow will not be able to move the status bar. By setting a break point and printing out the keyWindow recursive description, we'll notice there's no status bar info inside.
(lldb) po [[[UIApplication sharedApplication] keyWindow] recursiveDescription]
<UIWindow: 0x8c5bf80; frame = (0 0; 320 480); gestureRecognizers = <NSArray: 0x8c5c500>; layer = <UIWindowLayer: 0x8c5c0a0>>
| <UIView: 0x8b49700; frame = (0 0; 320 480); autoresize = W+H; layer = <CALayer: 0x8b496b0>>
(lldb)
Follow Chris's link to Simon's answer does the job
I'm running into a bit of a weird problem when hiding the status bar after the view had loaded. If I add the following method in the ViewDidLoad method, the status bar is completely removed from the view:
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide];
However, if I call this method in an IBAction or another method, the status bar still slides away but leaves a black bar the same height as itself behind.
I've thought about shifting the entire view up by 20px but is this really a fix? I don't want to just overlap a black bar incase the status bar height changes in future OS upgrades.
Hard-coding any number is always counter to future proofing. Your concerns are correct. There is a bit of a trick to properly handling the hiding of the statusBar. But all the needed information is available.
For example the UIApplication singleton has a property named statusBarFrame which is precisely what it sounds like, a CGRect of the statusBar's frame. The cool thing is that once you have called setStatusBarHidden:withAnimation: that property will give you the new frame, even before the animation completes. So really you are simply left with some basic math to adjust the view's frame.
In short your gut feeling is correct; always compute things live.
I've had good success with a category method like this. (Even when toggling in-call status bar in simulator(Command - T)):
#implementation UIApplication (nj_SmartStatusBar)
// Always designate your custom methods by prefix.
-(void)nj_setStatusBarHidden:(BOOL)hidden withAnimation:(UIStatusBarAnimation)animation{
UIWindow *window = [self.windows objectAtIndex:0];
UIViewController *rootViewController = window.rootViewController;
UIView *view = rootViewController.view;
// slight optimization to avoid unnecassary calls.
BOOL isHiddenNow = self.statusBarHidden;
if (hidden == isHiddenNow) return;
// Hide/Unhide the status bar
[self setStatusBarHidden:hidden withAnimation:animation];
// Get statusBar's frame
CGRect statusBarFrame = self.statusBarFrame;
// Establish a baseline frame.
CGRect newViewFrame = window.bounds;
// Check if statusBar's frame is worth dodging.
if (!CGRectEqualToRect(statusBarFrame, CGRectZero)){
UIInterfaceOrientation currentOrientation = rootViewController.interfaceOrientation;
if (UIInterfaceOrientationIsPortrait(currentOrientation)){
// If portrait we need to shrink height
newViewFrame.size.height -= statusBarFrame.size.height;
if (currentOrientation == UIInterfaceOrientationPortrait){
// If not upside-down move down the origin.
newViewFrame.origin.y += statusBarFrame.size.height;
}
} else { // Is landscape / Slightly trickier.
// For portrait we shink width (for status bar on side of window)
newViewFrame.size.width -= statusBarFrame.size.width;
if (currentOrientation == UIInterfaceOrientationLandscapeLeft){
// If the status bar is on the left side of the window we move the origin over.
newViewFrame.origin.x += statusBarFrame.size.width;
}
}
}
// Animate... Play with duration later...
[UIView animateWithDuration:0.35 animations:^{
view.frame = newViewFrame;
}];
}
#end
Why are you calling this in viewDidLoad?
Try it in loadView?
- (void)loadView {
[super loadView];
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide];
}
Yes, moving the view 20 pixels should fix your problem. The black is absence of anything to display, not an actual black bar.
As for potential status height changes, this fix will not work if that happens because the view will be moved by the height of the new status bar. If that happens, you will have to either add different offsets for different status bars, or find a completely new solution.
In viewWillAppear:
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationFade];
In viewDidAppear, you can insert:
self.view.window.rootViewController.view.frame = [UIScreen mainScreen].applicationFrame;
In viewWillDisappear:
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
I was able to fix this issue by calling:
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide];
as others recommended before presenting the view controller that is displaying the black bar.
For example, if I had an action that presented ViewController, I would call it like so:
- (IBAction)presentViewController:(id)sender {
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide];
ViewController *vc = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
[self presentViewController:vc animated:YES completion:nil];
}
I put this code into all of my viewControllers:
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if (orientation == UIInterfaceOrientationLandscapeRight) {
CGAffineTransform transform = self.view.transform;
// Use the status bar frame to determine the center point of the window's content area.
//CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
//CGRect bounds = CGRectMake(0, 0, statusBarFrame.size.height, statusBarFrame.origin.x);
CGPoint center = CGPointMake(320/2, 480/2);
// Set the center point of the view to the center point of the window's content area.
self.view.center = center;
// Rotate the view 90 degrees around its new center point.
transform = CGAffineTransformRotate(transform, (M_PI / 2.0));
self.view.transform = transform;
}
and I also have:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationLandscapeRight);
}
I can see that the codes work correctly as they should, but once in a while, there is a rare occasion that the main view controller failed to rotate for landscape mode after I put its view back to be the main view. The chance that it occurs is very rare, but this is nagging me. Moreover, I can see that it occurs more often on the iphone. On the simulator, I recalled it never happen. Do you know what are the possible causes for this? Thank you in advance.
I found the cause of my iphone program.
One of the possible causes is memory. This method below will be called when the system is low on memory:
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
An "unseen" UIView is unloaded when this method is called. When you are about to see this view again, the device reloaded it, but maybe because you set the parameters (for the rotation, etc.) in a place where it won't be called when it gets reloaded. You may use:
- (void)viewDidLoad {
UIApplication* application = [UIApplication sharedApplication];
application.statusBarOrientation = UIInterfaceOrientationLandscapeRight;
CGAffineTransform landscapeTransform = CGAffineTransformMakeRotation([MyMath radd:90]);
landscapeTransform = CGAffineTransformTranslate (landscapeTransform, -80, 83);
[self.view setTransform: landscapeTransform];
[super viewDidLoad];
}
It occurs in the device because of we are talking real memory on the real device. You can use "Hardware > Simulate Memory Warning" when you are using the simulator.
I'm having a real issue with UITabBarController.
The outcome I'm after is the following:
1) in portrait mode, a simple tab bar based application (with navigation bars) nothing too fancy.
2) in landscape mode, I want to use my own UIViewController ignoring the UITabBar completely.
The approach (I tried many variants) I tried last which I fail to understand why is not "working" is the following:
I have a custom UIViewController (Call this AA) that is suppose to manage "everything".
This controller is added to the window in application start and in its loadView creates two controllers: a UITabBarController (Call this TBC) and a UILandscapeController (Call this LSC). then I add the tabbarcontroller view as a subview of AA's view.
now in AA class I override the didRotate blah or willRotate blah and basically want to switch between the two views, by this I means something like: (pseudo code):
going from portrait to landscape:
[TBC.view removeFromSuperView];
[AA.view addSubview:LSC.view];
and when returning to portrait reverse it.
[LSC.view removeFromSuperView];
[AA.view addSubview:TBC.view];
The amount of problems I have (well, it simple rotates wrongly creating a real messed up interface) are something completely unexplained. It seems like the tabbarcontroller view does not "like" at all to be in the standard view heirarchy but rather it wants to be attached directly to the screen.
I wonder what is the best approach to achieve my goal and why the tabbar does not like to be a subview of a view,
any hints mostly appreciated.
-t
Just in case you still need the answer, or someone else stumbles onto this, I've done the same thing and got it working, but there are a couple of hoops you have to jump through. In order to rotate a UITabBarController's view, there are four things you have to do:
Remove the status bar before switching to the view
Rotate the view to the new frame
Add the status bar back to the view
Switch to the view.
I've got a RootRotationController that does this that looks like this:
#implementation RootRotationController
#define degreesToRadian(x) (M_PI * (x) / 180.0)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
if ((UIInterfaceOrientationPortrait == interfaceOrientation) || (UIInterfaceOrientationPortraitUpsideDown == interfaceOrientation)) {
[[UIApplication sharedApplication] setStatusBarHidden:YES animated:NO];
}
// Return YES for supported orientations
return YES;
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
[super willAnimateRotationToInterfaceOrientation:interfaceOrientation duration:duration];
if (UIInterfaceOrientationLandscapeLeft == interfaceOrientation) {
self.view = self.landscape.view;
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(-90));
self.view.bounds = CGRectMake(0, 0, 480, 300);
} else if (UIInterfaceOrientationLandscapeRight == interfaceOrientation) {
self.view = self.landscape.view;
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(90));
self.view.bounds = CGRectMake(0, 0, 480, 300);
} else if (UIInterfaceOrientationPortrait == interfaceOrientation) {
mainInterface.view.transform = CGAffineTransformIdentity;
mainInterface.view.transform = CGAffineTransformMakeRotation(degreesToRadian(0));
mainInterface.view.bounds = CGRectMake(0, 0, 300, 480);
[[UIApplication sharedApplication] setStatusBarHidden:NO animated:NO];
self.view = mainInterface.view;
} else if (UIInterfaceOrientationPortraitUpsideDown == interfaceOrientation) {
mainInterface.view.transform = CGAffineTransformIdentity;
mainInterface.view.transform = CGAffineTransformMakeRotation(degreesToRadian(180));
mainInterface.view.bounds = CGRectMake(0, 0, 300,480);
[[UIApplication sharedApplication] setStatusBarHidden:NO animated:NO];
self.view = mainInterface.view;
}
}
In addition, you should know that shouldAutorotateToInterfaceOrientation is called just after adding the root controller's view to the window, so you'll have to re-enable the status bar just after having done so in your application delegate.
Your problem comes from the typo, I think. Change removeFromSuperView to removeFromSuperview.
Though, it still has a problem. Tab bar doesn't rotate properly. It go upwards till it disappers.
How about not removing the tab bar, and make it transparent.
Check out the UIViewController instance method rotatingFooterView in the docs.
Or, you may manage TabBar by yourself, not through the UITabBarController.