On the latest snapchat update, when you "swipe right for messages" the status bar turns from black to white in a sort of gradient fashion.
http://imgur.com/osJI20u
How are they doing this without using private API's, or are they using private API's?
What I've looked into:
At first glance, I thought they are just doing what everyone else does, (like rdio currently does) by screen capturing the whole screen, and then modifying/animating that screenshot.
But... you cant access or animate the UIImage from that screen captures method, so although you could move/crop that screenshot, you cannot change its look.
So... I tested it, and when the status bar is in that state (half black/half white), it is still LIVE as in its not a screenshot, and it responds to charging changes/signal changes etc, as far as I can tell this is impossible without using a private API.
This got me curious so I played with the app a bit and inspected the view hierarchy of the app using Spark Inspector on a jailbroken device and I found a few things.
You are incorrect about them not using a screenshot. The status bar on the side you are navigating AWAY from is a snapshot and does not change. You can easily see this when a minute goes by, the new status bar for the view you are transitioning into will have changed because it is real but the old one will not because it is a snapshot. That makes the illusion easy to work out.
Here is what is happening at a high level:
When you begin the transition, the app takes a snapshot of the status bar(most likely it is actually taking a snapshot of the entire UIScreen
and simply cropping the status bar out of it) in its current state and attaches a window containing that snapshot to the top of the view in the transition, anchored to the final transition position and masked by the "from" view controller's view. This window appears to have a windowLevel
above UIWindowLevelStatusBar
so it is able to cover the real status bar.
Then, the "to" view controller takes over the actual status bar and changes it to the "light content" state. This presents the clever illusion that a single status bar changes color with the transition border, but the real status bar is actually always sitting below the snapshot window and would indeed be visible immediately when the transitions begins if it weren't for the snapshot window sitting on top of it.
Building off of what Dima answered I thought some basic implementation to get people started could help. As Dima said it's not 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];
So basically the status bar is cropped from the picture and added to a UIWindow
with a windowLevel
above UIWindowStatusLevelBar
so you can see it over the real status bar. Something like:
UIWindow *statusBarWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
statusBarWindow.windowLevel = UIWindowLevelStatusBar + 1; // higher
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]; // since visible, make it clear until we actually add the screen shot to it so as to not block anything
...
// The view that will hold the screen shot
// Later, I think we're going to need to play with contentInsets to make the view look like its staying still, so we're making it a UIScrollView in case
UIScrollView *statusBarView = [[UIScrollView alloc] initWithFrame:[UIApplication sharedApplication].statusBarFrame];
statusBarView.clipsToBounds = YES;
// Now we add the screen shot we took to this status bar view
[scrollingStatusBarView addSubview:statusBarView];
[statusBarView addSubview:screen]; // screen is the UIScrollView from previous code block (the screen shot)
// 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.
When swiping starts, configure your new status bar (the one underneath) with whatever style, background, whatever:
[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent animated:NO];
// anything else you might feel like doing
Now we have something interesting going on here. As the transition moves along, you will have to use the amount its scrolled to "snip" that offset amount off of the statusBarView
. The statusBarView.frame
will have to begin at x=offset or else it'll show over the actual status bar (since it's window is higher), but if you don't change the contentInsets then the picture will slide with the transition. So to create the illusion you will need to push the origin over to the right while increasing the left content inset by the same amount so it appears to be in the same location.
So inside whatever method is handling the sliding action:
Note: This will strongly depend on personal implementation and will probably take some experimentation
// if you're using an animation to handle the transition you can match up this change in x position with the animation
// if you're doing via pan gesture you could try something like:
CGFloat offset = self.viewThatIsScrolling.contentOffset.x; // may be negative depending on direction that is being swiped
if (midTransition) { // you can use the offset value here to check
// NOTE: again, will depend on which direction this animation is happening in. I'll assume a new view coming in from left (so swiping right)
statusBarView.frame = CGRectMake(offset, 0, self.statusBarView.frame.width, 20.0f); // move the origin over to the right
statusBarView.contentInsets = UIEdgeInsetsMake(0, offset, 0, 0); // make the picture start 'offset' pixels to the left so it'll look like it hasn't moved
}
(Side note: you can also try using a transformation instead of explicitly setting the frame: statusBarView.transform = CGAffineTransformMakeTranslation(offset -previousOffsetAmount, 0);
. I use the previousOffsetAmount there because 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 from the window completely:
// if continuing from before:
if (midTransition) {
...
} else { // the view has been slid completely
[statusBarView removeFromSuperview];
statusBarView = nil;
// I'm keeping the UIWindow where it is for now so it doesn't have to be recreated and because it has a clear background anyways
}
// if this is being done via animation the above can be called the animation finishes
I might actually be interested in trying to get this to work, so will update if so.