Skip to content
This repository has been archived by the owner on Aug 30, 2023. It is now read-only.

Commit

Permalink
Add a stock presentation controller implementation. (#35)
Browse files Browse the repository at this point in the history
Also updated the examples accordingly by removing the custom presentation controller.
  • Loading branch information
jverkoey authored Aug 23, 2017
1 parent 28f6e2e commit 6c98fa2
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 105 deletions.
98 changes: 4 additions & 94 deletions examples/CustomPresentationExample.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ final class VerticalSheetTransition: NSObject, Transition {

// When provided, the transition will use a presentation controller to customize the presentation
// of the transition.
var calculateFrameOfPresentedViewInContainerView: CalculateFrame?
var calculateFrameOfPresentedViewInContainerView: TransitionFrameCalculation?

func start(with context: TransitionContext) {
CATransaction.begin()
Expand Down Expand Up @@ -122,104 +122,14 @@ extension VerticalSheetTransition: TransitionWithPresentation, TransitionWithFal
presenting: UIViewController,
source: UIViewController?) -> UIPresentationController? {
if let calculateFrameOfPresentedViewInContainerView = calculateFrameOfPresentedViewInContainerView {
return DimmingPresentationController(presentedViewController: presented,
presenting: presenting,
calculateFrameOfPresentedViewInContainerView: calculateFrameOfPresentedViewInContainerView)
return TransitionPresentationController(presentedViewController: presented,
presenting: presenting,
calculateFrameOfPresentedView: calculateFrameOfPresentedViewInContainerView)
}
return nil
}
}

// What follows is a fairly typical presentation controller implementation that adds a dimming view
// and fades the dimming view in/out during the transition.
//
// Note that we've conformed to the Transition type: this allows the presentation controller to
// add any custom animations during the transition. The presentation controller's `start` method
// will be invoked before the Transition object's `start` method.

final class DimmingPresentationController: UIPresentationController {

init(presentedViewController: UIViewController,
presenting presentingViewController: UIViewController,
calculateFrameOfPresentedViewInContainerView: @escaping CalculateFrame) {
let dimmingView = UIView()
dimmingView.backgroundColor = UIColor(white: 0, alpha: 0.3)
dimmingView.alpha = 0
dimmingView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.dimmingView = dimmingView

self.calculateFrameOfPresentedViewInContainerView = calculateFrameOfPresentedViewInContainerView

super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
}

override var frameOfPresentedViewInContainerView: CGRect {
// We delegate out our frame calculation here:
return calculateFrameOfPresentedViewInContainerView(self)
}

override func presentationTransitionWillBegin() {
guard let containerView = containerView else { return }

dimmingView.frame = containerView.bounds
containerView.insertSubview(dimmingView, at: 0)

// This autoresizing mask assumes that the calculated frame is centered in the screen. This
// assumption won't hold true if the frame is aligned to a particular edge. We could improve
// this implementation by allowing the creator of the transition to customize the
// autoresizingMask in some manner.
presentedViewController.view.autoresizingMask = [.flexibleLeftMargin,
.flexibleTopMargin,
.flexibleRightMargin,
.flexibleBottomMargin]
}

override func presentationTransitionDidEnd(_ completed: Bool) {
if !completed {
dimmingView.removeFromSuperview()
}
}

override func dismissalTransitionWillBegin() {
// We fall back to an alongside fade out when there is no active transition instance because
// our start implementation won't be invoked in this case.
if presentedViewController.transitionController.activeTransition == nil {
presentedViewController.transitionCoordinator?.animate(alongsideTransition: { context in
self.dimmingView.alpha = 0
})
}
}

override func dismissalTransitionDidEnd(_ completed: Bool) {
if completed {
dimmingView.removeFromSuperview()
} else {
dimmingView.alpha = 1
}
}

private let calculateFrameOfPresentedViewInContainerView: CalculateFrame
fileprivate let dimmingView: UIView
}

extension DimmingPresentationController: Transition {
func start(with context: TransitionContext) {
let fade = CABasicAnimation(keyPath: "opacity")
fade.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
fade.fromValue = 0
fade.toValue = 1
if context.direction == .backward {
let swap = fade.fromValue
fade.fromValue = fade.toValue
fade.toValue = swap
}
dimmingView.layer.add(fade, forKey: fade.keyPath)
dimmingView.layer.setValue(fade.toValue, forKeyPath: fade.keyPath!)
}
}

typealias CalculateFrame = (UIPresentationController) -> CGRect

// MARK: Supplemental code

extension CustomPresentationExampleViewController {
Expand Down
2 changes: 1 addition & 1 deletion src/MDMTransitionNavigationControllerDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#import "MDMTransitionNavigationControllerDelegate.h"

#import "MDMTransitionContext.h"
#import "private/MDMPresentationTransitionController.h"
#import "private/MDMViewControllerTransitionController.h"
#import "private/MDMViewControllerTransitionContext.h"

@interface MDMTransitionNavigationControllerDelegate () <UINavigationControllerDelegate>
Expand Down
91 changes: 91 additions & 0 deletions src/MDMTransitionPresentationController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
Copyright 2017-present The Material Motion Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@protocol MDMTransitionContext;
@protocol MDMTransitionPresentationAnimationControlling;

NS_SWIFT_NAME(TransitionFrameCalculation)
typedef CGRect (^MDMTransitionFrameCalculation)(UIPresentationController * _Nonnull);

/**
A transition presentation controller implementation that supports animation delegation, a darkened
overlay view, and custom presentation frames.
The presentation controller will create and manage the lifecycle of the scrim view, ensuring that
it is removed upon a completed dismissal of the presented view controller.
*/
NS_SWIFT_NAME(TransitionPresentationController)
@interface MDMTransitionPresentationController : UIPresentationController

/**
Initializes a presentation controller with the standard values and a frame calculation block.
The frame calculation block is expected to return the desired frame of the presented view
controller.
*/
- (nonnull instancetype)initWithPresentedViewController:(nonnull UIViewController *)presentedViewController
presentingViewController:(nonnull UIViewController *)presentingViewController
calculateFrameOfPresentedView:(nullable MDMTransitionFrameCalculation)calculateFrameOfPresentedView
NS_DESIGNATED_INITIALIZER;

/**
The presentation controller's scrim view.
*/
@property(nonatomic, strong, nullable, readonly) UIView * scrimView;

/**
The animation controller is able to customize animations in reaction to view controller
presentation and dismissal events.
The animation controller is explicitly nil'd upon completion of the dismissal transition.
*/
@property(nonatomic, strong, nullable) id <MDMTransitionPresentationAnimationControlling> animationController;

@end

/**
An animation controller receives additional presentation- and dismissal-related events during a
view controller transition.
*/
NS_SWIFT_NAME(TransitionPresentationAnimationControlling)
@protocol MDMTransitionPresentationAnimationControlling <NSObject>
@optional

/**
Allows the receiver to register animations for the given transition context.
Invoked prior to the Transition instance's startWithContext.
If not implemented, the scrim view will be faded in during presentation and out during dismissal.
*/
- (void)presentationController:(nonnull MDMTransitionPresentationController *)presentationController
startWithContext:(nonnull NSObject<MDMTransitionContext> *)context;

/**
Informs the receiver that the dismissal transition is about to begin.
*/
- (void)dismissalTransitionWillBeginWithPresentationController:(nonnull MDMTransitionPresentationController *)presentationController;

/**
Informs the receiver that the dismissal transition has completed.
*/
- (void)presentationController:(nonnull MDMTransitionPresentationController *)presentationController
dismissalTransitionDidEnd:(BOOL)completed;

@end
120 changes: 120 additions & 0 deletions src/MDMTransitionPresentationController.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
Copyright 2017-present The Material Motion Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#import "MDMTransitionPresentationController.h"

#import "MDMTransition.h"
#import "MDMTransitionContext.h"
#import "MDMTransitionController.h"
#import "UIViewController+TransitionController.h"

@interface MDMTransitionPresentationController () <MDMTransition>
@end

@implementation MDMTransitionPresentationController {
CGRect (^_calculateFrameOfPresentedView)(UIPresentationController *);
}

- (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController
presentingViewController:(UIViewController *)presentingViewController
calculateFrameOfPresentedView:(MDMTransitionFrameCalculation)calculateFrameOfPresentedView {
self = [super initWithPresentedViewController:presentedViewController
presentingViewController:presentingViewController];
if (self) {
_calculateFrameOfPresentedView = [calculateFrameOfPresentedView copy];
}
return self;
}

- (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(UIViewController *)presentingViewController {
return [self initWithPresentedViewController:presentedViewController
presentingViewController:presentingViewController
calculateFrameOfPresentedView:nil];
}

- (CGRect)frameOfPresentedViewInContainerView {
if (_calculateFrameOfPresentedView) {
return _calculateFrameOfPresentedView(self);
} else {
return self.containerView.bounds;
}
}

- (BOOL)shouldRemovePresentersView {
// We don't have access to the container view when this method is called, so we can only guess as
// to whether we'll be presenting full screen by checking for the presence of a frame calculation
// block.
BOOL definitelyFullscreen = _calculateFrameOfPresentedView == nil;

// Returning true here will cause UIKit to invoke viewWillDisappear and viewDidDisappear on the
// presenting view controller, and the presenting view controller's view will be removed on
// completion of the transition.
return definitelyFullscreen;
}

- (void)dismissalTransitionWillBegin {
if (!self.presentedViewController.mdm_transitionController.activeTransition) {
[self.presentedViewController.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
self.scrimView.alpha = 0;
} completion:nil];

if ([self.animationController respondsToSelector:@selector(dismissalTransitionWillBeginWithPresentationController:)]) {
[self.animationController dismissalTransitionWillBeginWithPresentationController:self];
}
}
}

- (void)dismissalTransitionDidEnd:(BOOL)completed {
if (completed) {
[self.scrimView removeFromSuperview];
_scrimView = nil;

} else {
self.scrimView.alpha = 1;
}

if ([self.animationController respondsToSelector:@selector(presentationController:dismissalTransitionDidEnd:)]) {
[self.animationController presentationController:self dismissalTransitionDidEnd:completed];
}

if (completed) {
// Break any potential memory cycles due to our strong ownership of the animation controller.
self.animationController = nil;
}
}

- (void)startWithContext:(NSObject<MDMTransitionContext> *)context {
if (!self.scrimView) {
_scrimView = [[UIView alloc] initWithFrame:context.containerView.bounds];
self.scrimView.autoresizingMask = (UIViewAutoresizingFlexibleWidth
| UIViewAutoresizingFlexibleHeight);
self.scrimView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.3f];
[context.containerView insertSubview:self.scrimView
belowSubview:context.foreViewController.view];
}

if ([self.animationController respondsToSelector:@selector(presentationController:startWithContext:)]) {
[self.animationController presentationController:self startWithContext:context];
} else {
self.scrimView.alpha = context.direction == MDMTransitionDirectionForward ? 0 : 1;

[UIView animateWithDuration:context.duration animations:^{
self.scrimView.alpha = context.direction == MDMTransitionDirectionForward ? 1 : 0;
}];
}
}

@end
1 change: 1 addition & 0 deletions src/MotionTransitioning.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@
#import "MDMTransitionContext.h"
#import "MDMTransitionController.h"
#import "MDMTransitionNavigationControllerDelegate.h"
#import "MDMTransitionPresentationController.h"
#import "UIViewController+TransitionController.h"
10 changes: 5 additions & 5 deletions src/UIViewController+TransitionController.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

#import "UIViewController+TransitionController.h"

#import "private/MDMPresentationTransitionController.h"
#import "private/MDMViewControllerTransitionController.h"

#import <objc/runtime.h>

Expand All @@ -27,21 +27,21 @@ @implementation UIViewController (MDMTransitionController)
- (id<MDMTransitionController>)mdm_transitionController {
const void *key = [self mdm_transitionControllerKey];

MDMPresentationTransitionController *controller = objc_getAssociatedObject(self, key);
MDMViewControllerTransitionController *controller = objc_getAssociatedObject(self, key);
if (!controller) {
controller = [[MDMPresentationTransitionController alloc] initWithViewController:self];
controller = [[MDMViewControllerTransitionController alloc] initWithViewController:self];
[self mdm_setTransitionController:controller];
}
return controller;
}

#pragma mark - Private

- (void)mdm_setTransitionController:(MDMPresentationTransitionController *)controller {
- (void)mdm_setTransitionController:(MDMViewControllerTransitionController *)controller {
const void *key = [self mdm_transitionControllerKey];

// Clear the previous delegate if we'd previously set one.
MDMPresentationTransitionController *existingController = objc_getAssociatedObject(self, key);
MDMViewControllerTransitionController *existingController = objc_getAssociatedObject(self, key);
id<UIViewControllerTransitioningDelegate> delegate = self.transitioningDelegate;
if (existingController == delegate) {
self.transitioningDelegate = nil;
Expand Down
2 changes: 1 addition & 1 deletion src/private/MDMViewControllerTransitionContext.m
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionCo

// TODO(featherless): Implement interactive transitioning. Need to implement
// UIViewControllerInteractiveTransitioning here and isInteractive and interactionController* in
// MDMPresentationTransitionController.
// MDMViewControllerTransitionController.

#pragma mark - MDMTransitionContext

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

#import "MDMTransitionController.h"

@interface MDMPresentationTransitionController : NSObject <MDMTransitionController, UIViewControllerTransitioningDelegate>
@interface MDMViewControllerTransitionController : NSObject <MDMTransitionController, UIViewControllerTransitioningDelegate>

- (nonnull instancetype)initWithViewController:(nonnull UIViewController *)viewController
NS_DESIGNATED_INITIALIZER;
Expand Down
Loading

0 comments on commit 6c98fa2

Please sign in to comment.