36

The problem I'm facing is this:

I want to implement an iOS 7 app with nice design and left/right menu, which appears after the main view animate itself to the right/left. I'm doing this with [UIView animateWithDuration...] code, but that's not really important. What I want to achieve is the same effect the Mailbox iOS 7 app has: to move the status bar away (to the right/left) with the main view

Image for better explanation:

Mailbox app with status bar moved to the side

What I only found is this article about the issue, with some working code using Private APIs, which I'd like not to use, since I want my app to be accepted on the App Store.

I'd like to achieve the same effect ('legally'). Does anybody knows how to?

Thanks!

Gyfis
  • 1,184
  • 1
  • 14
  • 33
  • Mailbox does not appear to be using the standard Status Bar. They're sticking extra text up there, so my guess is they're using something like https://github.com/myell0w/MTStatusBarOverlay, but updated for iOS 7. – Ben Gottlieb Oct 08 '13 at 00:14
  • Create a container controller and add your menu and main view as childViewControllers. Then you can set the status bar hidden in the menu controller, and it will preserve the other view controller's status bar, without using screenshots (which are less than awesome in my opinion). – user Feb 13 '14 at 06:19
  • @user have you had an example project? If yes, can you share this one? – Dmitry Coolerov Feb 14 '15 at 11:33

5 Answers5

48

The gist of it is to use this method introduced in iOS 7:

https://developer.apple.com/documentation/uikit/uiscreen/1617814-snapshotview:

With that you get a UIView containing a screenshot that includes the status bar. Once you have that, it's just a matter of hiding your current view then pushing the screenshot view around.

I posted a proof of concept here: https://github.com/simonholroyd/StatusBarTest

NOTE I haven't submitted code that does this through the Apple review process, but this is not a private API method.

Cœur
  • 32,421
  • 21
  • 173
  • 232
Simon Holroyd
  • 904
  • 9
  • 12
  • This looks interesting, I'll look into it and report my findings! – Gyfis Sep 25 '13 at 17:19
  • 5
    Something else to note if you move further with this feature: the Mailbox app has a bug where when you multitask, the screenshot it displays does not suppress the status bar. So if you exit the app with left menu open, then multitask, you'll see duplicate status bars in the multitasking view. – Simon Holroyd Sep 25 '13 at 18:43
  • That's actually pretty good to know! They are not gods :) Thank you! – Gyfis Sep 25 '13 at 20:22
  • Hey, I'm just trying to write the code, but now I realized that this method just takes a _screenshot_ of the screen! I was asking rather for a functional status bar moved than for a screenshot, although it's a nice method – Gyfis Sep 26 '13 at 11:16
  • 1
    Although, as it seems, the Mailbox app does it in the same way, since the status bar does not change when moved away from the screen. The only thing that I'm thinking about now is 'Busted!' – Gyfis Sep 26 '13 at 11:21
  • 2
    You asked how to achieve the same effect! It is just that, an effect. – Simon Holroyd Sep 26 '13 at 13:32
  • Yes, of course you are right :) I've already accomplished the task (the effect), so I'll probably post something about it tonight here – Gyfis Sep 26 '13 at 20:10
  • +1 That is Super cool @SimonHolroyd! I love the way you're using the screenshot to then use that as the view whilst hiding the actual status bar ;) hehe quite nifty! Man I wish to see other cool projects like this, if only there was a place where you could quickly see all these kind of nifty tricks for iOS development! If anyone knows of something cool like this related to anything, please do tag me and let me know! cheers! – Pavan Jan 19 '14 at 21:29
  • This isn't how mailbox does it. Although I'm not sure how they do, you can see that their status bar notifications will finish animating even while in the 'slide-open' position. (Press the slide button immediately after you first open the app and you'll see this) – Cypher Feb 21 '14 at 18:21
7

So, after the initial push by Mr. Simon Holroyd and some searching, I've found the solution of how to achieve this "effect" functionality. This is the code:

statusbarView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 20)];

EDIT: mister pcholberg correctly pointed out that the former code did not work on the actual device, only on the iOS Simulator, so I've edited it by his recommendation

if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0"))
{
    UIView *screenShot = [[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO];
    [statusbarView addSubview:screenShot];
    [statusbarView setClipsToBounds:YES];
    [self.view addSubview:statusbarView];

    [self setPrefersStatusBarHidden:YES];
    [self prefersStatusBarHidden];
    [self performSelector:@selector(setNeedsStatusBarAppearanceUpdate)];
}

...

- (BOOL)prefersStatusBarHidden
{
    return prefersStatusBarHidden;
}

...


So the first part creates context, uses the method Simon mentioned, draws the view with the statusbar, and saves that as an UIImage

The second part adds the snapshot UIView to my viewController's UIView

And the third part sets my bool for statusbar to YES (for easier use in the method above), and calls methods to redraw it

This then sets the UIView as not-functional statusbar at its place and hides the original one, so there is no double-rendering. Then I can use this view in my [UIView animateWithDuration... method

And when I return, I use this code in the completion handler of the animation block:

[statusbarView removeFromSuperview];

if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0"))
{

    [self setPrefersStatusBarHidden:NO];
    [self prefersStatusBarHidden];
    [self performSelector:@selector(setNeedsStatusBarAppearanceUpdate)];

}

And voilá! This works as the described effect in my question.

Hope this helps somebody!

adamdehaven
  • 5,772
  • 9
  • 56
  • 84
Gyfis
  • 1,184
  • 1
  • 14
  • 33
  • 1
    Do not render snapshotView ([[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO]) into image, snapshot will appear without text on statusbar on real device. Just add this shanpshot as subview. – pcholberg Oct 04 '13 at 14:00
  • You are absolutely right! This somehow works on simulator, however does not on an actual device. But I was not able to add the snapshot as subview and move it afterwards, since it seemed it changes after some time... – Gyfis Oct 04 '13 at 21:28
  • 1
    What is the purpose of `[self prefersStatusBarHidden];` in your code, since you're doing nothing with the return value? And why do you have to do `[self performSelector:@selector(setNeedsStatusBarAppearanceUpdate)];` instead of just `[self setNeedsStatusBarAppearanceUpdate]`? If your code does not work without those lines, I would be reluctant to use it in an app as it would be likely to suddenly stop working with a new iOS release. – MusiGenesis Dec 06 '13 at 04:28
  • I've override the method prefersStatusBarHidden, which now returns a boolean - prefersStatusBarHidden (now that I see it, it's rather strange that I named them the same way!). About the selector - I'm not really sure, it's possibly some reference to a lot of tweaking and changing little bits so the whole effect would work. – Gyfis Dec 06 '13 at 07:44
7

I use this method to move statuebar with slider view,in a application there are two window,one normal window,other statuBarWindow,i get statuBarView which superView is statuBarWindows ,and move it with slider view.

- (UIView *)statuBarView
{
    NSString *key = [[NSString alloc] initWithData:[NSData dataWithBytes:(unsigned char []){0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x61, 0x72} length:9] encoding:NSASCIIStringEncoding];
    id object = [UIApplication sharedApplication];
    UIView *statusBar = nil;
    if ([object respondsToSelector:NSSelectorFromString(key)]) {
        statusBar = [object valueForKey:key];
    }
    return statusBar;
}
Dominik Hadl
  • 3,529
  • 3
  • 21
  • 56
signal
  • 226
  • 3
  • 10
  • This is pretty interesting.. is this considered a use of apples private API? Also, why do you use dataWithBytes instead of just @"statusBar" which is what that turns into.. is that to avoid detection in some way? (seems like they would check for this simple work around..) – Cypher Feb 21 '14 at 18:39
3

I just created BSPanViewController which makes it extremely easy to achieve this effect. The control and instructions on how to use it can be found on GitHub.

The implementation is the same as the one explained by Simon Holroyd.

Community
  • 1
  • 1
simonbs
  • 7,543
  • 12
  • 64
  • 112
-3

In my experience, App Store reviewers generally don't care about private API's use, especially this simple and harmless. For the task you can get a pointer to application's status bar view through several methods, which you can find in iOS complete headers like https://github.com/nst/iOS-Runtime-Headers

note173
  • 1,585
  • 2
  • 12
  • 15
  • 7
    If you got away with using a private API, it's an anomaly. There is great risk in breaking the rules, and I wouldn't advise people to try. – Eric Goldberg Oct 16 '13 at 19:12