Implementing A Super Simple Blurred Status Bar on iOS 8+


Ever since iOS 7 hit, I haven’t really noticed a lot of apps doing anything interesting with their status bars. I’ve been rewriting my pet project Flyte for iOS recently for a 2.0 release, which of course involves a lot of experimentation with UI and UX, so I thought it would be the perfect opportunity to play around with status bar experiments.

The introduction of iOS 7 brought sweeping changes to the API set, adding many new abilities that developers had long pined for. These changes included a brand new physics engine called UIKit Dynamics, which revives the old, dated animation engine in UIKit and now drives almost all the animations in iOS, and drastically simplified interface building tools, such as Autolayout. iOS 8 brought even more drastic changes, such as the ability to build extensions for the notification center and action sheets. The ability to build custom keyboards was also introduced, and only a couple of months later, incredible new keyboards such as Nintype began popping up.

While these changes are fantastic and scratches many itches, they do come with their regressions. For example, due to sandboxing restrictions, extensions may only use a certain amount of memory so as to not crash their containing apps if the extensions themselves crash. UI regressions are abound as well. Full screen and landscape UI and UX issues have to now be constantly considered, whereas prior to iOS 7 they were mostly handled automatically. Apple has tried to offset these issues by handling things like status bar changes and tinting automatically, but in doing so they’ve deprecated the old APIs and have made it nearly impossible to customize these aspects easily.

As a result, many developers targeting iOS 7 and above have opted not to customize areas of their apps that were previously easily customizable. What they don’t realize, however, is that it is actually still relatively easy to customize these areas. I’m going to use the status bar as an example, but first, a little background on what we’re going to do.

Before iOS 8, it was exceedingly difficult to achieve a nice live blur on your own. You could use a couple of methods, such as stealing a pre-existing blurred element’s blur layer, or use a third party library such as FXBlurView.

The problem with stealing another element’s layer is that you’re initializing an element that you don’t need for a small part of it, then keeping that element around in memory just so you can use one layer from it. You’re also writing code that you otherwise would not have needed to write if you were using a third party or first party blurring library. Since no first party effects framework existed, during iOS 7, the only alternative was a third party library. There are many good ones, but they could only make do with what they are given. Apple’s first party blurring effects have access to the device’s GPU, which is able to produce fast, accurate blurs of large areas without that much impact on the CPU. Third party libraries, however, only have access to the CPU, and are sandboxed at that. These restrictions produced many interesting blurring algorithms, but none could match the speed of Apple’s in house algorithms for large areas.

It is often said that when iOS 8 was introduced, a collective sigh a relief was heard around the world. Finally, a first party blurring framework that was easy to use and performant across the board. UIVibrancyEffect is a fantastic framework, and probably one of the more stable ones as it had already existed one release cycle before its public introduction, it was just not public then. Anyways, I realize that this is just turning into a long rant, and i do want to show some code before I end, so let’s get down to it: A super simple live blurring status bar that handles rotation and frame changes.

You’ll want to start with a UIView as the basis. So as usual, in Xcode, create a new subclass of UIView via File -> New -> File....

Name it whatever you want, but as always, keep in mind Cocoa convention. I'll just name mine BTBlurredStatusBar.

Now, in your new .m file, you'll find some things already set up. Get rid of the default generated and commented out drawing function, you won't be needing it right now. We want to make it simple to create and instantiate this control, so let's make a new initialization method.

- (id)initWithStyle:(UIBlurEffectStyle)style
{

Here we only really want one parameter, the style of the blur, since we want everything else to be automatic, right?

Now, let's instantiate the control itself.

self = [super initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIApplication sharedApplication].statusBarFrame.size.height)];  

We'll use the super method for instantiating the control. We want the view's frame to start at the top left corner of the screen, so (0, 0), and have the width of the screen and the height of the original navigation bar, hence we get the width of the UIScreen's bounds and the height of whatever UIApplication happens to return for the current height of the navigation bar.

Next, following Apple's initialization convention, we check to see if the thing's actually been initialized.

if (self)  
{

Then, we apply visual effects.

UIBlurEffect *blur = [UIBlurEffect effectWithStyle:style];  
UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:blur];  
[effectView setFrame:self.bounds];
[self addSubview:effectView];

What did we just do, you ask? We just initialized a UIBlurEffect with whatever style we passed it in the initializer. We then initialized the actual visual effects view with the blurring effect, set the frame to the bounds of our original view. Finally we added the effects view to our original view. Simple.

Now, what happens when your frame changes though? What if a call comes in or some other process changes the frame of the navigation bar? What happens if your user rotates their device and the your application rotates? Here's where we want to do some NSNotification magic.

So, we add observers to watch for the actual frame changing.

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(rotated:) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changedFrame:) name:UIApplicationWillChangeStatusBarFrameNotification object:nil];

Finally,

}
return self;  
}

Now, we add the rotation and frame changing methods.

- (void)rotated:(NSNotification *)notification
{
    int number = [[notification.userInfo objectForKey:UIApplicationStatusBarOrientationUserInfoKey] intValue];
    if (number == UIInterfaceOrientationPortrait || number == UIInterfaceOrientationPortraitUpsideDown)
    {
        [UIView animateWithDuration:0.5 animations:^{
            self.alpha = 1;
        }];
    } else
    {
        [UIView animateWithDuration:0.5 animations:^{
            self.alpha = 0;
        }];
    }
}

- (void)changedFrame:(NSNotification *)notification
{
    NSValue *value = [notification.userInfo objectForKey:UIApplicationStatusBarFrameUserInfoKey];
    CGRect newRect = [value CGRectValue];

    [UIView animateWithDuration:0.5 animations:^{
        [self setFrame:CGRectMake(self.frame.origin.x, self.frame.origin.y, newRect.size.width, newRect.size.height)];
    }];
}

These methods should be pretty self explanatory. The rotation method follows iOS 8's status bar conventions. Landscape should hide the status bar, while the status bar should show up again in portrait mode. Frame changing should also be pretty easy to understand.

Of course, now that you've created this wonderful initialization method, you'll want to expose it.

So in your corresponding header file, simply add your initialization method signature.

- (id)initWithStyle:(UIBlurEffectStyle)style;

That's it. Now when you want to initialize a blurred status bar, simply do

BTBlurredStatusBar *statusBar = [[BTBlurredStatusBar alloc] initWithStyle:UIBlurEffectStyleLight];  
[self.window addSubview:statusBar];

Of course, this code doesn't account for edge cases, like what happens when something is modally presented with a higher window index than your status bar window, but in that situation all you need to do is bring your subview forward in the window.

Anyways, that's it for this post. Have fun with your new blurred status bar!