Last active
December 20, 2019 04:48
-
-
Save sathishvgs/6a9adbba38933a9f6fa5ab5cea29d389 to your computer and use it in GitHub Desktop.
Interactive Animation
This file contains 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
// | |
// MusicListViewController.swift | |
// MusicPlayer | |
// | |
// Created by Sathish on 17/11/19. | |
// Copyright © 2019 Full. All rights reserved. | |
// | |
import UIKit | |
import UIKit.UIGestureRecognizerSubclass | |
class MusicListViewController: UIViewController { | |
@IBOutlet weak var aniView: UIView! // PanView | |
@IBOutlet weak var bottomCons: NSLayoutConstraint! | |
// @IBOutlet weak var heightCons: NSLayoutConstraint! | |
@IBOutlet weak var panInView: UIView! // Pan Inside View | |
// @IBOutlet weak var panInTopCons: NSLayoutConstraint! | |
var animator: UIViewPropertyAnimator? | |
var transitionAnimator: [UIViewPropertyAnimator] = [] | |
var animationProcess: [CGFloat] = [] | |
var currentState: State = .closed | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
configPanGesture() | |
} | |
@IBAction func didTappedButton(_ sender: Any) { | |
let animator = UIViewPropertyAnimator(duration: 0.28, curve: .easeInOut) { | |
self.aniView.transform = CGAffineTransform(translationX: 260, y: 0) | |
} | |
animator.startAnimation() | |
} | |
func configPanGesture() { | |
let gesture = InstantPanGestureRecognizer(target: self, action: #selector(handlePan(recognizer:))) | |
aniView.addGestureRecognizer(gesture) | |
} | |
@objc | |
func handlePan(recognizer: UIPanGestureRecognizer) { | |
switch recognizer.state { | |
case .began: | |
animateIfNeeded(state: currentState.invert) | |
print("********* BEGAN STATE STARTED *********") | |
transitionAnimator.forEach { $0.pauseAnimation() } | |
animationProcess = transitionAnimator.map { $0.fractionComplete } | |
case .changed: | |
var yPoint = -(recognizer.translation(in: aniView).y / 440) | |
if currentState == .open { | |
yPoint *= -1 | |
} | |
if transitionAnimator[0].isReversed { yPoint *= -1 } | |
print("********* CHANGED STATE STARTED *********") | |
for (index, animator) in transitionAnimator.enumerated() { | |
print("Animation Process => \(animationProcess[index]) //// Translation => \(yPoint)") | |
animator.fractionComplete = yPoint + animationProcess[index] | |
} | |
case .ended: | |
let yVelocity = recognizer.velocity(in: aniView).y | |
let shouldClose = yVelocity > 0 | |
print("********* ENDED STATE STARTED *********") | |
if yVelocity == 0 { | |
transitionAnimator.forEach { $0.continueAnimation(withTimingParameters: nil, durationFactor: 0) } | |
break | |
} | |
switch currentState { | |
case .open: | |
if !shouldClose && !transitionAnimator[0].isReversed { transitionAnimator.forEach { $0.isReversed = !$0.isReversed } } | |
if shouldClose && transitionAnimator[0].isReversed { transitionAnimator.forEach { $0.isReversed = !$0.isReversed } } | |
case .closed: | |
if shouldClose && !transitionAnimator[0].isReversed { transitionAnimator.forEach { $0.isReversed = !$0.isReversed } } | |
if !shouldClose && transitionAnimator[0].isReversed { transitionAnimator.forEach { $0.isReversed = !$0.isReversed } } | |
} | |
transitionAnimator.forEach { $0.continueAnimation(withTimingParameters: nil, durationFactor: 0) } | |
default: | |
break | |
} | |
} | |
func animateIfNeeded(state: State) { | |
guard transitionAnimator.isEmpty else { return } | |
let panInsideAnimator = UIViewPropertyAnimator(duration: 1, dampingRatio: 2) { | |
switch state { | |
case .open: | |
self.panInView.transform = CGAffineTransform(translationX: 0, y: -30) | |
case .closed: | |
self.panInView.transform = CGAffineTransform(translationX: 0, y: 30) | |
} | |
} | |
let transitionAnimator = UIViewPropertyAnimator(duration: 1, dampingRatio: 1) | |
transitionAnimator.addAnimations { | |
switch state { | |
case .open: | |
self.bottomCons.constant = 0 | |
case .closed: | |
self.bottomCons.constant = -300 | |
} | |
self.view.layoutIfNeeded() | |
} | |
transitionAnimator.addCompletion { position in | |
switch position { | |
case .start: | |
self.currentState = state.invert | |
case .end: | |
self.currentState = state | |
case .current: break | |
@unknown default: () | |
} | |
switch self.currentState { | |
case .open: | |
self.bottomCons.constant = 0 | |
case .closed: | |
self.bottomCons.constant = -300 | |
} | |
print("Animation Removing...") | |
self.transitionAnimator.removeAll() | |
} | |
transitionAnimator.startAnimation() | |
panInsideAnimator.startAnimation() | |
self.transitionAnimator.append(transitionAnimator) | |
self.transitionAnimator.append(panInsideAnimator) | |
} | |
} | |
class InstantPanGestureRecognizer: UIPanGestureRecognizer { | |
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) { | |
if (self.state == UIGestureRecognizer.State.began) { return } | |
super.touchesBegan(touches, with: event) | |
self.state = UIGestureRecognizer.State.began | |
} | |
} | |
public enum State { | |
case open | |
case closed | |
var invert: State { | |
switch self { | |
case .open: return .closed | |
case .closed: return .open | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment