Skip to content

Instantly share code, notes, and snippets.

@automactic
Created January 24, 2018 04:18
Show Gist options
  • Save automactic/842669d1ab1be9953f8d252824c8c5bc to your computer and use it in GitHub Desktop.
Save automactic/842669d1ab1be9953f8d252824c8c5bc to your computer and use it in GitHub Desktop.
Interactive Controller Experiments
//
// InteractiveController.swift
// iOS
//
// Created by Chris Li on 1/22/18.
// Copyright © 2018 Chris Li. All rights reserved.
//
import UIKit
class TestTableViewController: InteractiveController, UITableViewDataSource, UIGestureRecognizerDelegate {
let tableView = UITableView()
override func loadView() {
view = tableView
tableView.dataSource = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
override func viewDidLoad() {
super.viewDidLoad()
panGestureRecognizer.delegate = self
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "\(indexPath.row)"
return cell
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
let gestureRecognizer = (gestureRecognizer as! UIPanGestureRecognizer)
let direction = gestureRecognizer.velocity(in: view).y
if tableView.contentOffset.y == 0 && direction > 0 {
tableView.isUserInteractionEnabled = false
} else {
tableView.isUserInteractionEnabled = true
}
return false
}
}
class InteractiveController: UIViewController, UIViewControllerTransitioningDelegate {
let panGestureRecognizer = UIPanGestureRecognizer()
let dismissalTransition = InteractiveDismissalTransition()
override func viewDidLoad() {
super.viewDidLoad()
view.addGestureRecognizer(panGestureRecognizer)
panGestureRecognizer.addTarget(self, action: #selector(handleGestureRecognizer(gestureRecognizer:)))
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissController))
}
@objc func dismissController() {
dismissalTransition.wantsInteractiveStart = false
dismiss(animated: true, completion: nil)
}
@objc func handleGestureRecognizer(gestureRecognizer: UIPanGestureRecognizer) {
let location = gestureRecognizer.translation(in: gestureRecognizer.view?.superview)
switch gestureRecognizer.state {
case .began:
dismissalTransition.wantsInteractiveStart = true
dismiss(animated: true, completion: nil)
case .changed:
let progress = location.y / view.frame.height
dismissalTransition.update(progress)
case .cancelled:
dismissalTransition.cancel()
case .ended:
let progress = location.y / view.frame.height
let velocity = gestureRecognizer.velocity(in: gestureRecognizer.view?.superview)
if velocity.y > 1000 {
dismissalTransition.finish()
} else if progress < 0.1 {
dismissalTransition.cancel()
} else if velocity.y < -100 {
dismissalTransition.cancel()
} else {
dismissalTransition.finish()
}
default:
break
}
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return dismissalTransition
}
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return dismissalTransition
}
}
class InteractiveDismissalTransition: UIPercentDrivenInteractiveTransition, UIViewControllerAnimatedTransitioning {
override init() {
super.init()
timingCurve = UICubicTimingParameters(animationCurve: .easeInOut)
completionSpeed = 0.9
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.25
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
animate(context: transitionContext)
}
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
let animator = UIViewPropertyAnimator(duration: transitionDuration(using: transitionContext), timingParameters: timingCurve ?? UICubicTimingParameters(animationCurve: .easeInOut))
animator.addAnimations {
self.animate(context: transitionContext)
}
return animator
}
private func animate(context: UIViewControllerContextTransitioning) {
let container = context.containerView
guard let fromView = context.view(forKey: .from) else {
context.completeTransition(false)
return
}
let constraints = container.constraints.filter({ ($0.firstItem === container && $0.secondItem === fromView) || ($0.firstItem === fromView && $0.secondItem === container) })
NSLayoutConstraint.deactivate(constraints)
fromView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
fromView.widthAnchor.constraint(equalTo: container.widthAnchor),
fromView.heightAnchor.constraint(equalTo: container.heightAnchor),
fromView.centerXAnchor.constraint(equalTo: container.centerXAnchor)])
let topConstraint = fromView.topAnchor.constraint(equalTo: container.topAnchor)
topConstraint.priority = .defaultHigh
topConstraint.isActive = true
let bottomConstraint = fromView.topAnchor.constraint(equalTo: container.bottomAnchor)
bottomConstraint.priority = .defaultLow
bottomConstraint.isActive = true
container.layoutIfNeeded()
UIView.animate(withDuration: transitionDuration(using: context), delay: 0.0, options: .curveEaseInOut, animations: {
topConstraint.priority = .defaultLow
bottomConstraint.priority = .defaultHigh
container.layoutIfNeeded()
}) { (completed) in
if context.transitionWasCancelled || !completed {
topConstraint.priority = .defaultHigh
bottomConstraint.priority = .defaultLow
context.completeTransition(false)
} else {
context.completeTransition(true)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment