How to implement transparent modal view on iPhone?
Some time ago at Untitled Kingdom, I had to implement a modal view controller with a transparent background for iPhone. I did some research, but solutions I found did not work or were not clear enough for me. I decided to do my own tests, and now I’m sharing the results. Please keep in mind that this solution applies only for iPhone.
Custom segue
You can easily make a transparent modal view using custom segue. This is my first version:
#import "UKCustomModalSegue.h"
@implementation UKCustomModalSegue
- (void)perform
{
UIViewController * sourceViewController = self.sourceViewController;
UIViewController * modalViewController = self.destinationViewController;
UINavigationController * navCon = sourceViewController.navigationController;
UIModalPresentationStyle prevStyle = navCon.modalPresentationStyle;
[navCon setModalPresentationStyle:UIModalPresentationCurrentContext];
[sourceViewController presentViewController:modalViewController animated:NO completion:nil];
[navCon setModalPresentationStyle:prevStyle];
}
@end
The biggest adventage of this solution is that it works, but the view has no animation as you launch it. It does have an animation when you dissmiss it. I decided to make a launch animation of my own. The final code looks like this:
#import "UKCustomModalSegue.h"
static CGFloat animationDuration = 0.4f;
@implementation UKCustomModalSegue
- (void)perform
{
UIViewController * sourceViewController = self.sourceViewController;
UIViewController * modalViewController = self.destinationViewController;
UINavigationController * navCon = sourceViewController.navigationController;
UIWindow * mainWindow = [[[UIApplication sharedApplication] windows] firstObject];
[mainWindow addSubview:modalViewController.view];
CGPoint modalCenter = modalViewController.view.center;
modalCenter.y += [[UIScreen mainScreen] bounds].size.height;
modalViewController.view.center = modalCenter;
[UIView animateWithDuration:animationDuration animations:^{
modalViewController.view.center = sourceViewController.view.center;
} completion:^(BOOL finished) {
[modalViewController.view removeFromSuperview];
UIModalPresentationStyle prevStyle = navCon.modalPresentationStyle;
[navCon setModalPresentationStyle:UIModalPresentationCurrentContext];
[sourceViewController setModalPresentationStyle:UIModalPresentationCurrentContext];
[sourceViewController presentViewController:modalViewController animated:NO completion:nil];
[navCon setModalPresentationStyle:prevStyle];
}];
}
@end
The most important thing to remember when using this solution is to change the modalPresentationStyle of UINavigationController to UIModalPresentationCurrentContext. I think this is the simplest way to display a modal view controler with a transparent background using the standard animation. If you want to make custom animation, it becames more complicated. You can change the launch animation fairly easily, but to change the dismissing animation you have to use unwind segue, which may cause some problems. To achieve it, you can use another solution.
Custom transition
In the solution shown above, you have to make your own segue. In this solution, you need to adjust the view controller and use a standard modal segue. The first step is to make a transitioning delegate object, which will describe the animation. In my case, it is a fade in for the launch animation and a fade out for the dismissing animation.
UKModalTransitioningDelegate.h
#import <Foundation/Foundation.h>
@interface UKModalTransitioningDelegate : NSObject <UIViewControllerTransitioningDelegate,
UIViewControllerAnimatedTransitioning>
@end
UKModalTransitioningDelegate.m
#import "UKModalTransitioningDelegate.h"
static NSTimeInterval animationDuration = 0.4f;
@interface UKModalTransitioningDelegate()
@property (nonatomic) BOOL presenting;
@end
@implementation UKModalTransitioningDelegate
#pragma mark - UIViewControllerTransitioningDelegate
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented
presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
self.presenting = YES;
return self;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
self.presenting = NO;
return self;
}
#pragma mark - UIViewControllerAnimatedTransitioning
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return animationDuration;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
UIViewController * fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController * toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView * containerView = [transitionContext containerView];
void (^animationBlock)();
if (self.presenting)
{
toViewController.view.alpha = 0.0f;
[containerView addSubview:fromViewController.view];
[containerView addSubview:toViewController.view];
animationBlock = ^(){
toViewController.view.alpha = 1.0f;
};
} else {
[containerView addSubview:toViewController.view];
[containerView addSubview:fromViewController.view];
animationBlock = ^(){
fromViewController.view.alpha = 0.0f;
};
}
void (^completion)(BOOL) = ^(BOOL finished) {
[transitionContext completeTransition:!transitionContext.transitionWasCancelled];
};
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:animationBlock
completion:completion];
}
@end
Next, you need to set the transitioning delegate for the controller you want to present. I made a custom property for my subclass of UIViewController, which the user can change, or use a default delegate.
UKModalTransitioningViewController.h
#import <UIKit/UIKit.h>
@class UKModalTransitioningDelegate;
@interface UKModalTransitioningViewController : UIViewController
@property (strong, nonatomic) UKModalTransitioningDelegate * modalTransitioningDelegate;
@end
UKModalTransitioningViewController.m
#import "UKModalTransitioningViewController.h"
#import "UKModalTransitioningDelegate.h"
@interface UKModalTransitioningViewController ()
@end
@implementation UKModalTransitioningViewController
#pragma mark - Initializations
- (id)init
{
self = [super init];
if (self)
{
[self setup];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self setup];
}
return self;
}
- (void)setup
{
self.modalPresentationStyle = UIModalPresentationCustom;
self.modalTransitioningDelegate = [[UKModalTransitioningDelegate alloc] init];
}
#pragma mark - Custom accessors
- (void)setModalTransitioningDelegate:(UKModalTransitioningDelegate *)modalTransitioningDelegate
{
_modalTransitioningDelegate = modalTransitioningDelegate;
self.transitioningDelegate = modalTransitioningDelegate;
}
#pragma mark - IBActions
- (IBAction)viewTapAction:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
@end
This solution is more complex than the previous one, but allows you to easily change the animations for both presenting and dismissing.
Summary
You can use both solutions to present view controllers in the view controller embedded in the UINavigationController or in another modal view controller. I switch between those two depending on what I need my view controllers to do. I prefer the second solution because it doesn’t use any tricks with modalPresentationStyle, and I like to keep my code as simple as possible. When I’m 100% sure that I won’t need anything more than the first solution provides, I’ll stick with that.