Window for an External Display affects primary screen - ios

I create a new window when there is an additional screen (up to 2). Each window shows a different content, in a different screen.
The problem is under iOS7: Creating and showing this external window makes the status bar visible in the first one, which is also the main one. Then, the system adds some space (20points) to rearrange the top bar and some views. It does not work for me, because it's a custom bar.
Why is this happening and how may I stop the system to add the status bar?
This is the offending code:
- (void) handleScreenConnectNotification:(NSNotification*)notification
{
NSLog(#"screens=%# _secondWindow = %#",[UIScreen screens], _secondWindow );
if ( [[UIScreen screens] count] > 1) {
// Associate the window with the second screen.
// The main screen is always at index 0.
UIScreen * secondScreen = [[UIScreen screens] objectAtIndex:1];
CGRect screenBounds = secondScreen.bounds;
_secondWindow = [[UIWindow alloc] initWithFrame:screenBounds];
_secondWindow.screen = secondScreen;
_secondWindow.hidden = NO;
}
}
I have tried changing the _secondWindow's frame to a smaller area. Does not solve the issue.
To handle the status bar, the app is configured like this
Under the app property list: View controller-based status bar appearance = YES
I added this code for each view I do not want to show the status bar:
- (BOOL)prefersStatusBarHidden
{
return YES;
}

Because you mentioned the "View controller-based status bar appearance" - also try setting the "Status bar is initially hidden" to true.
<key>UIStatusBarHidden</key>
<true/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>

To correctly handle the case you don't want the status bar initially hidden, provide a rootViewController to your second window. This rootViewController must implement -(BOOL)prefersStatusBarHidden.

Related

Unable to click "under" a hidden TabBar

I hide my tab bar like so:
self.tabBarController.tabBar.hidden = YES;
And because now there is a black bar where it once stood I stretch the view which is a UIWebView on top(or is it under?) that empty space. The UIWebView is in a UIViewController. I do that with a constraint which by default is like so:
The code for the constraint:
if(self.tabBarController.tabBar.hidden){
self.webviewBottomConstrain.constant = -self.tabBarController.tabBar.frame.size.height;
}else{
self.webviewBottomConstrain.constant = 0;
}
However if I tap the device on the place where the TabBar was it will not execute. It is as if there is something invisible there with the size of the tab bar. I have also tried hiding it the way this thread sugests. Still the same result.
Update: It seems that when you tap on the invisible tab bar the tap is recognized by the tab bar and not by the view that is visible under the tab bar
self.extendedLayoutIncludesOpaqueBars = YES;
this will solve you problem
You hide your tabBar by setting its hidden property to NO? Try setting it to YES. Unless I am misunderstanding what you are trying to do, it seems like your tab bar is not hidden with that code.
Another thing I would check is to see if User Interaction Enabled is checked for the web view. If it is not, that can seem like there is something invisible blocking you from interacting with your view.
Well I am using quite ugly hack to fix this. I am hiding the tab bar in another way now:
if (shouldShow) {
self.hidesBottomBarWhenPushed = NO;
UIViewController *someView = [[UIViewController alloc] init];
[self.navigationController pushViewController:someView animated:NO];
[self.navigationController popToViewController:self animated:NO];
} else if (shouldHide) {
self.hidesBottomBarWhenPushed = YES;
self.tabBarController.hidesBottomBarWhenPushed = YES;
self.navigationController.hidesBottomBarWhenPushed = YES;
UIViewController *someView = [[UIViewController alloc] init];
[self.navigationController pushViewController:someView animated:NO];
[self.navigationController popToViewController:self animated:NO];
}
I do need that random view because I cannot push the view on itself.
I had the same issue when hiding the tab bar by moving it offscreen to the bottom. My custom UITabBarViewController was intercepting the touch events in the area vacated by the tab bar, so instead of changing the frame of the tab bar to move the tab bar offscreen, I extended the height of my tab bar view controller so that the tab bar still moved offscreen, but the child view above the tab bar now filled that space. This allowed the touches to be received by the child view.
As you may see with view hierarchy instrument, UITabBar is not directly blocking your tap, but your current view controller's view height is not full screen:
So, the tap doesn't response because your finger's y position is higher than view's maxY.
Code like this (inside your UITabBarController) will expand your view's height, according to tabbar visibility, and all tap events will work correctly.
func updateTabBarAppearanceWithDegree(_ degree: CGFloat) {
let screenHeight = UIScreen.main.bounds.size.height
let tabBarHeight = self.tabBar.frame.size.height
self.tabBar.frame.origin.y = screenHeight - tabBarHeight * degree
self.tabBar.alpha = degree
let currentNavigation = self.selectedViewController as? UINavigationController
if let currentTopView = currentNavigation?.viewControllers.last?.view {
currentTopView.frame.size.height = self.tabBar.frame.origin.y
}
}

Slide UIPageViewController page over the status bar

I am trying to find a clean way to handle UIPageViewController and the status bar. I noticed Snapchat does it perfectly by sliding the viewcontroller OVER the status bar when you are sliding to a new page that does not show the status bar. It looks like this...
Does anyone know how this is being done? The only way I can think of is by using a different UIWindow, but how would you implement a UIPageViewController with multiple UIWindows? If that is even what is being done. Otherwise how is this effect being achieved?
Basically create a UIViewController, put the UIPageViewController inside that. Make the size of UIVC the size of iphone screen. Then set the appdelegate.window.rootviewcontroller as your UIViewControllers
Here is a great example repo for this task: https://github.com/goktugyil/EZSwipeController
Ok so this is actually done using some clever tricks.
Basically it's not actually moving the status bar, its moving an image of the status bar.
Disclaimer: I YOLO'd this implementation so I make no guarantees on if it'll work out of the box
Starting with iOS7 you can take a picture using
UIView *screen = [[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO];
There's a bit more useful information I saw here. But basically the status bar is cropped from the picture and added to a UIWindow with a windowLevel above UIWindowStatusLevelBar that will mask the real status bar. Something like:
UIWindow *statusBarWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
statusBarWindow.windowLevel = UIWindowLevelStatusBar;
statusBarWindow.hidden = NO; // fun fact you don't have to add a UIWindow to anything, just setting hidden = NO should display it
statusBarWindow.backgroundColor = [UIColor clearColor]; // I have no idea if this is necessary but since it's now visible you def don't want it to have a color
...
// The view that will hold the screen shot
UIView *statusBarView = [[UIView alloc] initWithFrame:[UIApplication sharedApplication].statusBarFrame];
statusBarView.clipsToBounds = YES;
// Now we add the screen shot we took to this status bar view
[statusBarView addSubview:screen]; // screen is the UIView from previous code block
// Now add this statusBarView with the image to the window we created
[statusBarWindow addSubview:statusBarView];
Now the rest really depends on what your implementation is looking like but from here you really just need to handle moving the view with either a pan, or whatever action is causing the new view to come in through the side:
// you're going to want to hide the status bar when this action starts so that your new window is visible
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
// then inside whatever method is handling the sliding you're going to adjust the frame of the statusBarView
// this can be done explicitly by setting a new frame, or animating its x position, or whatever
// if you're doing it explicitly something like:
CGFloat offset = self.viewThatIsScrolling.contentOffset.x; // may be negative depending on direction that is being swiped
statusBarView.frame = CGRectMake(offset, 0, self.statusBarView.frame.width, 20.0f);
// or maybe even
statusBarView.transform = CGAffineTransformMakeTranslation(offset -previousOffsetAmount, 0); // you only want it to move over by whatever new amount so it can match up, this will have to be tracked somehow if you go this route
...
// and finally once the 'sliding' is done don't forget to remove the screenshot and unhide the status bar
// (can check by looking at the offset value from earlier or as a callback after animation, or whatever)
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
[statusBarView removeFromSuperview];
statusBarView = nil;
Also note this doesn't handle orientation changes or anything (the fixed frame might cause it to not work). you could probably use autoResizingMask or something

Black status bar in ios 7 - secondary issue

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.

Handling In-Call Status Bar with Custom Modal Presentation

The Problem
I've noticed some strange behavior when presenting a UINavigationController (with a root view controller, already pushed, naturally) with UIViewControllerAnimatedTransitioning during a phone call.
If the in-call status bar is enabled after the the navigation controller is presented, the navigation controller shifts its view down as expected. But when the call is ended, the controller does not shift its view back up, leaving a 20p gap under the status bar.
If the in-call status bar is enabled before presenting the controller, the controller does not account for the status bar at all, leaving 4p of the 44p-high navigation bar peeking out from under the 40p status bar. When the call is ended, the controller shifts its view down to accommodate the normal 20p status bar.
*note: this was tested on the simulator, due to the ease of enabling/disabling the in-call status bar, but testers have observed this phenomenon on actual phones.
My (Partial) Workaround
I hacked around the issue by adjusting the frame of the controller during presentation, if the status bar was an abnormal height:
#interface CustomAnimationController : NSObject <UIViewControllerAnimatedTransitioning>
#end
#implementation CustomAnimationController
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *toController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *container = [transitionContext containerView];
CGRect frame = [transitionContext finalFrameForViewController:toController];
if (CGRectEqualToRect(frame, CGRectZero))
{
// In my experience, the final frame is always a zero rect, so this is always hit
UIEdgeInsets insets = UIEdgeInsetsZero;
// My "solution" was to inset the container frame by the difference between the
// actual status bar height and the normal status bar height
insets.top = CGRectGetHeight([UIApplication sharedApplication].statusBarFrame) - 20;
frame = UIEdgeInsetsInsetRect(container.bounds, insets);
}
toController.view.frame = frame;
[container addSubview:toController.view];
// Perform whiz-bang animation here
}
#end
This solution ensures that the navigation bar is below the status bar, but the navigation controller still fails to shift itself back up when the call is ended. So the app is at least usable, but there is an ugly 20p gap above the navigation bar after a call ends.
Is There a Better Way?
Am I missing some critical step to ensure that the navigation controller accounts for the in-call status bar on its own? It works just fine when presented with the built-in modal presentation style.
In my opinion this smacks of a UIKit bug — after all, the navigation controller seems to receive the UIApplicationWillChangeStatusBarFrameNotification (see second point of The Problem). If anyone else has encountered this problem and has found a better way, I would greatly appreciate a solution.
I have spent far too much time on over coming the status bar height issue and have come up with a general solution that works for me and I think will work for your situation as well.
First, a couple things that are odd about the status bar.
It's normally 20 points tall and the screen is normally 568 points tall
While "in-call", the status bar is 40 points high and the screen is 548 points tall
While the status bar is hidden, the status bar is 0 points tall and the screen is 568 points tall
If the status bar changes but you don't update the height of the screen then the calculations will be off, and this can be seen in some pretty big name (and even default) applications.
So, the solution that I've come up with is two fold: 1. Create a macro to get the adjusted screen height 2. Register a notification to update the view when the status bar changes.
Here are the macros, I'd recommend putting these in your prefix file
#define kScreenWidth [UIScreen mainScreen].bounds.size.width
#define kStatusBarHeight (([[UIApplication sharedApplication] statusBarFrame].size.height == 20.0f) ? 20.0f : (([[UIApplication sharedApplication] statusBarFrame].size.height == 40.0f) ? 20.0f : 0.0f))
#define kScreenHeight (([[UIApplication sharedApplication] statusBarFrame].size.height > 20.0f) ? [UIScreen mainScreen].bounds.size.height - 20.0f : [UIScreen mainScreen].bounds.size.height)
Additionally, here's the notification center call that I've found works for me 100% of the time the status bar changes.
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self.view selector:#selector(layoutSubviews) name:UIApplicationWillChangeStatusBarFrameNotification object:nil];
I had the same issue and the problem was with the view that I was presenting which was automatically adjusted by 20pt because of in-call bar. However since I insert the view into container, I had to reset the frame to match container's bounds.
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
UIView* destinationView = [transitionContext viewForKey:UITransitionContextToViewKey];
UIView* container = transitionContext.containerView;
// Make sure destination view matches container bounds
// This is necessary to fix the issue with in-call bar
// which adjusts the view's frame by 20pt.
destinationView.frame = container.bounds;
// Add destination view to container
[container insertSubview:destinationView atIndex:0];
// [reducted]
}
Just set the frame in layoutSubviews as it is called when the status bar height changes. In general as a rule of thumb, do all frame setting in layoutSubviews only.
Here is a workaround I put in my app delegate that fixed the problem for me:
- (void)application:(UIApplication *)application willChangeStatusBarFrame:(CGRect)newStatusBarFrame
{
if (newStatusBarFrame.size.height < 40) {
for (UIView *view in self.window.subviews) {
view.frame = self.window.bounds;
}
}
}
I had the same issue and I fixed it by call update frame view, so i get height status bar. My problem was solved.
UIView *toViewSnapshot = [toView resizableSnapshotViewFromRect:toView.frame afterScreenUpdates:YES withCapInsets:UIEdgeInsetsZero];

Adding a subview above the status bar iOS 7

Ive got a small rectangular view that animates down from the top of my app. I need it to be at the very top of the screen and animate down over the status bar, however the animated view is appearing under the status bar. Anyone know how I can get it over the status bar??
here is what Im doing currently
[self.navigationController.view addSubview:self.headerView];
It works perfectly and is in the correct position EXCEPT for the fact its underneath the status bar. Any ideas?
edit: I know this is possible because snapchat does it.
One option you can do is, when you animating the headerView of yours,
take snapshot of the statusbar
hide statusbar
add subview of snapshot view on the statusbar position
do the header view animation over snapshot view
remove snapshot view and show statusbar again.
This way you can get nice animation which looks like it's doing over status bar.
Edit: I will try to explain in pseudo-code
// 1. take snapshot of the status bar
UIView* snapshotView = [[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO];
// 2. hide statusbar
_statusBarHidden = YES;
[self setNeedsStatusBarAppearanceUpdate];
// you need to set "View controller-based status bar appearance" option to yes on plist
- (BOOL)prefersStatusBarHidden
{
return _statusBarHidden;
}
// 3. add subview of snapshot view on the statusbar position
CGRect frame = self.view.frame;
frame.height = [[UIApplication sharedApplication] statusBarFrame].size.height;
[self.view addSubView:snapshotView];
// 4. do the header view animation over snapshot view
.. Just do the animation you already were doing
// 5. remove snapshot view and show statusbar again.
[snapshotView removeFromSuperview];
_statusBarHidden = NO;
[self setNeedsStatusBarAppearanceUpdate];

Resources