Cross-dissolve transitions interfering with page curls on UIPageController pages - ios

I'm cross-dissolving between images on the pages of my UIPageController subclass. It is possible for a page to be turned while the transition is being applied. On occasion, the page being turned will become transparent mid-flip until the transition completes.
In the code below, the Page class is a subclass of UIViewController whose view has a UIImageView subview. PageController is my UIPageViewController subclass.
#implementation PageController : UIPageViewController
- (void)crossfadeToImage:(UIImage *)image
{
for (int i = 0; i < self.pages.count; i++) {
Page *page = self.pages[i]; // Pages fed to page controller in delegate
[UIView transitionWithView:page.view duration:duration options:UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveEaseInOut animations:^{
page.image = image;
} completion:nil];
}
}
Page code goes as follows:
// HEADER
#interface Page : UIViewController
#property (strong, nonatomic) UIImage *image;
- (instancetype)initWithPageFrameSize:(CGSize)size image:(UIImage *)image;
#end
// IMPLEMENTATION
#implementation Page
- (void)loadView
{
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, _pageFrameSize.width, _pageFrameSize.height)];
self.view = view;
}
- (UIImage *)image
{
return _imageView.image;
}
- (void)setImage:(UIImage *)image
{
if (!_imageView) {
_imageView = [[UIImageView alloc] initWithImage:image];
_imageView.frame = self.view.bounds;
[self.view addSubview:_imageView];
} else {
_imageView.image = image;
}
}
#end
I'm guessing the crossfade and the page curl animation are stacking in a weird way or something. Any ideas on how to prevent this "disappearing page" side-effect?

Turns out applying the animation to the imageView directly fixes this. Something like:
- (void)crossfadeToImage:(UIImage *)image
{
for (int i = 0; i < self.pages.count; i++) {
Page *page = self.pages[i]; // Pages fed to page controller in delegate
[UIView transitionWithView:page.**imageView** duration:duration options:UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveEaseInOut animations:^{
page.**imageView**.image = image;
} completion:nil];
}
}
Still, any insights as to why it doesn't work the other way are welcome.

Related

how to present modal UIViewController dialog from bottom in IOS like this (image attached)

How would one present a UIViewController (from Storyboard say) that is modal, and slides up from the bottom of the presenting view controller. Requirements would be:
slides up from bottom, with widths aligning with the width of the presenting view controller
does NOT take up whole screen or whole parent presenting view controller (rather only is as high as required to show itself)
can be shown within the context of a view controller which doesn't take the whole screen
I do not use storyboards so I wrote it all out. You can copy paste this into a brand new project and run it to see it working.
Your PresentingController needs to conform to two things. The first protocol is: UIViewControllerTransitioningDelegate which allows the controller to provide a custom presenter (namely itself in our case below). Whatever you return here (be it self, or some other object) needs to conform to UIViewControllerAnimatedTransitioning and provide the custom animations. For this self-contained example, I chose the current viewController to be the presenter and animator.
Next, it needs to conform to protocol: UIViewControllerAnimatedTransitioning which provides the custom animation for any presenting or dismissing controllers.
In other words, when we present or dismiss a viewController, animateTransition from the UIViewControllerAnimatedTransitioning protocol will be called to determine how the child controller should animate into perspective or dismiss from the view-port.
Example (With Transition Animation):
//
// ViewController.m
// SO
//
// Created by Brandon T on 2017-01-23.
// Copyright © 2017 XIO. All rights reserved.
//
#import "ViewController.h"
//Some view controller that will be presented modally.
//I have coloured it red.
#interface ModalController : UIViewController
#end
#implementation ModalController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
}
#end
//The view controller that will present or dismiss some other view controller modally.
//I have coloured it white.
#interface ViewController () <UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning>
#property (nonatomic, assign) bool presentingModalController;
#property (nonnull, nonatomic, strong) ModalController *modalController;
#property (nonnull, nonatomic, strong) UIButton *button;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
//For this example, I add a button to present and dismiss the redViewController.
self.button = [[UIButton alloc] initWithFrame:CGRectMake(15, self.view.center.y - 100, self.view.frame.size.width - 30, 45)];
[self.button setTitle:#"Present" forState:UIControlStateNormal];
[self.button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[self.button setBackgroundColor:[UIColor lightGrayColor]];
[self.button.layer setCornerRadius:5.0];
[self.button addTarget:self action:#selector(onButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.button];
//Create the redViewController and set its transitioning delegate to self (this controller will be providing the animation and presenter).
//We also set the style to OverFullScreen because we don't want this controller to disappear.
//When a view controller is presented, the one that presented it usually disappears or gets removed from the hierarchy until the child is dismissed. In the case of alerts, or controllers that need to display OVER the current one, we need to set the modalPresentationStyle.
self.modalController = [[ModalController alloc] init];
self.modalController.transitioningDelegate = self;
self.modalController.modalPresentationStyle = UIModalPresentationOverFullScreen;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)onButtonClicked:(UIButton *)button {
if (self.modalController.view.window == nil) {
[self presentViewController:self.modalController animated:YES completion:nil];
[self.button setTitle:#"Dismiss" forState:UIControlStateNormal];
//not a good idea but meh.. I need to keep this example short.
[self.view.window addSubview:self.button];
}
else {
[self.modalController dismissViewControllerAnimated:YES completion:nil];
[self.button setTitle:#"Present" forState:UIControlStateNormal];
[self.view addSubview:self.button];
}
}
//Custom Animations and Presenters.
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
self.presentingModalController = true; //We are presenting the controller.
return self; //Who is animating it? We are.
}
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
self.presentingModalController = false; //We are dismissing the view controller.
return self; //Who animated it? We did.
}
//How fast should it present? I chose 0.5 seconds.
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext {
return 0.5;
}
//The actual animation code.
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
if (self.presentingModalController) {
//If we are presenting, we need to add the new controller's view as a sub-view.
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//We need a starting frame for the animation.
CGRect startingFrame = transitionContext.containerView.bounds;
startingFrame.origin.y = startingFrame.size.height; //Starts from the bottom of the parent.
startingFrame.size.height = 100; //Has a height of 100.
//We need an end frame for the animation.
CGRect finalFrame = transitionContext.containerView.bounds;
finalFrame.origin.y = finalFrame.size.height - 100; //100 from the bottom of the parent.
finalFrame.size.height = 100; //Present with a size of 100 height.
//Add the controller's view as a subview of the context.
[transitionContext.containerView addSubview:toViewController.view];
[toViewController.view setFrame:startingFrame];
//Start animating from "startFrame" --> "endFrame" with 0.5 seconds duration and no delay. I chose easeIn style.
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
[toViewController.view setFrame:finalFrame];
} completion:^(BOOL finished) {
//We are finished animating, complete the transition!
[transitionContext completeTransition:YES];
}];
}
else {
//If we are dismissing the view controller, we need to animate it down the screen and then remove its view from the context.
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
//We only need one frame. This is the first frame. We are animating from "endFrame" --> "startingFrame" (backwards/reverse animation).
CGRect startingFrame = transitionContext.containerView.bounds;
startingFrame.origin.y = startingFrame.size.height; //Starts from the bottom of the parent.
startingFrame.size.height = 100; //Has a height of 100.
//Start the animation with 0.5 seconds duration and I chose easeOut style.
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
[fromViewController.view setFrame:startingFrame];
} completion:^(BOOL finished) {
//Remove the view controller's view from the context and complete the transition!
[fromViewController.view removeFromSuperview];
[transitionContext completeTransition:YES];
}];
}
}
#end
Example (Without Transition Animation):
//
// ViewController.m
// SO2
//
// Created by Brandon Thomas on 2017-01-23.
// Copyright © 2017 XIO. All rights reserved.
//
#import "ViewController.h"
#interface ModalController : UIViewController
#end
#implementation ModalController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
}
#end
#interface ViewController ()
#property (nonatomic, assign) bool presentingModalController;
#property (nonnull, nonatomic, strong) ModalController *modalController;
#property (nonnull, nonatomic, strong) UIButton *button;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.button = [[UIButton alloc] initWithFrame:CGRectMake(15, self.view.center.y - 100, self.view.frame.size.width - 30, 45)];
[self.button setTitle:#"Present" forState:UIControlStateNormal];
[self.button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[self.button setBackgroundColor:[UIColor lightGrayColor]];
[self.button.layer setCornerRadius:5.0];
[self.button addTarget:self action:#selector(onButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.button];
self.modalController = [[ModalController alloc] init];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)onButtonClicked:(UIButton *)button {
if (self.modalController.view.window == nil) {
//Present
CGRect startingFrame = self.view.bounds;
startingFrame.origin.y = startingFrame.size.height; //Starts from the bottom of the parent.
startingFrame.size.height = 100; //Has a height of 100.
CGRect finalFrame = self.view.bounds;
finalFrame.origin.y = finalFrame.size.height - 100; //100 from the bottom of the parent.
finalFrame.size.height = 100; //Present with a size of 100 height.
[self.modalController.view setFrame:startingFrame];
[self.modalController willMoveToParentViewController:self];
[self addChildViewController:self.modalController];
[self.view addSubview:self.modalController.view];
[self.modalController didMoveToParentViewController:self];
[UIView animateWithDuration:0.5 animations:^{
[self.modalController.view setFrame:finalFrame];
} completion:^(BOOL finished) {
}];
}
else {
//Dismiss
CGRect startingFrame = self.view.bounds;
startingFrame.origin.y = startingFrame.size.height; //Starts from the bottom of the parent.
startingFrame.size.height = 100; //Has a height of 100.
[UIView animateWithDuration:0.5 animations:^{
[self.modalController.view setFrame:startingFrame];
} completion:^(BOOL finished) {
[self.modalController.view removeFromSuperview];
[self.modalController willMoveToParentViewController:nil];
[self.modalController removeFromParentViewController];
[self.modalController didMoveToParentViewController:nil];
}];
}
}
#end
Check out the Apple documentation for this:
Presenting a View Controller Using Custom Animations
To present a view controller using custom animations, do the following
in an action method of your existing view controllers:
Create the view controller that you want to present. Create your
custom transitioning delegate object and assign it to the view
controller’s transitioningDelegate property. The methods of your
transitioning delegate should create and return your custom animator
objects when asked. Call the
presentViewController:animated:completion: method to present the view
controller. When you call the
presentViewController:animated:completion: method, UIKit initiates the
presentation process. Presentations start during the next run loop
iteration and continue until your custom animator calls the
completeTransition: method. Interactive transitions allow you to
process touch events while the transition is ongoing, but
noninteractive transitions run for the duration specified by the
animator object.
EDIT:
Your alternative option is to create a container with your custom sizes and animate your UIViewController added as a child view of your UIViewController:
[self addChildViewController:content];
content.view.frame = [self frameForContentController];
[self.view addSubview:self.currentClientView];
[content didMoveToParentViewController:self];
Taken from this Thread

Subview enlarges when toolbars vanish

I've made a subview to record sound which looks like this:
http://i.stack.imgur.com/EBdEV.png
After the bars faded, it looks like this:
http://i.imgur.com/sDAp4nk.png
I couldn't figure out why. This is the controller. Is there something in the storyboard I could turn on/off?
Thanks for help :)
#import "SubviewController.h"
#implementation SubviewController
#synthesize delegate;
- (void)viewWillAppear:(BOOL)inAnimated {
[super viewWillAppear:inAnimated];
if([self.delegate respondsToSelector:#selector(subviewControllerWillAppear:)]) {
[self.delegate subviewControllerWillAppear:self];
}
}
- (void)viewWillDisappear:(BOOL)inAnimated {
if([self.delegate respondsToSelector:#selector(subviewControllerWillDisappear:)]) {
[self.delegate subviewControllerWillDisappear:self];
}
[super viewWillDisappear:inAnimated];
}
- (void)presentFromViewController:(UIViewController *)inViewController animated:(BOOL)inAnimated {
CGRect theBounds = inViewController.view.bounds;
UIView *theView = self.view;
UIView *theBackgroundView = [[UIView alloc] initWithFrame:theBounds];
CGRect theFrame = theView.frame;
theFrame.origin.x = (CGRectGetWidth(theBounds) - CGRectGetWidth(theFrame)) / 2.0;
theFrame.origin.y = (CGRectGetHeight(theBounds) - CGRectGetHeight(theFrame)) / 2.0;
theView.frame = theFrame;
[inViewController addChildViewController:self];
theBackgroundView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.8];
theBackgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
theBackgroundView.alpha = 0.0;
[theBackgroundView addSubview:theView];
[inViewController.view addSubview:theBackgroundView];
[UIView animateWithDuration:inAnimated ? 0.75 : 0.0
animations:^{
theBackgroundView.alpha = 1.0;
}
completion:^(BOOL inFinished) {
[self didMoveToParentViewController:inViewController];
}];
}
- (void)dismissAnimated:(BOOL)inAnimated {
UIView *theView = self.view;
UIView *theBackgroundView = theView.superview;
[self willMoveToParentViewController:nil];
[UIView animateWithDuration:inAnimated ? 0.75 : 0.0
animations:^{
theBackgroundView.alpha = 0.0;
}
completion:^(BOOL inFinished) {
[theBackgroundView removeFromSuperview];
[theView removeFromSuperview];
[self removeFromParentViewController];
}];
}
#end
#implementation SubviewSegue
- (void)perform {
[self.destinationViewController presentFromViewController:self.sourceViewController animated:YES];
}
#end
Check your constraints. You probably have one that has a minimum height between 2 subviews and its pushing that toolbar down or something.
Also, a lot of weird things happen when you animate frames while autolayout is on so be careful about that.

A custom background for a modal VC and rotation

I've implemented a custom (blurred) background for a modal view controller on iPad. Here's how I do it: in the presented VC's viewWillShow: I take a snapshot image of the presenting VC's view, blur it, and add a UIImage view on top of the presenting VC. This works well.
However, when I rotate the device while the modal is showing, the blurred image gets out of sync with the view controller it represents. The image gets stretched to fill the screen, but the view controller doesn't resize its views the same way. So when the modal is dismissed and the blur goes away, the transition looks bad.
I've tried taking another snapshot in didRotateFromInterfaceOrientation:, and replacing the blurred image, but that didn't work.
Any advice on how to solve this?
UPDATE: a sample project
My code (in the modal view controller):
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self drawBackgroundBlurView];
}
- (void)drawBackgroundBlurView
{
if(_blurModalBackground) {
[_backgroundBlurView removeFromSuperview];
CGRect backgroundFrame = self.presentingViewController.view.bounds;
_backgroundBlurView = [[UIImageView alloc] initWithFrame:backgroundFrame];
_backgroundBlurView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[_backgroundBlurView setImage:[STSUtils imageByBlurringView:self.presentingViewController.view]];
[self.presentingViewController.view addSubview:_backgroundBlurView];
}
}
Utils code
+ (UIImage *)imageByBlurringView:(UIView *)view
{
UIWindow *window = [[[UIApplication sharedApplication] windows] lastObject];
BOOL isPortrait = (UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]));
CGRect frame;
if(isPortrait) {
frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.height, view.frame.size.width);
}
else {
frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, view.frame.size.height);
}
UIGraphicsBeginImageContextWithOptions(frame.size, NO, window.screen.scale);
[view drawViewHierarchyInRect:frame afterScreenUpdates:NO];
// Get the snapshot
UIImage *snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
// Apply blur
UIColor *tintColor = [UIColor colorWithWhite:1.0 alpha:0.3];
UIImage *blurredSnapshotImage = [snapshotImage applyBlurWithRadius:8
tintColor:tintColor
saturationDeltaFactor:1.8
maskImage:nil];
UIGraphicsEndImageContext();
return blurredSnapshotImage;
}
I coded a simple example that takes a screenshot from presentingViewController and sets it as background image. Here is the implementaion of my modal view controller. And it works well (both on viewWillAppear and handles rotation properly). Could you please also post STSUtils code?
#import "ScreenshoterViewController.h"
#interface ScreenshoterViewController ()
#property (weak, nonatomic) IBOutlet UIImageView *imageView;
- (IBAction)buttonBackPressed;
#end
#implementation ScreenshoterViewController
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self updateImage];
}
- (void)updateImage
{
UIGraphicsBeginImageContext(self.view.bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[self.presentingViewController.view.layer renderInContext:context];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.imageView.image = img;
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[self updateImage];
}
- (IBAction)buttonBackPressed {
[self dismissViewControllerAnimated:YES
completion:NULL];
}
#end
Update
In your utils code just replace this
[view drawViewHierarchyInRect:frame afterScreenUpdates:NO];
with this
[view drawViewHierarchyInRect:frame afterScreenUpdates:YES];
From apple docs:
afterUpdates A Boolean value that indicates whether the snapshot
should be rendered after recent changes have been incorporated.
Specify the value NO if you want to render a snapshot in the view
hierarchy’s current state, which might not include recent changes.
So you were taking screenshot that did not included latest changes in the view.

Show partial content of image using animation

I am working with images in iOS. So when a user presses let's say a button, I want an image to appear to fill up from the bottom to the top.
In order be more precise, imagine a thermometer that is empty and when you press a button the thermometer fills up from the bottom to the top. But I want this with animation.
Do you have any idea how to implement that?
Thanks!
You can easily do it by having a second view acting as a mask over the image view with your image. Then you can apply simple scale transform and animate it revealing the image. Alternatively, you can animate the frame change. Effect will be the same. I'm attaching code for 2 ways of doind this.
Here is what I achieved in both cases:
Code I used for the case I:
#interface MyViewController ()
#property(nonatomic, strong) UIImageView *imageView;
#property(nonatomic, strong) UIView *maskView;
#end
#implementation MyViewController
- (void)viewDidLoad
{
[super viewDidLoad];
_imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Image"]];
_imageView.center = self.view.center;
_imageView.layer.anchorPoint = CGPointMake(0.5, 0.0);
[self.view addSubview:_imageView];
_maskView = [[UIView alloc] initWithFrame:_imageView.frame];
_maskView.backgroundColor = [UIColor whiteColor];
_maskView.center = self.view.center;
_maskView.layer.anchorPoint = CGPointMake(0.5, 0.0);
[self.view addSubview:_maskView];
}
- (IBAction)buttonTapped:(id)sender
{
[UIView animateWithDuration:1.0f
animations:^
{
_maskView.transform = CGAffineTransformMakeScale(1.0, 0.0001);
}];
}
#end
Code I used for the case II:
#interface MyViewController ()
#property(nonatomic, strong) UIImageView *imageView;
#property(nonatomic, assign) CGFloat height;
#end
#implementation MyViewController
- (void)viewDidLoad
{
[super viewDidLoad];
_imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Image"]];
_imageView.center = self.view.center;
_imageView.contentMode = UIViewContentModeBottom;
_height = CGRectGetHeight(_imageView.bounds);
CGRect frame = _imageView.frame;
frame.size.height = 0.00001;
frame.origin.y += _height;
_imageView.frame = frame;
_imageView.clipsToBounds = YES;
[self.view addSubview:_imageView];
}
- (IBAction)buttonTapped:(id)sender
{
[UIView animateWithDuration:1.0f
animations:^
{
CGRect frame = _imageView.frame;
frame.size.height = _height;
frame.origin.y -= _height;
_imageView.frame = frame;
}];
}
#end
In both cases the end effect was the same.
You can do this by changing the frame of the view within the animate block. For example, if myView was a 100x100 sized view:
myView.frame = CGRectMake(0, [UIScreen mainScreen].bounds.size.height, 0, 0);
[UIView animateWithDuration:1.0f
animations:^
{
myView.frame = CGRectMake(0, [UIScreen mainScreen].bounds.size.height, 100, 100);
}];
This example should enlarge a 100x100 view in the bottom left corner of the screen, making it appear to reveal itself from the bottom up.

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).
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];
}
}

Resources