MusiGenesis
MusiGenesis

Reputation: 75296

How to show app-specific status bar at bottom of screen, outside of app?

I have a requirement to show a status bar at certain times at the bottom of my application. I can easily put this at the bottom of my application's main view, but whenever I push a view controller on top of this (either modally or not) it hides this status bar.

Is there any way I can add a status bar like this, and have it be outside the bounds of my application itself? Ideally I'd like this to work like the call-in-progress status bar on the iPhone - when this bar appears, the app is pushed down, and a call to [[UIScreen mainScreen] applicationFrame] returns the correct size (i.e. it accounts for the presence of this status bar when calculating the height available for the app).

Upvotes: 1

Views: 519

Answers (1)

Rob
Rob

Reputation: 437462

I wanted to do this, too, so I tried View Controller Containment. I'm still trying it out, so I'm not willing to give this a ringing endorsement, but it might be something you'd want to try playing around with yourself if you're in iOS5. But it appears to give you a status bar that will appear or disappear from the bottom of the screen.

This is a view controller that will open another view controller, but if there is status text to show, it pops up from the bottom of the screen and stays there until you get rid of it. I've only done a little testing so far, but it looks like this handles pushViewController/popViewController, but maybe not modal views.

My header looks like:

//  StatusBarViewController.h
//
//  Created by Robert Ryan on 7/8/12.

#import <UIKit/UIKit.h>

@interface StatusBarViewController : UIViewController

@property (strong, nonatomic) UIViewController *appController;

- (void)setStatus:(NSString *)text;

@end

My implementation file (this is ARC) looks like:

//  StatusBarViewController.m
//
//  Created by Robert Ryan on 7/8/12.

#import "StatusBarViewController.h"

@interface StatusBarViewController ()
{
    BOOL _statusHidden;
    UIView *_appView;
    UILabel *_statusLabel;
}
@end

@implementation StatusBarViewController

@synthesize appController = _appController;

- (void)dealloc
{
    _appView = nil;
    _statusLabel = nil;

    [self setAppController:nil]; // usually I don't like setters in dealloc, but this does some special stuff
}

- (void)createControlsWithStatusHidden
{
    // create default app view that takes up whole screen

    CGRect frame = self.view.frame;
    frame.origin = CGPointMake(0.0, 0.0);
    _appView = [[UIView alloc] initWithFrame:frame];
    _appView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    _appView.clipsToBounds = YES;
    [self.view addSubview:_appView];

    // create status label that is just off screen below the app view

    _statusLabel = [[UILabel alloc] init];
    _statusLabel.font = [UIFont fontWithName:@"Helvetica-Bold" size:12.0];
    _statusLabel.backgroundColor = [UIColor darkGrayColor];
    _statusLabel.textColor = [UIColor whiteColor];
    CGSize size = [@"Hey!" sizeWithFont:_statusLabel.font]; // test size of box with random text
    _statusLabel.frame = CGRectMake(0.0, frame.size.height, frame.size.width, size.height);
    _statusLabel.textAlignment = UITextAlignmentCenter;
    _statusLabel.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
    [self.view addSubview:_statusLabel];
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self createControlsWithStatusHidden];
    _statusHidden = YES;

    // I'm instantiating from storyboard. If you're using NIBs, just create your controller controller using initWithNib and then set our appController accordingly.

    self.appController = [self.storyboard instantiateViewControllerWithIdentifier:@"MainNavigator"];
}

- (void)setAppController:(UIViewController *)controller
{
    if (controller)
    {
        controller.view.frame = CGRectMake(0.0, 0.0, _appView.frame.size.width, _appView.frame.size.height);
        [self addChildViewController:controller];
        [controller didMoveToParentViewController:self];

        if (self.appController)
        {
            // if we have both a new controller and and old one, then let's transition, cleaning up the old one upon completion

            [self transitionFromViewController:self.appController
                              toViewController:controller
                                      duration:0.5 
                                       options:UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionCurveEaseInOut
                                    animations:nil
                                    completion:^(BOOL finished){
                                        if (self.appController)
                                        {
                                            [self.appController willMoveToParentViewController:nil];
                                            [self.appController removeFromParentViewController];
                                        }
                                    }];
        }
        else 
        {
            // if we have no previous controller (i.e. this is our first rodeo), then just add it to the view

            [_appView addSubview:controller.view];
        }
    }
    else
    {
        // no new controller, so we're just removing any old on if it was there

        if (self.appController)
        {
            // if there was an old controller, remove it's view, and remove it from the view controller hierarchy

            [self.appController.view removeFromSuperview];
            [self.appController willMoveToParentViewController:nil];
            [self.appController removeFromParentViewController];
        }
    }

    _appController = controller;
}

- (void)hideStatusWithCompletion:(void (^)(BOOL finished))completion
{
    [UIView animateWithDuration:0.25 
                     animations:^{
                         CGRect labelFrame = _statusLabel.frame;
                         labelFrame.origin.y += labelFrame.size.height;
                         _statusLabel.frame = labelFrame;

                         CGRect appFrame = _appView.frame;
                         appFrame.size.height += labelFrame.size.height;
                         _appView.frame = appFrame;
                     }
                     completion:completion];
}

- (void)unhideStatusWithCompletion:(void (^)(BOOL finished))completion
{
    [UIView animateWithDuration:0.25 
                     animations:^{
                         CGRect labelFrame = _statusLabel.frame;
                         labelFrame.origin.y -= labelFrame.size.height;
                         _statusLabel.frame = labelFrame;

                         CGRect appFrame = _appView.frame;
                         appFrame.size.height -= labelFrame.size.height;
                         _appView.frame = appFrame;
                     }
                     completion:completion];
}

- (void)setStatus:(NSString *)text
{
    BOOL hasText = (text && [text length] > 0);

    if (hasText)
    {
        if (!_statusHidden)
        {
            // if we have text, but status is already shown, then hide it and unhide it with new value

            [self hideStatusWithCompletion:^(BOOL finished){
                _statusLabel.text = text;
                [self unhideStatusWithCompletion:nil];
            }];
        }
        else
        {
            // if we have text, but no status is currently shown, then just unhide it
            _statusLabel.text = text;
            [self unhideStatusWithCompletion:nil];
        }
        _statusHidden = NO;
    }
    else
    {
        if (!_statusHidden)
        {
            // if we don't have text, but status bar is shown, then just hide it

            [self hideStatusWithCompletion:^(BOOL finished){
                _statusLabel.text = text;
            }];
            _statusHidden = YES;
        }
    }
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}

@end

And then, any view controller that wants to update the status message would use a method kind of like:

- (void)setStatus:(NSString *)text
{
    UIViewController *controller = [UIApplication sharedApplication].delegate.window.rootViewController;

    if ([controller isKindOfClass:[StatusBarViewController class]])
    {
        [(StatusBarViewController *)controller setStatus:text];
    }
}

Upvotes: 1

Related Questions