Simple iOS Loading Modal

Hello World!

First off I would like to thank Matt for building this site for us and allowing us to contribute to the technical community. My name is Chason and to start off my blogging career I would like to showcase a simple loading screen for iOS devices.

Grab the iOS project here!

This snippet of code is intended to be used in your app delegate and accessed by all view controllers that need it. The advantage to this structure is to cut down on redundant code and excessive IBOutlets in .xibs for every view controller. The same solution may also be found by setting up IBOutlets in the app delegate and adding the UI elements to the MainWindow.xib, but for the sake of learning and extensibility I will show you the programmatic way. I have also implemented rotation for the loadingView, but I will not include it in this post. It is however in the iOS project I have included and I recommend you check it out if interested.

Step 1:

For step one you need to create instance variables in your appDelegate.h file. The loadingView will be the rounded box in the middle of the screen and the dimView will be a mostly transparent black view behind our loadingView. The instance variables allow for easy manipulation of the UI elements after creation and also retains the elements so we dont lose them after they are removed from the window. We also want to create the method declarations for the loadingView so other viewControllers may call them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@interface LoadingTestAppDelegate : NSObject <UIApplicationDelegate>
{
  UIView* loadingView;
  UIView* dimView;
  UILabel* label;
}

@property ( nonatomic, retain ) UIView* loadingView;
@property ( nonatomic, retain ) UIView* dimView;
@property ( nonatomic, retain ) UILabel* label;

- (void) createLoading;
- (void) showLoading: (NSString*) title;
- (void) hideLoading;

//Refer to iOS project for rotation.
//- (void) rotateLoading;
//- (void) animateRotation: (CGAffineTransform) transform;

To actually use the getter and setter methods for the variables we need to synthesize the variables in the appDelegate.m file.

1
2
3
4
5
@implementation LoadingTestAppDelegate

@synthesize window = _window;
@synthesize viewController = _viewController;
@synthesize loadingView, dimView, label; // <-- Here

Step 2:

Now that we have a way to grab a handle to our UI elements we need to setup methods that make showing and hiding our loading screen relatively easy.

Here we will define the methods declared earlier in the appDelegate.h file. There are three methods. “createLoading” which will give our UI elements values. “showLoading” which will calculate where on the screen the loading view should go. Finally, “hideLoading” which will remove the loading view from the window.

As you view the createLoading method you may notice something strange, and your probably wondering why is there another uiview in our loading view? The reason behind the extra view is so we may add elements to the loading view without the elements themselves becoming transparent. If you notice, we dont add the indicator and label to the transparent view because we want them to remain opaque.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
- (void) createLoading
{
  //create the loading view
  //The loadingView is retained by the appDelegate
  CGSize loadingSize = CGSizeMake( 150, 150 );
  loadingView = [[UIView alloc] initWithFrame:CGRectMake(0,0,0,0)];
  loadingView.backgroundColor = [UIColor clearColor];
 
  //Round corners the corners of the loadingView
  //NOTE: you must #import <QuartzCore/QuartzCore.h>
  //for this to work
  [[loadingView layer] setCornerRadius:10];

  //Any overflowing elements will be clipped.
  //No elements should overflow anyway
  [loadingView setClipsToBounds:YES];
 
  //Create colored border using CALayer property
  [[loadingView layer] setBorderColor:[[UIColor whiteColor] CGColor]];
  [[loadingView layer] setBorderWidth:2.75];
 
  //create the semi-transparent view
  CGRect transparentViewFrame = CGRectMake( 0, 0, loadingSize.width, loadingSize.height );
  UIView* transparentView = [[UIView alloc] initWithFrame:transparentViewFrame ];
  transparentView.backgroundColor = [UIColor blackColor];
  transparentView.alpha = 0.75;
 
  [loadingView addSubview:transparentView];
  [transparentView release];
 
  //create the activity indicator
  UIActivityIndicatorView* loadingIcon = [[UIActivityIndicatorView alloc]
                                          initWithActivityIndicatorStyle:
                                          UIActivityIndicatorViewStyleWhiteLarge];
  [loadingIcon startAnimating];

  //Set the frame for the indicator
  CGRect liFrame = loadingIcon.frame;
  CGRect loadingIconFrame = CGRectMake( loadingSize.width/2 - liFrame.size.width/2,
                                       loadingSize.height/2 - 20 - liFrame.size.height/2,
                                       liFrame.size.width, liFrame.size.height );
  loadingIcon.frame = loadingIconFrame;
  [loadingView addSubview:loadingIcon];
  [loadingIcon release];
 
  //Create the text to place in the loading view.
  //Label will also be retained by the appDelegate
  //so we may change the title
  CGRect labelFrame = CGRectMake( 10, loadingSize.height - 80,
                                 loadingSize.width - 20, 55 );
  label = [[UILabel alloc] initWithFrame:labelFrame];
  label.textColor = [UIColor whiteColor];
  label.textAlignment = UITextAlignmentCenter;
  label.backgroundColor = [UIColor clearColor];
  label.font = [UIFont boldSystemFontOfSize:50];
  label.adjustsFontSizeToFitWidth = YES;  
  [loadingView addSubview:label];
 
  //create the view to dim the screen
  //Also retained by the appDelegate
  dimView = [[UIView alloc] initWithFrame:CGRectMake(0,0,0,0) ];
  dimView.backgroundColor = [UIColor blackColor];
  dimView.alpha = 0.25;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void) showLoading: (NSString*) title
{
  //Grab application frames
  CGRect appFrame = [[UIScreen mainScreen] bounds];

  //Calculate the loading view frame
  CGSize loadingSize = CGSizeMake( 150, 150 );
  CGRect loadingFrame = CGRectMake( appFrame.size.width/2 - loadingSize.width/2,
                                   appFrame.size.height/2 - loadingSize.height/2,
                                   loadingSize.width, loadingSize.height );
  loadingView.frame = loadingFrame;
  label.text = title;
 
  CGRect dimViewFrame = CGRectMake( 0, 0, appFrame.size.width, appFrame.size.height );
  dimView.frame = dimViewFrame;
 
  //Refer to the included iOS project for this method
  //[self rotateLoading];
 
  //Add the loadingView and dimView to the window
  [[[UIApplication sharedApplication] keyWindow] addSubview:dimView];
  [[[UIApplication sharedApplication] keyWindow] addSubview:loadingView];
}
1
2
3
4
5
6
7
8
9
10
- (void) hideLoading
{
  [loadingView removeFromSuperview];
  [dimView removeFromSuperview];
 
  //For rotation refer to iOS project for complete details
  //CGAffineTransform rotationTransform = CGAffineTransformIdentity;
  //rotationTransform = CGAffineTransformRotate(rotationTransform, degreesToRadians(0));
  //loadingView.transform = rotationTransform;
}

Now we need to call the createLoading method when the application fires up. I simply add the method after the appDelegate is ready.

1
2
3
4
5
6
7
8
9
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  // Override point for customization after application launch.
   
  self.window.rootViewController = self.viewController;
  [self.window makeKeyAndVisible];
  [self createLoading]; // <-- Here
  return YES;
}

Step: 3

Alright, now that we have the loading methods setup and the loadingView created, we may call the methods from any view controller whenever we want. Keep in mind even on view changes the loadingView will remain visible so be sure to hide when necessary.

So in any viewController you wish to show the loadingView just make calls like this:

1
2
3
4
5
6
7
8
9
10
11
//Whatever function you want here
- (void) viewDidLoad
{
  //Grab a handle to the appDelegate
  LoadingTestAppDelegate* appDelegate = [[UIApplication sharedApplication] delegate];
  //Maybe do some stuff here
  [appDelegate showLoading: @"Loading Content..." ];
  //Churning...
  //Little more Churning...
  [appDelegate hideLoading];
}

Summary

Thats it for the simple iOS loading indicator. With the code supplied you should be able to make a decent loading indicator that looks great in portrait orientation. If you want to support multiple orientations refer to the supplied iOS project for full details.

Thanks everyone!

How to Emulate Gmail’s Floating Menu Bar with jQuery

A few weeks ago, Gmail received a polished new look. One of the new features you might have noticed is that the menu bar at the top starts floating over the rest of the page once you scroll past it. This is a neat little effect, and, luckily, it’s really easy to duplicate. The trick lies with jQuery’s scroll event. We just need to check how far the page is scrolled whenever this event is fired and update the DOM if the page is scrolled past our menu bar. Let’s check out some code…

The HTML
1
2
3
4
5
6
7
8
9
10
11
<div id="menu-container">
    <div id="menu">
        <div class="button">Compose</div>
        <div class="button">Reply</div>
        <div class="button">Reply All</div>
        <div class="button">Forward</div>
        <div class="button">Delete</div>
        <div class="button">Print</div>
        <div class="button">Refresh</div>
    </div>
</div>

Those menu options seem arbitrary enough. The #menu-container is important, even though it seems unnecessary. Some CSS will make it more clear.

The CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#menu-container {
    height: 46px;
    width: 800px;
}

#menu {
    overflow: hidden;
    background: #fff;
    width: 800px;
    height: 30px;
    padding: 8px 0;
    z-index: 1;
}

#menu.floating {
    position: fixed;
    top: 0;
    box-shadow: 0 4px 2px -1px rgba(0, 0, 0, 0.25);
    -moz-box-shadow: 0 4px 2px -1px rgba(0, 0, 0, 0.25);
    -webkit-box-shadow: 0 4px 2px -1px rgba(0, 0, 0, 0.25);
}

.button {
    float: left;
    padding: 4px 16px;
    margin-right: 8px;
    border: 1px solid #999;
    background: #ddd;
    cursor: default;
}

You can probably already figure out what our JavaScript will look like. When the page is scrolled past the #menu-container, we change the #menu to position: fixed and top: 0. Now you can see why the #menu-container is important. When the #menu starts floating, the #menu-container stays put to keep track of the initial menu location. When the page is scrolled back above it, the #menu stops floating. The fixed-height of the #menu-container is necessary to keep everything below the menu at the same position when the #menu is removed from the normal flow of the page. If the #menu has a variable height, then you can simply use jQuery to set the height of the #menu-container before the #menu starts floating. Now, to the JavaScript…

The JavaScript
1
2
3
4
5
6
7
8
9
10
$(document).ready( function() {

    $(window).scroll( function() {
        if ($(window).scrollTop() > $('#menu-container').offset().top)
            $('#menu').addClass('floating');
        else
            $('#menu').removeClass('floating');
    } );

} );

Pretty simple: whenever the window is scrolled, if it was scrolled past the #menu-container, then add .floating to the #menu. Otherwise, remove the .floating class. See it in action.

So what else could this feature be useful for? How about social networking buttons to the side of an article or blog post that follow you down as you scroll the page? See an example.

Pretty neat, huh? I’ll leave it to you to come up with the real blockbuster ideas.

A Simple jQuery Modal Box

If you want a simple modal box without having to resort to jQuery UI’s dialog widget, then try this simple jQuery modal. Before we start, brush up on your jQuery and make sure you have the jQuery source file linked into your page (you are letting Google host jQuery for you, right?). If you don’t know much about jQuery, visit the jQuery documentation for some great information.

The HTML
1
2
3
4
<div id="modal-mask"></div>
<div id="modal-box">
    Modal content goes here...
</div>

Pretty simple so far. Those two div elements can go anywhere directly inside the body tag. Right before </body> seems to be a good place for them.

The CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#modal-mask {
    display: none;
    position: fixed;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    z-index: 1000;
    background: rgba(0, 0, 0, 0.75);
}

#modal-box {
    display: none;
    position: fixed;
    z-index: 1001;
    background: #fff;
}

The #modal-mask div will be used to darken the rest of the page to make the #modal-box stand out. The position: fixed, width: 100%, and height: 100% properties set the modal mask as large as the window. The #modal-box div also has position: fixed to attach it to the center of the window. We’ll let jQuery handle its position later. Make sure your z-index values are high enough so that the #modal-mask and #modal-box stack on top of the rest of your page.

The JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$(document).ready( function() {

    $('.show-modal').click( function() {
        $('#modal-box').css( {
            left: $(window).width() / 2 - $('#modal-box').width() / 2,
            top: $(window).height() / 2 - $('#modal-box').height() / 2
        } );

        $('#modal-box').show();
        $('#modal-mask').show();
    } );

    $('#modal-mask').click( function() {
        $(this).hide();
        $('#modal-box').hide();
    } );

} );

You can replace the .show-modal selector with any of your choice. When this click event is triggered, the #modal-box is re-positioned based on the dimensions of the browser window. Then, both the #modal-box and #modal-mask are shown. The #modal-mask dims the rest of the page, and the #modal-box sits on top.

So, there you have it! A super-simple jQuery modal box. …What’s that? You say you want more? Okay, well, take a look at what we have so far, and I’ll see what I can do.

Improvements

If you’ve already gone off and implemented this modal on your website, you might have noticed that you can still scroll the page underneath the modal using the scroll bar (see for yourself). That seems kind of goofy because a modal window really isn’t modal if it lets the user interact with other parts of the application. We can easily get rid of the scroll bar by using overflow: hidden on the html and body tags when the modal is shown (both are required because some browsers attach the scroll bar to the body and some to the document). However, with one problem solved, another surfaces: when the scroll bar goes away, the width of the body element grows. On fixed-width websites, the entire page will seem to shift to the right a dozen or so pixels. On fluid-width sites, the change in width could alter how text is laid out when the modal pops up. See it happen.

So, when the modal is shown, how can we get rid of the scroll bar and keep the width of the page constant? The answer is margin, my friends. When the scroll bar is hidden, we add margin to the right of the body equal to the width of the scroll bar, which keeps the width of the body constant. But how do we know the exact width of the scroll bar? That’s easy: we use jQuery to get the width of the body before and after the scroll bar is hidden and find the difference. Just add right margin equal to that difference. We’ll also keep track of the initial right margin on the body just in case there’s any before the modal is shown (some browsers put margin on the body in the default user agent styles). Let’s look at some code…

Some New JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var bodyMarginRight;

$(document).ready( function() {

    $('.show-modal').click( function() {
        bodyMarginRight = parseInt($('body').css('margin-right'));
        var oldBodyWidth = $('body').width();

        $('body, html').css('overflow', 'hidden');

        var scrollBarWidth = $('body').width() - oldBodyWidth;
        $('body').css('margin-right', (scrollBarWidth + bodyMarginRight) + 'px');

        $('#modal-box').css( {
            left: $(window).width() / 2 - $('#modal-box').width() / 2,
            top: $(window).height() / 2 - $('#modal-box').height() / 2
        } );

        $('#modal-box').show();
        $('#modal-mask').show();
    } );

    $('#modal-mask').click( function() {
        $(this).hide();
        $('#modal-box').hide();
        $('body, html').css('overflow', 'auto');
        $('body').css('margin-right', bodyMarginRight + 'px');
    } );

} );

It’s that easy: the scroll bar is gone when the modal is shown, but the width of the body stays the same. Check out the example.

So, there you have it. A simple jQuery modal box with… wait, you’re saying you want more? You say it’s not fancy enough? Well, okay, I guess I could add some features…

Let’s Get Fancy

Let’s start with adding some padding to the #modal-box and maybe some rounded borders. Oh! and let’s add a radial gradient background to the #modal-mask, and a box-shadow to the #modal-box, of course. And how about fading everything in and out with jQuery. And just for fun, let’s use Ajax to load a page on an external server into the modal.

Some New CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#modal-mask {
    display: none;
    position: fixed;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    z-index: 1000;
    background: radial-gradient(rgba(127, 127, 127, 0.5), rgba(0, 0, 0, 0.5) 50%, rgba(0, 0, 0, 0.9));
    background: -moz-radial-gradient(rgba(127, 127, 127, 0.5), rgba(0, 0, 0, 0.5) 50%, rgba(0, 0, 0, 0.9));
    background: -webkit-radial-gradient(rgba(127, 127, 127, 0.5), rgba(0, 0, 0, 0.5) 50%, rgba(0, 0, 0, 0.9));
}

#modal-box {
    display: none;
    position: fixed;
    z-index: 1001;
    background: #fff;
    width: 600px;
    height: 400px;
    overflow: auto;
    padding: 8px;
    border-radius: 8px;
    -moz-border-radius: 8px;
    -webkit-border-radius: 8px;
    box-shadow: 0 0 8px 16px #333;
    -moz-box-shadow: 0 0 64px 8px #333;
    -webkit-box-shadow: 0 0 64px 8px #333;
}
Some Newer JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var bodyMarginRight;

$(document).ready( function() {

    $('a.show-modal').click( function() {
        bodyMarginRight = parseInt($('body').css('margin-right'));
        var oldBodyWidth = $('body').width();

        $('body, html').css('overflow', 'hidden');

        var scrollBarWidth = $('body').width() - oldBodyWidth;
        $('body').css('margin-right', (scrollBarWidth + bodyMarginRight) + 'px');

        $('#modal-box').css( {
            left: $(window).width() / 2 - $('#modal-box').width() / 2,
            top: $(window).height() / 2 - $('#modal-box').height() / 2
        } );

        var url = $(this).attr('href');
        $('#modal-box').load('getpage.php?url=' + url).fadeIn();
        $('#modal-mask').fadeIn();

        return false;
    } );

    $('#modal-mask').click( function() {
        $(this).fadeOut();
        $('#modal-box').fadeOut( function() {
            $('body, html').css('overflow', 'auto');
            $('body').css('margin-right', bodyMarginRight + 'px');
        } );
    } );

} );

Since JavaScript can’t send requests to an external server, I have a simple PHP script that bounces the request. To see all of the effects, make sure you’re using a good browser (Chrome, Firefox, Safari). Well, that’s all I got for now. See the final product in action.