setStatusBarHidden:NO after XIB load covers UINavigationBar - ios

When setStatusBarHidden:NO is set before the view loads, the UINavigationBar and other elements appear aligned immediately below the StatusBar as they should. However, when setStatusBarHidden:NO is set after the view loads, the UINavigationBar is partially covered.
The StatusBar must be revealed after loading the said view, but how can this be done without encountering the aforementioned problem?

I found a hack in a code of mine, though can't remember or find where it came from. The trick is to refresh the navigation bar by hiding and reshowing it:
[self.navigationController setNavigationBarHidden:YES animated:NO];
[self.navigationController setNavigationBarHidden:NO animated:NO];
In my code the function looks like this:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
[self.navigationController setNavigationBarHidden:YES animated:NO];
[self.navigationController setNavigationBarHidden:NO animated:NO];
}
However, BE WARNED, this is a hack, and currently I'm struggling with some bugs that appear to originate from this code (navigation item doesn't match navigation content). But since it did work for me in some places, I'd thought I'd mention it.
Edit:
I think I found the initial post here:
How do I get the navigation bar in a UINavigationController to update its position when the status bar is hidden?
GL,
Oded

(I realise this was an old question, but I just spent half an hour trying to find the answer myself without success, so I thought I would post it here for anyone else who get stuck... especially if you are trying to SHOW the status bar and your view is ending up overlapping it)
I found this works if you want to HIDE the status bar...
[[UIApplication sharedApplication] setStatusBarHidden:YES];
[self.view setFrame: [[UIScreen mainScreen] bounds]];
but not when you want to SHOW the status bar...
in that case I use this solution which works, but worries me because it hard codes the status bar height to 20...
it also worries me that I have to adjust the view differently depending on orientation. but if I didn't do that it always had the 20 point gap on the wrong edge.
In my case I want to turn the status bar off for some views, and then back on when I return. I had particular problems if I rotated the device while the bar was off. so the switch statement, although ugly (someone might post a cleaner solution), works.
[[UIApplication sharedApplication] setStatusBarHidden:NO];
CGRect frame = [[UIScreen mainScreen] bounds];
switch (self.interfaceOrientation)
{
case UIInterfaceOrientationPortrait:
frame.origin.y = 20;
frame.size.height -= 20;
break;
case UIInterfaceOrientationPortraitUpsideDown:
frame.origin.y = 0;
frame.size.height -= 20;
break;
case UIInterfaceOrientationLandscapeLeft:
frame.origin.x = 20;
frame.size.width -= 20;
break;
case UIInterfaceOrientationLandscapeRight:
frame.origin.x = 0;
frame.size.width -= 20;
break;
}
[self.view setFrame:frame];

My guess is the nav bar is being loaded before the status bar is shown, so the position of the nav bar is (0,0) which then overlaps with the status bar at (0,0). You can just move the frame of the navigation bar (or set up an animation block) in viewDidLoad, after you call setStatusBarHidden:NO.
Try doing navigationBar.frame = CGRectMake(0,20,320,44);
The status bar is 320x20, so just moving your navigation bar down by 20 should accomodate for it.

If you are having this problem because you are not displaying the status bar while your Default.png is loading, and then want to display the status bar immediately upon viewing your first View Controller, just make sure you put [[UIApplication sharedApplication] setStatusBarHidden:NO]; before [self.window makeKeyAndVisible]; in your AppDelegate.m. It happens so quick, you won't ever see the status bar on the splash screen.
[[UIApplication sharedApplication] setStatusBarHidden:NO];
[self.window makeKeyAndVisible];

Here's what I'm doing in my root controller now in iOS 5 after I tell the status bar to animate in. Ugly, but it seems to work.
CGRect rect;
if ( self.interfaceOrientation == UIInterfaceOrientationPortrait )
rect = CGRectMake(0, 20, 320, 460);
else if ( self.interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown )
rect = CGRectMake(0, 0, 320, 460);
else if ( self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft )
rect = CGRectMake(20, 0, 300, 480);
else
rect = CGRectMake(0, 0, 300, 480);
[UIView animateWithDuration:0.35 animations:^{ self.view.frame = rect; }];

in iOS 7 you can use:
setNeedsStatusBarAppearanceUpdate
for example:
[self.mainViewController.navigationController setNeedsStatusBarAppearanceUpdate];
apple docs:
Call this method if the view controller's status bar attributes, such
as hidden/unhidden status or style, change. If you call this method
within an animation block, the changes are animated along with the
rest of the animation block.
use it only for iOS 7.

Related

iOS in-Call indicator is pushing down view/content, modifying root view `frame`

I have a problem that my root view (the UIViewController view) is being pushed down by the in-call indicator: window.rootViewController.view.frame is being modifeid (Y is set to 20). As I respond to did/willStatusBarFrameChange on my own, I don't want this behaviour.
I'm looking for the property, or setup, that prevents the modification of the frame in response to an in-call status bar. I use other APIs to respond to changes in the top/bottom frames and iPhone X safe areas.
I've tried things like autoResizingMask, extendedLayoutIncludesOpaqueBars, edgesForExtendedLayout, viewRespectsSystemMinimumLayoutMargins but can't get anything working.
If relevant, the view is also animating down, indicating it's not some side-effect but an intended behaviour somewhere.
I've read many reports of similar behaviour but have yet to figure out if they actually resolved it and/or what the solution actually was (each solution appears to address a slightly different problem).
Related questions: Prevent In-Call Status Bar from Affecting View (Answer has insufficient detail), Auto Layout and in-call status bar (Unclear how to adapt this)
--
I can't provide a simple reproduction, but the portions of code setting up the view looks something like this:
Window setup:
uWindow* window = [[uContext sharedContext] window];
window.rootViewController = (UIViewController*)[[UIApplication sharedApplication] delegate];
[window makeKeyAndVisible];
Our AppDelegate implementation (relevant part)
#interface uAppDelegate : UIViewController<#(AppDelegate.Implements:Join(', '))>
...
#implementation uAppDelegate
- (id)init
{
CGRect screenBounds = [UIScreen mainScreen].bounds;
uWindow* window = [[uWindow alloc] initWithFrame:screenBounds];
return self;
}
We assign our root view to the above delegate, the UIViewController's .view property.
#interface OurRootView : UIControl<UIKeyInput>
UIControl* root = [[::OurRootView alloc] init];
[root setUserInteractionEnabled: true];
[root setMultipleTouchEnabled: true];
[root setOpaque: false];
[[root layer] setAnchorPoint: { 0.0f, 0.0f }];
// some roundabout calls that make `root` the `rootViewController.view = root`
[root sizeToFit];
The goal is that OurRootView occupies the entire screen space at all times, regardless of what frames/controls/margins are adjusted. I'm using other APIs to detect those frames and adjust the contents accordingly. I'm not using any other controller, view, or layout.
It's unclear if there is a flag to disable this behaviour. I did however find a way that negates the effect.
Whatever is causing the frame to shift down does so by modifying the frame of the root view. It's possible to override this setter and block the movement. In our case the root view is fixed in position, thus I did this:
#implementation OurRootView
- (void)setFrame:(CGRect)frame;
{
frame.origin.y = 0;
[super setFrame:frame];
}
#endf
This keeps the view in a fixed location when the in-call display is shown (we handle the new size ourselves via a change in the statusBarFrame and/or safeAreaInsets). I do not know why this also avoids the animation of the frame, but it does.
If for some reason you cannot override setFrame you can get a near similar seffect by overriding the app delegate's didChangeStatusBarFrame and modifying the root view's frame (setting origin back to 0). The animation still plays with this route.
I hope I understand your problem: If you have some indicator like incall, or in my case location using by maps. You need to detect on launching of the app that there is some indicator and re-set the frame of the whole window. My solution for this:
In didFinishLaunchingWithOptions you check for the frame of the status bar, because incall is the part of status bar.
CGFloat height = [UIApplication sharedApplication].statusBarFrame.size.height;
if (height == 20) {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
}
else {
CGRect frame = [[UIScreen mainScreen] bounds];
frame.size.height = frame.size.height - height +20;
frame.origin.y = height-20;
self.window = [[UIWindow alloc] initWithFrame:frame];
}
You can listen to the notification UIApplicationDidChangeStatusBarFrameNotification in your view controller(s) to catch when the status bar has changed. Then you adjust your view controller's main view rectangle to always cover the entire screen.
// Declare in your class
#property (strong, nonatomic) id<NSObject> observer;
- (void)viewDidLoad {
[super viewDidLoad];
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidChangeStatusBarFrameNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
CGFloat newHeight = self.view.frame.size.height + self.view.frame.origin.y;
self.view.frame = CGRectMake(0.0, 0.0, self.view.frame.size.width, newHeight);
}];
}
-(void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:_observer];
}
I tried it on various models, and it works fine, as far as I can tell. On iPhone X the notification is not posted since it does not alter the status bar height on calls.
There is also a corresponding UIApplicationWillChangeStatusBarFrameNotification which is fired before the status bar changes, in case you want to prepare your view in some way.

how to set an uiview fullscreen dynamically?

I have an app which has two subviews.I wanna double tap the video view(the top one) to make it fullscreen to display video.(By fullscreen, I mean it should be in landscape mode) So how should I do in the method -(void)handleTapGesture::(UITapGestureRecognizer*)recognizer?
I guess, first of all, I should hide status bar and navigation bar; then rotate the video view to make it landscape left/right programmatically.
BTW, for some reason, I have to make my app only support portrait mode.Forgive my terrible English, if you don't understand my question clearly, plz leave a comment, thanks.
Update:
I've hided status bar and navigation bar both, but when I make the video view fullscreen, it seems navigation bar and status bar are still there and my video view cannot move up to the top of the screen!
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide];
[self.navigationController.navigationBar setHidden:YES];
[UIView beginAnimations : #"video full screen" context:nil];
[UIView setAnimationDuration:0.3];
[UIView setAnimationBeginsFromCurrentState:YES];
self.videoView.frame = self.view.bounds;
moviewGLView.frame = CGRectMake(0, 0, self.videoView.frame.size.width, self.videoView.frame.size.width*3/4);
moviewGLView.center = self.videoView.center;
videoDefault.center = self.videoView.center;
[UIView commitAnimations];
add this code in your ViewDidLoad() method
UITapGestureRecognizer *tapOnTopView = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(makeTopViewFullScreen)];
tapOnTopView.numberOfTapsRequired = 1;
[topView addGestureRecognizer:tapOnTopView];
add the following function
-(void)makeTopViewFullScreen
{
[UIView animateWithDuration:0.3
animation:^
{
topViewHeightConstraint = self.view.frame.size.height;
bottomViewHeightConstraint = 0;
[self layoutIfNeeded];
}
completion:^
{
//your code to rotate the topView
}
}
note that the topViewHeightConstraint and bottomViewHeightConstraint should be outlets connected in IB.
Hope that helps, let me know if you need more help.

Is it possible to access properties of an iOS system animation?

Is it possible to get the animation properties (speed and easing curve) for a built in iOS system animation? Specifically, the UIStatusBarAnimationSlide when you hide or show the status bar. Right now I'm just eyeballing it and have come up with a good match: .35 seconds using the default animation curve. This works fine, but Apple is liable to change an animation like this in a future iOS update and it would be nice to match it exactly and not rely on hard-coded values I came up with myself.
For what it's worth, here is the method my view controller is calling when I tap the view to hide the status bar and resize the view to fill the screen.
-(void)tappedView:(UIGestureRecognizer *)gestureRecognizer
{
UIApplication *app = [UIApplication sharedApplication];
// First, toggle the visibility of the status bar
[[UIApplication sharedApplication] setStatusBarHidden:![app isStatusBarHidden] withAnimation:UIStatusBarAnimationSlide];
// Then scale this view controller's view, attempting to match the built-in
// UIStatusBarAnimationSlide animation
[UIView animateWithDuration:.35
animations:^{
self.view.frame = [UIScreen mainScreen].applicationFrame;
}];
}
As an aside, I'm surprised I couldn't find a built in way to handle resizing a VC's view when the status bar is hidden. After all, if the status bar doubles its height when a call is in progress, the view resizes automatically. Tell me I'm missing something and there's a way to get the view to grow automatically, too.
Here's a chuck of code I use in my app:
- (void)application:(UIApplication *)application willChangeStatusBarFrame:
(CGRect)oldStatusBarFrame {
[UIView animateWithDuration:0.355f animations:^{
if(floating_point_values_are_equal(oldStatusBarFrame.size.height, 20.0f)) {
for(UIViewController* VC in self.tabBarController.viewControllers) {
UIView* view = VC.view;
[view setTransform:CGAffineTransformMakeScale(1.0f, 1.0f)];
}
} else {
for(UIViewController* VC in self.tabBarController.viewControllers) {
UIView* view = VC.view;
CGFloat ratio = (view.frame.size.height - 20) / view.frame.size.height;
[view setTransform:CGAffineTransformMakeScale(1.0f, ratio)];
}
}
}];
}
It basically scales the entire app depending on the new screen dimensions. It only works because the scale ratio is not a big change- doing this for the new iPhone screen would not look right.

usable view size after status bar and navigation bar

I am writing an iPad app that needs to know the usable area of the view for drawing purposes. The view is added into a Navigation controller, so I have the status bar plus the navigation controller both taking up a certain number of pixels. My app happens to be in landscape mode, although I don't think that's relevant.
I am able to get the correct view size AFTER rotation using didRotateFromInterfaceOrientation. But I can't figure out how to do it without the screen being rotated.
- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
[self.view setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
NSLog(#"drfi %d %d", (int)self.view.frame.size.width, (int)self.view.frame.size.height);
}
^^ that works after rotation. Not before. Can't figure out how to get accurate numbers. And I REALLY don't want to hard wire this.
I will also need this function to be device independent -- it should work on the NEW iPad as well as the older iPad resolutions. I can handle the scaling issues once I know the exact usable area. Why is this so hard? Help!!
I don't think you need to specify your frame's view within the didRotateFromInterfaceOrientation what i will suggest instead is setting some properties to your view autoresizing mask so that it automatically resize itself according to your view orientation.
By setting this for example to your view when your view is loaded (viewDidLoad method):
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
you specify that your view will change its width and height automatically and can get the right values you need to get from there.
You should read this: http://developer.apple.com/library/ios/#documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingViews/CreatingViews.html#//apple_ref/doc/uid/TP40009503-CH5-SW1
for a better understanding of views in iOS
EDIT
Also you probably want to spot what is the orientation of your device which can be accomplish with [[UIApplication sharedApplication] statusBarOrientation];
Your application looks like: there is a start up view, then in this view you will load and add a main view into window, right? Then you should do as below in your main view:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
CGRect frame = self.view.frame;
frame.origin.y = frame.origin.y + 20.0;
self.view.frame = frame;
}
return self;
}
Try this.
CGRect frame = [UIScreen mainScreen].bounds;
CGRect navFrame = [[self.navigationController navigationBar] frame];
/* navFrame.origin.y is the status bar's height and navFrame.size.height is navigation bar's height.
So you can get usable view frame like this */
frame.size.height -= navFrame.origin.y + navFrame.size.height;
You can get this dynamically by combining an instance method with a category method:
Instance method:
This assumes that your view controller (self) is embedded within a navigation controller.
-(int)getUsableFrameHeight {
// get the current frame height not including the navigationBar or statusBar
return [MenuViewController screenHeight] - [self.navigationController navigationBar].frame.size.height;
}
Class category method:
+(CGFloat)screenHeight {
CGFloat screenHeight;
// it is important to do this after presentModalViewController:animated:
if ([[UIApplication sharedApplication] statusBarOrientation] == UIDeviceOrientationPortrait ||
[[UIApplication sharedApplication] statusBarOrientation] == UIDeviceOrientationPortraitUpsideDown){
screenHeight = [UIScreen mainScreen].applicationFrame.size.height;
} else {
screenHeight = [UIScreen mainScreen].applicationFrame.size.width;
}
return screenHeight;
}
The above will consistently give you the usable frame height after the status bar and navigation bar have been removed, in both portrait and landscape.
Note: the class method will automatically deduct the 20 pt for the status bar - then we just subtract the navigation header variable height (32 pt for landscape, 44 pt for portrait).

Forcing a layout position and size in iOS Superviews

I've got a massive problem and I really hope you can help me here... I'm very lost at the moment :(
I've got a project that runs with a MainWindow.xib. In my app delegate file I check the orientation of the device and load an appropriate NIB file that have different layouts (subviews) based on orientation. Here is the code to check the orientation:
-(void)checkTheOrientation
{
if ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeLeft || [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeRight)
{
viewController = [[[MyViewController alloc] initWithNibName:#"MyWideViewController" bundle:nil] autorelease];
NSLog(#"Landscape = MyWideViewController");
}
else if ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationPortrait || [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationPortraitUpsideDown)
{
viewController = [[[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil] autorelease];
NSLog(#"Portrait = MyViewController");
}
}
This works as expected and in Landscape or Portrait I am loading the correct views. The portrait view loads perfectly but the Landscape view loads with a thick black edge to the left as if it's x & y positions are not set to 0 & 0 respectively.
Here is the portrait view: http://uploads.socialcode.biz/2f25352B0e3x3z2t2z21
Here is the landscape view with the bug: http://uploads.socialcode.biz/1G2k3T012d1z0Y1U2q1k
In the MyViewController.m file I have a rough fix to get the sizing done correctly to avoid this big black strip on the left. Here's the code for this:
- (void) performLayout {
// Ensure the main view is properly placed
NSInteger MaxSizeHeight, MaxSizeWidth;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
MaxSizeHeight = 1024;
MaxSizeWidth = 768;
}
if ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeLeft || [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeRight) {
CGRect mainViewFrame = [[self view] frame];
float width = mainViewFrame.size.width;
mainViewFrame.size.width = mainViewFrame.size.height;
mainViewFrame.size.height = width;
mainViewFrame.origin.x = 0;
mainViewFrame.origin.y = 0;
[[self view] setFrame:mainViewFrame];
} else {
CGRect mainViewFrame = [[self view] frame];
mainViewFrame.origin.x = MaxSizeWidth - mainViewFrame.size.width;
mainViewFrame.origin.y = MaxSizeHeight - mainViewFrame.size.height;
[[self view] setFrame:mainViewFrame];
}
// Ensure the content view is properly placed
CGRect contentFrame = [mainContentView frame];
if ([[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeLeft || [[UIApplication sharedApplication] statusBarOrientation] == UIInterfaceOrientationLandscapeRight)
{
contentFrame.size.width = MaxSizeHeight;
contentFrame.size.height = MaxSizeWidth;
}
contentFrame.origin.x = 0;
contentFrame.origin.y = 44;
[mainContentView setFrame:contentFrame];
// Ensure the content subviews are properly placed
contentFrame.origin.y = 0;
for (UIView *contentView in [mainContentView subviews]) {
[contentView setFrame:contentFrame];
}
}
The problem with this method is that this is just a very bad hack and it's not actually solving my problem. When it loads up in Landscape it now resizes and positions the subview to 1024x768,0,0 but any additional subviews that I load via other NIBs have the same problem.
What I would really like to know is how on earth can I set the landscape main superview to be 1024x768 position 0 & 0 without having to try and hack this together and keep performing the performLayout selector? At the moment there is a lot of inconsistence with this as the hack doesn't actually set the superview sizing correctly but rather just the subviews I load on top of the superview.
I thought that maybe a simple fix like this might solve the superview issue but alas it doesn't:
window.frame = CGRectMake(0, 0, 1024, 768);
I just want my main superview to be the placed and sized correctly at load and then the superviews just load in the right place. Please can you help me here???
First, UIView has a method named -layoutSubviews that you can override to position the subviews however you like. It'll be called when the view is first loaded, and again before drawing if you ever send it a -setNeedsLayout message.
Second, you should be able to properly set these positions in your .xib or storyboard file. Select your main view in its .xib file and check its position in the Size inspector. Also, ensure that the view is set to Landscape orientation in the Attributes inspector. If it's all correct, then there's something else going on. To simplify the problem, make a new project in Xcode with nothing but an empty view in landscape orientation. I just did one, and there's no position problem. That's what you want in your real app, so figure out what's happening in your real app to affect the view's position that's not happening in your new sample app.

Resources