Forked from quantcon/InteractiveVisorViewController.swift
Created
December 24, 2019 09:41
-
-
Save sugarmo/50cb5fa721cda8849a6c1f067c92ce7d to your computer and use it in GitHub Desktop.
How to use UIPercentDrivenInteractiveTransition in one file
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ViewController.swift | |
// Interactive Animated Presentation | |
// "Visor" Effect | |
// | |
import UIKit | |
class ViewController: UIViewController { | |
let visorTab = UIView() | |
let visorViewController = UIViewController() | |
var isInteractive = false | |
let interactiveCoordinator = UIPercentDrivenInteractiveTransition() | |
var dragControlView: UIView { | |
return self.visorTab | |
} | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
visorViewController.transitioningDelegate = self | |
visorViewController.view.backgroundColor = .red | |
let pan = UIPanGestureRecognizer(target: self, action: #selector(didPan)) | |
visorTab.addGestureRecognizer(pan) | |
visorTab.backgroundColor = .red | |
visorTab.translatesAutoresizingMaskIntoConstraints = false | |
view.addSubview(visorTab) | |
NSLayoutConstraint.activate( | |
[ | |
NSLayoutConstraint(item: visorTab, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: 0.0), | |
NSLayoutConstraint(item: visorTab, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1.0, constant: 0.0), | |
NSLayoutConstraint(item: visorTab, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 100.0), | |
NSLayoutConstraint(item: visorTab, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 100.0) | |
] | |
) | |
} | |
@objc func didPan(_ pan: UIPanGestureRecognizer) { | |
let translation = pan.translation(in: view).y | |
let distance = translation / view.bounds.height | |
self.interactiveCoordinator.completionSpeed = 1.1 - distance | |
switch (pan.state) { | |
case .began: | |
self.isInteractive = true | |
self.present(visorViewController, animated: true) | |
case .changed: | |
self.interactiveCoordinator.update(distance) | |
default: | |
self.isInteractive = false | |
if distance < 0.4 { | |
self.interactiveCoordinator.cancel() | |
} else { | |
self.interactiveCoordinator.finish() | |
} | |
} | |
} | |
} | |
extension ViewController: UIViewControllerTransitioningDelegate { | |
public func presentationController( | |
forPresented presented: UIViewController, | |
presenting: UIViewController?, | |
source: UIViewController | |
) -> UIPresentationController? { | |
return DragTransitionPresentationController(presentedViewController: presented, presenting: presenting) | |
} | |
public func animationController( | |
forPresented presented: UIViewController, | |
presenting: UIViewController, source: UIViewController | |
) -> UIViewControllerAnimatedTransitioning? { | |
return DragTransitionAnimator(.present) | |
} | |
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { | |
return DragTransitionAnimator(.dismiss) | |
} | |
public func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { | |
return self.isInteractive ? self.interactiveCoordinator : nil | |
} | |
public func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { | |
return self.isInteractive ? self.interactiveCoordinator : nil | |
} | |
} | |
public class DragTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning { | |
public enum Mode { | |
case dismiss | |
case present | |
} | |
var mode: Mode | |
public init(_ mode: Mode) { | |
self.mode = mode | |
super.init() | |
} | |
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { | |
return 0.3 | |
} | |
public func animateTransition(using context: UIViewControllerContextTransitioning) { | |
func slide(_ view: UIView, from: CGPoint, to: CGPoint, controlView: UIView? = nil) { | |
view.center = from | |
let duration = self.transitionDuration(using: context) | |
let distance = to.y - from.y | |
UIView.animateKeyframes(withDuration: duration, delay: 0, options: [], animations: { | |
UIView.setAnimationCurve(.linear) | |
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1, animations: { | |
view.center = to | |
if let controlView = controlView { | |
controlView.center = CGPoint(x: controlView.center.x, y: controlView.center.y + distance) | |
} | |
}) | |
}) { done in | |
if done { | |
context.completeTransition(!context.transitionWasCancelled) | |
} | |
} | |
} | |
guard let fromView = context.view(forKey: UITransitionContextViewKey.from), | |
let toView = context.view(forKey: UITransitionContextViewKey.to) else { | |
context.completeTransition(true) | |
return | |
} | |
let vc = context.viewController(forKey: UITransitionContextViewControllerKey.from) as? ViewController | |
let containerView = context.containerView | |
containerView.addSubview(fromView) | |
containerView.addSubview(toView) | |
let controlView = vc?.dragControlView.snapshotView(afterScreenUpdates: false) | |
if let controlView = controlView { | |
controlView.frame = vc?.dragControlView.frame ?? .zero | |
containerView.addSubview(controlView) | |
} | |
let viewHeight = containerView.bounds.size.height | |
let ending = containerView.center | |
let starting = CGPoint(x: ending.x, y: ending.y + -1 * viewHeight) | |
switch mode { | |
case .present: | |
slide(toView, from: starting, to: ending, controlView: controlView) | |
case .dismiss: | |
containerView.sendSubviewToBack(toView) | |
slide(fromView, from: ending, to: starting) | |
} | |
} | |
} | |
public class DragTransitionPresentationController: UIPresentationController { | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment