Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sugarmo/50cb5fa721cda8849a6c1f067c92ce7d to your computer and use it in GitHub Desktop.
Save sugarmo/50cb5fa721cda8849a6c1f067c92ce7d to your computer and use it in GitHub Desktop.
How to use UIPercentDrivenInteractiveTransition in one file
//
// 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