Like Instagram - EXPLORE Tab, when I scroll the content, the status bar moves as well.
Always called FullScreenScroll, like here, when the user scrolls the tableView, the NavigationBar & TabBar are scrolled to show or hide at the same time.
My problem is, not only NavigationBar & TabBar, I also want to make the StatusBar follow the finger move.
Finally, it is really fullscreen.
This is the best solution you can find to get status bar window
UIWindow *statusBarWindow = (UIWindow *)[[UIApplication sharedApplication] valueForKey:#"statusBarWindow"];
Then change the frame
I found a solution to achieve moving status bar.
Thanks to this question and answer which I upvoted for, I can change the statusBar's frame while scrolling like below:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSArray *windows = [[UIApplication sharedApplication] FEX_windows];
for (UIWindow *window in windows) {
if ([window isKindOfClass:NSClassFromString(#"UIStatusBarWindow")]) {
CGRect frame = window.frame;
frame.origin.y -= 5;
window.frame = frame;
}
}
}
You can set the status bar hidden with an animation by calling:
[[UIApplication sharedApplication] setStatusBarHidden:BOOL withAnimation:UIStatusBarAnimation]
If you'd like to move the status bar pixel by pixel, you'll need to take a more creative approach. I believe that the way Instagram does this is by taking an image representation of the status bar (like a screenshot of the status bar), hiding the actual status bar, and then moving the image representation up and down as the user scrolls.
And swift:
if let statusBarWindow:UIWindow = UIApplication.shared.value(forKey: "statusBarWindow") as? UIWindow {
statusBarWindow.frame.origin.y = -5
}
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 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
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];
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];
I have a view controller that displays full screen, and from the view controller another modal VC can be presented, the modal VC requires status bar, but after dismissing the modal VC controller, the base VC has the space for status bar on top, and even I set:
[[UIApplication sharedApplication] setStatusBarHidden:YES]
the status bar is hidden but the space is still there, and I checked the frame of the view of the base VC, it starts from 0, I don't think I should make its y-coordinate starts from -20, but what else can I do?
Thanks
Try this out:
[[UIApplication sharedApplication] setStatusBarHidden:NO];
self.view.frame = [[UIScreen mainScreen] applicationFrame];
From the docs on applicationFrame:
This property contains the screen bounds minus the area occupied by
the status bar, if it is visible. Using this property is the
recommended way to retrieve your application’s initial window size.
The rectangle is specified in points.
For a more robust solution, change your frame in response to a status bar frame change. Your application delegate subclass can implement:
-application:willChangeStatusBarFrame:
-application:didChangeStatusBarFrame:
Or, you can register for these notifications using NSNotificationCenter:
UIApplicationWillChangeStatusBarFrameNotification
UIApplicationDidChangeStatusBarFrameNotification
I've tried your method, but it did not work for me, I solved it in this way, when the modal VC dismisses, in the viewDidAppear of base VC I need to:
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
self.wantsFullScreenLayout = YES;
[self.view layoutSubviews];
I know this is not a good solution, but it is the only thing I could think of at the moment, thanks anyway for your help.