iOS Notification-style Drop-down Alert

Think back a few years ago to how social apps handled asynchronous actions, such as saves, deletions, or other network actions. That's right. They froze up the UI. They would throw up a spinner or a modal screen that would block the user from interacting with the app until the action was completed...or until there was an error.

Yeah, they're not doing that so much these days. Instead, apps are making efforts to allow users to continue using the app after initiating even relatively long back-end processes. They do this by first assuming that most network actions will be successful. In other words, you only need to alert your users in the relatively rare case of a network action failure.

Secondly, alerts are not tied to the view where the original back-end process was initiated (for example, a user might click "upload" under an image in a modal view, then be shown the error alert while in another part of the app).

So what has replaced the UIAlerts and UIActivityIndicator objects of yesteryear? Drop-down notifications. You see them everywhere now in a wide range of social chat, dating, and messaging apps. They drop down from the top of the view in a manner similar to a UILocalNotification banner alert. But as custom views, they are completely customizable, and they can be shown even when the app is running in the foreground.

This post will show you how to make a drop-down alert that has an optional image, both title and subtitle text, and customizable background and text colors:

And we even have a video capture! Note that the banner can be dismissed by any form of user gesture interaction. Otherwise, it self-dismisses a few seconds after being called.

Getting Started:

Head over to github and download the MPC_NotificationDemo (Objective-C). Run the app and it should appear as above. Have a look into the code and you'll see what is going on under the hood. Basically, there are two steps to calling the alert:

First, when you tap a button, the IBAction method in the ViewController instantiates the MPC_Notification class (a subclass of UIView) using the custom init method. At this time, the notification is passed the title text, the alert image, and other information. Once created, the notification view adds itself to the main UIApplication window just above the top of the screen.

Second, display is called on the instance, which triggers the alert to slide down, slide back up, and remove itself from the view hierarchy.

Let's have a look at the code in three parts:

1. The custom initializer

2. Creating the views added to the alert

3. Presenting and dismissing the alert

Custom Initializer:

A custom initializer allows you to set up a view (or any custom class) at the same time as it creates itself. The custom init method below is public, so be sure to add the method stub to your .h file or have it in your public methods if you are coding in Swift.

Before creating the instance, we begin by initializing some global properties. The ? : syntax above may not be familiar to some. ? : is a way to say, "If this is true, do this, else, do that." Put in more familiar syntax:

self.textXOffset = self.alertImage ? 64 : 15;

does the same thing as:

if (self.alertImage != nil) {

self.textXOffset = 64;

} else {

self.textXOffset = 15;

}

Having initialized a few properties, we go ahead and call to the super class and ask it to create our UIView instance.

if (self = [super initWithFrame:CGRectMake(0, -self.alertHeight, self.viewWidth, self.alertHeight)])

Note that the frame width is based on the UIWindow. Note also that that the y origin is a negative number. This is because we want the alert to be located just above the view and out of sight when we add it to the window.

Now that we have "self" -- our custom view -- we can set its background colors and add views and gesture recognizers to it. Before returning this instance to the class from which you called it, we'll add it directly to the main UIApplication window.

[[[[UIApplication sharedApplication] delegate] window] addSubview:self];

Creating the views & gestures:

The observant programmer will ask, "Why didn't you use storyboards or a .xib file to set up the view?" Good question! First, I wanted to make something that you could drop into a project, which rules out storyboards.

As for nibs, well, to be honest, making a UIView subclass work with a .xib involves a bit of voodoo, including subverting the self instance from initWithFrame with another instance based on the nibWithNibName, not to mention the need to register nibs, and bla, bla, bla. Sometimes just doing a bit of grade 5 math to put things where you need them is simpler. (But for the record, I use storyboards or .xibs for 95% of the views in my app).

This is all very straightforward stuff. The only point worth mentioning is that I do a check for screen sizes smaller than 320px (ie, anything from a 5S or smaller). Smaller screen sizes get a slightly smaller font size.

I also added both a tap and a pan gesture recognizer to the view to allow users to dismiss the view manually.

Showing and dismissing the view:

Display notification:

Have a look at the code snippet below. The notification uses simple UIView animation to show and dismiss itself. Within the animation block, you simply set the y origin to a new destination, and the animator does the rest.

The calling class initiates the alert display using the public display method. The view animates its own frame down onto the main window the exact height of the alert. It then calls to start the dismiss timer.

The _statusBarToBottom method is explained below. (It's worth reading about, so keep reading!)

Dismiss notification:

Dismissing the view is simply the reverse of displaying it. (Duh!) Have a look at the code below.

To time the view, we use Grand Central Dispatch. GCD allows us do both a timed method call and make sure we are on the main thread (as you should always be for any UI manipulation). In fact, this class does not do any asynchronous calls, but just in case this alert somehow gets called from a background thread, this makes sure the UI will respond immediately.

So why two dismiss methods? One is actually just bad naming. The timedDismiss isn't actually dismissing anything. It simply calls the same dismiss method that the gesture recognizers call.

_dismissView:(id)gesture above begins with a simple BOOL flag (declared in the @interface section). This is to ensure dismiss is called only once.

In fact, it actually makes little difference how many times dismissView: is called, but why waste CPU resources when we can ensure a single call in the easiest possible way. (Note that the least-easiest possible way would be to check if the gesture is a pan, then see if it is a start of a pan, and ignore the multiple pan gesture calls that follow it. Yeah, I thought you'd agree that a flag is less of a headache).

After the view is out of sight, we then ask it to remove itself from the superview. This should be enough to have the system deallocate the view. But just to be sure, we nil out the two strong properties associated with the view.

Finally, we call to reset the status bar to show at the top of the hierarchy. The timing is set so it appears just before the very end of the animation. This way it appears almost as a fade in, rather than JUST! SUDDENLY! APPEARING!, which is an assault on the user's eyeballs.

Status bar voodoo:

Since the deprecation of the once useful setStatusBarHidden:withAnimation:, getting the status bar to appear and disappear directly is more of a challenge. Here, instead of manipulating the UIStatusBar directly, we instead change the z axis (the level in a stack) of the main window.

To do this, we make use of the UIWindow class typealias "UIWindowLevel." Each widowLevel is set to a number (CGFloat). The higher the number, the higher up it is, and what is below it becomes hidden.

So, to "hide" the status bar, we take the current status bar level (the CGFloat representing that level, that is) and add 1 to it. We then set this higher number to the UIWindowLevel. This essentially moves the window one level "higher" than the status bar, thus hiding it.

To make the status bar visible, we just set the main window back to its normal state. How awesomely easy is that!?!

Usage notes:

When possible, try to call this class from a single location. This will reduce the chance of windows stacking up (which is useless), and it makes it easier should you need to change the custom initializer in the future.

At the moment, I'm only using this for error handling, but as you can easily change the image and colors, it could just as easily be used for success notifications or for user-related actions ("You have a message from Joe" -- with Joe's profile image). Whatever you need it for, I'm sure with a bit of simple tweaking you can customize it to your needs. Hope it helps!

If you use this class, please let me know if you like it, hate it, or find any issues or suggested improvements.

Search By Tags