From 3498a0f8e7d177e21e89fc2934c371423e79f1f2 Mon Sep 17 00:00:00 2001 From: Jeff Verkoeyen Date: Mon, 17 Apr 2017 17:02:24 -0400 Subject: [PATCH] Add foreAlignmentEdge property to TransitionController. Summary: If a view controller has a non-zero preferredContentSize, then the foreAlignmentEdge will be used to align the view to either the center of the screen, if nil, or to the specified edge (one of minX, minY, maxX, or maxY). If preferredContentSize is zero, then the transition's `finalFrame` value will be used instead - this is generally the containerView's bounds. This API is useful for building modal dialogs and sliding drawers that are presented over the current context with `modalPresentationStyle = .overCurrentContext`. Reviewers: O2 Material Motion, O4 Material Apple platform reviewers, #material_motion, markwei Reviewed By: O2 Material Motion, O4 Material Apple platform reviewers, #material_motion, markwei Subscribers: markwei Tags: #material_motion Differential Revision: http://codereview.cc/D3067 --- examples/ModalDialogExample.swift | 13 ++----- src/transitions/TransitionContext.swift | 40 ++++++++++++++++++++-- src/transitions/TransitionController.swift | 9 ++++- 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/examples/ModalDialogExample.swift b/examples/ModalDialogExample.swift index ca44606..4d5aeaa 100644 --- a/examples/ModalDialogExample.swift +++ b/examples/ModalDialogExample.swift @@ -46,7 +46,7 @@ class ModalDialogViewController: UIViewController { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) transitionController.transitionType = ModalDialogTransition.self - + preferredContentSize = .init(width: 200, height: 200) modalPresentationStyle = .overCurrentContext } @@ -64,8 +64,6 @@ class ModalDialogViewController: UIViewController { view.layer.shadowRadius = 5 view.layer.shadowOpacity = 1 view.layer.shadowOffset = .init(width: 0, height: 2) - - preferredContentSize = .init(width: 200, height: 200) } } @@ -74,15 +72,10 @@ class ModalDialogTransition: SelfDismissingTransition { required init() {} func willBeginTransition(withContext ctx: TransitionContext, runtime: MotionRuntime) -> [Stateful] { - let size = ctx.fore.preferredContentSize - - if ctx.direction == .forward { - ctx.fore.view.bounds = CGRect(origin: .zero, size: size) - } - + let size = ctx.fore.view.frame.size let bounds = ctx.containerView().bounds let backPosition = CGPoint(x: bounds.midX, y: bounds.maxY + size.height * 3 / 4) - let forePosition = CGPoint(x: bounds.midX, y: bounds.midY) + let forePosition = ctx.fore.view.layer.position let reactiveForeLayer = runtime.get(ctx.fore.view.layer) let position = reactiveForeLayer.position diff --git a/src/transitions/TransitionContext.swift b/src/transitions/TransitionContext.swift index 8664d59..5c63e68 100644 --- a/src/transitions/TransitionContext.swift +++ b/src/transitions/TransitionContext.swift @@ -84,6 +84,8 @@ public final class TransitionContext: NSObject { */ public let fore: UIViewController + public let foreAlignmentEdge: CGRectEdge? + /** The set of gesture recognizers associated with this transition. */ public let gestureRecognizers: Set @@ -96,12 +98,14 @@ public final class TransitionContext: NSObject { direction: TransitionDirection, back: UIViewController, fore: UIViewController, - gestureRecognizers: Set) { + gestureRecognizers: Set, + foreAlignmentEdge: CGRectEdge?) { self.direction = createProperty("Transition.direction", withInitialValue: direction) self.initialDirection = direction self.back = back self.fore = fore self.gestureRecognizers = gestureRecognizers + self.foreAlignmentEdge = foreAlignmentEdge self.window = TransitionTimeWindow(duration: TransitionContext.defaultDuration) // TODO: Create a Timeline. @@ -140,6 +144,30 @@ extension TransitionContext: UIViewControllerInteractiveTransitioning { } } +private func preferredFrame(for viewController: UIViewController, + inBounds bounds: CGRect, + alignmentEdge: CGRectEdge?) -> CGRect? { + guard viewController.preferredContentSize != .zero() else { + return nil + } + + let size = viewController.preferredContentSize + let origin: CGPoint + switch alignmentEdge { + case nil: // Centered + origin = .init(x: bounds.midX - size.width / 2, y: bounds.midY - size.height / 2) + case .minXEdge?: + origin = .init(x: bounds.minX, y: bounds.midY - size.height / 2) + case .minYEdge?: + origin = .init(x: bounds.midX - size.width / 2, y: bounds.minY) + case .maxXEdge?: + origin = .init(x: bounds.maxX - size.width, y: bounds.midY - size.height / 2) + case .maxYEdge?: + origin = .init(x: bounds.midX - size.width / 2, y: bounds.maxY - size.height) + } + return .init(origin: origin, size: size) +} + extension TransitionContext { fileprivate func initiateTransition() { if let from = context.viewController(forKey: .from) { @@ -150,7 +178,15 @@ extension TransitionContext { } if let to = context.viewController(forKey: .to) { - let finalFrame = context.finalFrame(for: to) + let finalFrame: CGRect + + if let preferredFrame = preferredFrame(for: to, + inBounds: context.containerView.bounds, + alignmentEdge: (direction.value == .forward) ? foreAlignmentEdge : nil) { + finalFrame = preferredFrame + } else { + finalFrame = context.finalFrame(for: to) + } if !finalFrame.isEmpty { to.view.frame = finalFrame } diff --git a/src/transitions/TransitionController.swift b/src/transitions/TransitionController.swift index 51906c9..13bcc39 100644 --- a/src/transitions/TransitionController.swift +++ b/src/transitions/TransitionController.swift @@ -109,6 +109,11 @@ public final class TransitionController { get { return _transitioningDelegate.gestureDelegate.gestureRecognizers } } + public var foreAlignmentEdge: CGRectEdge? { + set { _transitioningDelegate.foreAlignmentEdge = newValue } + get { return _transitioningDelegate.foreAlignmentEdge } + } + /** The transitioning delegate managed by this controller. @@ -152,6 +157,7 @@ private final class TransitioningDelegate: NSObject, UIViewControllerTransitioni self.dismisser.delegate = self } + var foreAlignmentEdge: CGRectEdge? var ctx: TransitionContext? var transitionType: Transition.Type? @@ -180,7 +186,8 @@ private final class TransitioningDelegate: NSObject, UIViewControllerTransitioni direction: direction, back: back, fore: fore, - gestureRecognizers: gestureDelegate.gestureRecognizers) + gestureRecognizers: gestureDelegate.gestureRecognizers, + foreAlignmentEdge: foreAlignmentEdge) ctx?.delegate = self } }