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];
Related
In the Snapchat app, the status bar changes alpha when a table view is dragged down to reload.
This is the normal status bar
This is what happens when the table view is dragged down
How is the status bar alpha changed? And how is the frame changed? Originally I thought it was a snapshot, but the clock changes as it normally should.
This should do the trick for the fade animation:
/* Swift 3 */
let statusBarWindow = UIApplication.shared.value(forKey: "statusBarWindow") as? UIWindow
UIView.animate(withDuration: 2) {
statusBarWindow?.alpha = 0.2
}
/* Objective-C */
UIWindow *statusBarWindow = (UIWindow *)[[UIApplication sharedApplication] valueForKey:#"statusBarWindow"];
[UIView animateWithDuration:2.f
animations:^{ [statusBarWindow setAlpha:0.2]; }
completion:nil];
You can also set statusBarWindow's frame and move it how you want. Have fun ;]
They are most likely either grabbing the window the status bar is in and animating that or they are placing a window above the status bar with a view and animating that. I don't have the app to check but it has to be either of these two methods. To place a window above the status bar I think it is window.windowLevel = UIWindowLevelStatusBar + 1 so that it will be above your status bar. If they are grabbing the reference you have to loop through the application windows to find it and I have never tried to find the Statusbar window but I have grabbed the keyboard window. If the clock is indeed being pulled down then it could be a combination of both the getting the window the status bar is in and adding a window above and animating both. Facebook grabs the keyboard window to scroll it up in Facebook Messenger. Hope this helps you.
Edit:
I found an example of getting the status bar window here.
Also I would recommend using CAAnimations and avoid really changing anything. Just use the animations to give that appearance.
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
}
}
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];
I am trying to hide the statusbar but maintain the "bigger" navigationbar height. Right now when I hide the statusbar by setting - (BOOL)prefersStatusBarHidden to YES and then calling [self setNeedsStatusBarAppearanceUpdate];. The problem with this is that the navigationbar will slide up and won't leave space for the notification I'm trying to show. Simply adding a view over the statusbar is not an option, our statusbar/navigation has the fancy blur effect. Does anyone have a clue how to maintain the standard navigationbar height with the status bar height and remove the statusbar from that?
Edit; what I ended up doing is taking a risk and getting the UIWindow of the statusbar via a private API and offsetting that.
Edit 2; App got approved with the private API. Be cautious though!
You can create a custom UIView with its frame as
customView.frame=CGRectMake(0, 20, self.view.frame.size.width, self.view.frame.size.height);
Also hide your status bar by following the below steps
Go to info.plist and add two attributes if not present. set "Status bar is initially hidden" to YES and set UIViewControllerBasedStatusBarAppearance to NO. This will hide status bar for your app.
Add this code in your view Controller:
if ([self respondsToSelector:#selector(setEdgesForExtendedLayout:)])
{
self.edgesForExtendedLayout = UIRectEdgeNone;
}
You should use of positionForBar: method of UIBarPositioningDelegate Protocol.
I don't want to put another answer or copy/past so you should take closer look at following question\answers. :)
iOS 7 Status Bar Collides With NavigationBar
iOS 7 UIToolBar Overriding With Status Bar
statusbar overlapping content in iOS7
I had to do this once. I ended up creating a custom navigationBar of my own and then just set the frame as:
navBar.frame=CGRectMake(0, 20, self.view.frame.size.width, self.view.frame.size.height);
It worked for me at the time. Just try it out.
Another workaround here: subclass UINavigationController override method:
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
if (self.navigationBar.frameMinY < 1) {
self.navigationBar.frameHeight = 64;
} else {
self.navigationBar.frameHeight = 44;
}
}
in which set frameMinY is set frame.origin.y and set frameHeight is set frame.size.height
I'm trying to hide my NavigationBar and the Status Bar (slide up animation) and I'm running into an issue.
When the status bar is visible, the origin point of every element that is at 0 point (x: 0) means right underneath the statusbar. However, when the statusbar is hidden, the 0 (x: 0) point updates to accomodate the new space, and 0 (x: 0) means the absolute top of the screen.
When I hide the status bar and rotate into landscape, the view autosizes and everything is shifted to use the status bar's space, and throws off my animation:
if (![[UIApplication sharedApplication] isStatusBarHidden]) {
// Change to fullscreen mode
// Hide status bar and navigation bar
[[UIApplication sharedApplication] setStatusBarHidden:YES
withAnimation:UIStatusBarAnimationSlide];
[UIView animateWithDuration:animationDuration animations:^{
navBar.frame = CGRectMake(navBar.frame.origin.x,
-navBar.frame.size.height-20,
navBar.frame.size.width,
navBar.frame.size.height);
} completion:^(BOOL finished) {
[navBar setHidden:TRUE];
}];
} else {
// Change to regular mode
// Show status bar and navigation bar
[navBar setHidden:FALSE];
[[UIApplication sharedApplication] setStatusBarHidden:NO
withAnimation:UIStatusBarAnimationSlide];
[UIView animateWithDuration:animationDuration animations:^{
navBar.frame = CGRectMake(navBar.frame.origin.x,
0,
navBar.frame.size.width,
navBar.frame.size.height);
} completion:^(BOOL finished) {
}];
}
Any suggestions?
EDIT: Here's what the screen looks like after rotation's relayout: Image
You're confusing the subdivision of the screen into UIView areas a little bit.
When you're in a navigation controller, there are three views:
Navigation controller's "root" view
Inside that, the NavigationBar (which of course is a UIView
Also inside the navigation controller, a "content area" view
So the Nav controller is managing it's own root view. In that it is filling the space with a NavigationBar at the top, and the rest of the area with one big UIView for content.
When you push your view controller onto the navigation stack, the Nav controller is adding your root view as the content. So your entire "self.view" is completely contained within the Nav controller's "content" view.
And so, of course, when the Nav controller hides the navigation bar... the "content" view expands up to fill the space. And then that view tells your view "hey, there's more space than you're using so your view also expands up to completely fill the Nav controller's content view.
So your view's "0" point is always the top of your view. That never changes. What is changing is where "the top of your view" is relative to the top edge of the screen.
If you want your content to remain in same spot on screen when navbar is removed, then you're going to have to account for the fact that your "zero" point is now higher than it was when there was a nav bar pushing the content view down.