Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modal transition style like in Mail app

I am trying to achieve a modal presentation effect where the presented view covers the parent view only partially as shown in the picture below.

enter image description here

I know I could achieve this by implementing custom transitions using UIPresentationController. I don't want to reinvent the wheel so before I roll on with development I would like to ask.

Is there a build in support for this kind of transition in the APIs?

I researched all available Modal Presentation Styles and it appears to me there is no support for the transition I want to make and the only way of achieving it is just to code it.

like image 224
Rafa de King Avatar asked Nov 18 '14 12:11

Rafa de King


1 Answers

I ran into this exact same issue. I went down the modal presentation styles route as well and kept hitting a wall (specifically getting it working on an iPhone rather than an iPad).

After some digging around, I was able to get it working though. Here's how I did it:

To start, we need a view controller that we will be presenting (the modal one) to set it's view's background color to transparent and set the frame of the navigation controller's view to some offset.

ModalViewController.h

@import UIKit;

@class ModalViewController;

@protocol ModalViewControllerDelegate <NSObject>

- (void)modalViewControllerDidCancel:(ModalViewController *)modalViewController;

@end

@interface ModalViewController : UIViewController
@property (weak, nonatomic) id<ModalViewControllerDelegate> delegate;

- (instancetype)initWithRootViewController:(UIViewController *)rootViewController;
@end

ModalViewController.m

static const CGFloat kTopOffset = 50.0f;

@implementation ModalViewController {
    UINavigationController *_navController;
}

- (instancetype)initWithRootViewController:(UIViewController *)rootViewController
{
    self = [super initWithNibName:nil bundle:nil];
    if (self) {
        rootViewController.navigationItem.leftBarButtonItem = [self cancelButton];
        _navController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
        self.view.backgroundColor = [UIColor clearColor];
        [self.view addSubview:_navController.view];

        // this is important (prevents black overlay)
        self.modalPresentationStyle = UIModalPresentationOverFullScreen;
    }

    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    CGRect bounds = self.view.bounds;
    _navController.view.frame = CGRectMake(0, kTopOffset, CGRectGetWidth(bounds), CGRectGetHeight(bounds) - kTopOffset);
}

- (UIBarButtonItem *)cancelButton
{
    return [[UIBarButtonItem alloc] initWithTitle:@"Cancel" style:UIBarButtonItemStylePlain target:self action:@selector(cancelButtonClicked:)];
}

- (void)cancelButtonClicked:(id)sender
{
    [_delegate modalViewControllerDidCancel:self];
}

@end

Next, we need to set up the presenting controller to run the following animation:

  • Scale itself down
  • Fade out a lil' bit
  • Present the modal view controller using presentViewController:animated:completion

This is what I did

PresentingViewController.m

static const CGFloat kTransitionScale = 0.9f;
static const CGFloat kTransitionAlpha = 0.6f;
static const NSTimeInterval kTransitionDuration = 0.5;

@interface PresentingViewController <ModalViewControllerDelegate>
@end

@implementation PresentingViewController
...
...

- (void)showModalViewController
{
    self.navigationController.view.layer.shouldRasterize = YES;
    self.navigationController.view.layer.rasterizationScale = [UIScreen mainScreen].scale;

    UIViewController *controller = // init some view controller
    ModalViewController *container = [[ModalViewController alloc] initWithRootViewController:controller];
    container.delegate = self;

    __weak UIViewController *weakSelf = self;
    [UIView animateWithDuration:kTransitionDuration animations:^{
        weakSelf.navigationController.view.transform = CGAffineTransformMakeScale(kTransitionScale, kTransitionScale);
        weakSelf.navigationController.view.alpha = kTransitionAlpha;
        [weakSelf presentViewController:container animated:YES completion:nil];
    } completion:^(BOOL finished) {
        weakSelf.navigationController.view.layer.shouldRasterize = NO;
    }];
}

#pragma mark - ModalViewControllerDelegate

- (void)modalViewControllerDidCancel:(ModalViewController *)modalViewController
{
    __weak UIViewController *weakSelf = self;
    [UIView animateWithDuration:kTransitionDuration animations:^{
        weakSelf.navigationController.view.alpha = 1;
        weakSelf.navigationController.view.transform = CGAffineTransformIdentity;
        [weakSelf dismissViewControllerAnimated:YES completion:nil];
    }];
}
@end
like image 104
pseudomuto Avatar answered Nov 03 '22 00:11

pseudomuto