123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- //
- // Created by Tom Baranes on 05/05/16.
- // Copyright © 2016 IBAnimatable. All rights reserved.
- //
- import UIKit
- public class FlipAnimator: NSObject, AnimatedTransitioning {
- // MARK: - AnimatorProtocol
- public var transitionAnimationType: TransitionAnimationType
- public var transitionDuration: Duration = defaultTransitionDuration
- public var reverseAnimationType: TransitionAnimationType?
- public var interactiveGestureType: InteractiveGestureType?
- // MARK: - Private params
- fileprivate var fromDirection: TransitionAnimationType.Direction
- // MARK: - Private fold transition
- fileprivate var transform: CATransform3D = CATransform3DIdentity
- fileprivate var reverse: Bool = false
- fileprivate var horizontal: Bool = false
- // MARK: - Life cycle
- public init(from direction: TransitionAnimationType.Direction, duration: Duration) {
- fromDirection = direction
- transitionDuration = duration
- horizontal = fromDirection.isHorizontal
- transitionAnimationType = .flip(from: direction)
- reverseAnimationType = .flip(from: direction.opposite)
- interactiveGestureType = .pan(from: direction.opposingGesture)
- reverse = direction == .right
- super.init()
- }
- }
- extension FlipAnimator: UIViewControllerAnimatedTransitioning {
- public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
- return retrieveTransitionDuration(transitionContext: transitionContext)
- }
- public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
- let (tempfromView, tempToView, tempContainerView) = retrieveViews(transitionContext: transitionContext)
- guard let fromView = tempfromView, let toView = tempToView, let containerView = tempContainerView else {
- transitionContext.completeTransition(true)
- return
- }
- containerView.insertSubview(toView, at: 0)
- transform.m34 = -0.002
- containerView.layer.sublayerTransform = transform
- toView.frame = fromView.frame
- let flipViews = makeSnapshots(toView: toView, fromView: fromView, containerView: containerView)
- animateFlipTransition(flippedSectionOfFromView: flipViews.0, flippedSectionOfToView: flipViews.1) {
- if transitionContext.transitionWasCancelled {
- self.removeOtherViews(viewToKeep: fromView)
- } else {
- self.removeOtherViews(viewToKeep: toView)
- }
- transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
- }
- }
- }
- // MARK: - Setup flip transition
- private extension FlipAnimator {
- func makeSnapshots(toView: UIView, fromView: UIView, containerView: UIView) -> ((UIView, UIView), (UIView, UIView)) {
- let toViewSnapshots = makeSnapshot(from: toView, afterUpdates: true)
- var flippedSectionOfToView = toViewSnapshots[reverse ? 0 : 1]
- let fromViewSnapshots = makeSnapshot(from: fromView, afterUpdates: false)
- var flippedSectionOfFromView = fromViewSnapshots[reverse ? 1 : 0]
- flippedSectionOfFromView = addShadow(to: flippedSectionOfFromView, reverse: !reverse)
- let flippedSectionOfFromViewShadow = flippedSectionOfFromView.subviews[1]
- flippedSectionOfFromViewShadow.alpha = 0.0
- flippedSectionOfToView = addShadow(to: flippedSectionOfToView, reverse: reverse)
- let flippedSectionOfToViewShadow = flippedSectionOfToView.subviews[1]
- flippedSectionOfToViewShadow.alpha = 1.0
- var axesValues = valuesForAxe(initialValue: reverse ? 0.0 : 1.0, reverseValue: 0.5)
- updateAnchorPointAndOffset(anchorPoint: CGPoint(x: axesValues.0, y: axesValues.1), view: flippedSectionOfFromView)
- axesValues = valuesForAxe(initialValue: reverse ? 1.0 : 0.0, reverseValue: 0.5)
- updateAnchorPointAndOffset(anchorPoint: CGPoint(x: axesValues.0, y: axesValues.1), view: flippedSectionOfToView)
- flippedSectionOfToView.layer.transform = rotate(angle: (reverse ? .pi : -.pi) / 2)
- return ((flippedSectionOfFromView, flippedSectionOfFromViewShadow), (flippedSectionOfToView, flippedSectionOfToViewShadow))
- }
- func makeSnapshot(from view: UIView, afterUpdates: Bool) -> [UIView] {
- let containerView = view.superview
- let width = valuesForAxe(initialValue: view.frame.size.width / 2, reverseValue: view.frame.size.width)
- let height = valuesForAxe(initialValue: view.frame.size.height, reverseValue: view.frame.size.height / 2)
- var snapshotRegion = CGRect(x: 0, y: 0, width: width.0, height: height.0)
- let leftHandView = view.resizableSnapshotView(from: snapshotRegion, afterScreenUpdates: afterUpdates, withCapInsets: .zero)
- leftHandView?.frame = snapshotRegion
- containerView?.addSubview(leftHandView!)
- let x = valuesForAxe(initialValue: view.frame.size.width / 2, reverseValue: 0)
- let y = valuesForAxe(initialValue: 0, reverseValue: view.frame.size.height / 2)
- snapshotRegion = CGRect(x: x.0, y: y.0, width: width.0, height: height.0)
- let rightHandView = view.resizableSnapshotView(from: snapshotRegion, afterScreenUpdates: afterUpdates, withCapInsets: .zero)
- rightHandView?.frame = snapshotRegion
- containerView?.addSubview(rightHandView!)
- containerView?.sendSubviewToBack(view)
- return [leftHandView!, rightHandView!]
- }
- func addShadow(to view: UIView, reverse: Bool) -> UIView {
- let containerView = view.superview
- let viewWithShadow = UIView(frame: view.frame)
- containerView?.insertSubview(viewWithShadow, aboveSubview: view)
- view.removeFromSuperview()
- let shadowView = UIView(frame: viewWithShadow.bounds)
- let gradient = CAGradientLayer()
- gradient.frame = shadowView.bounds
- // swiftlint:disable:next object_literal
- gradient.colors = [UIColor(white: 0.0, alpha: 0.0), UIColor(white: 0.0, alpha: 0.5)]
- if horizontal {
- var axesValues = valuesForAxe(initialValue: reverse ? 0.0 : 1.0, reverseValue: reverse ? 0.2 : 0.0)
- gradient.startPoint = CGPoint(x: axesValues.0, y: axesValues.1)
- axesValues = valuesForAxe(initialValue: reverse ? 1.0 : 0.0, reverseValue: reverse ? 0.0 : 1.0)
- gradient.endPoint = CGPoint(x: axesValues.0, y: axesValues.1)
- } else {
- var axesValues = valuesForAxe(initialValue: reverse ? 0.2 : 0.0, reverseValue: reverse ? 0.0 : 1.0)
- gradient.startPoint = CGPoint(x: axesValues.0, y: axesValues.1)
- axesValues = valuesForAxe(initialValue: reverse ? 0.0 : 1.0, reverseValue: reverse ? 1.0 : 0.0)
- gradient.endPoint = CGPoint(x: axesValues.0, y: axesValues.1)
- }
- shadowView.layer.insertSublayer(gradient, at: 1)
- view.frame = view.bounds
- viewWithShadow.addSubview(view)
- viewWithShadow.addSubview(shadowView)
- return viewWithShadow
- }
- func updateAnchorPointAndOffset(anchorPoint: CGPoint, view: UIView) {
- view.layer.anchorPoint = anchorPoint
- if horizontal {
- let xOffset = anchorPoint.x - 0.5
- view.frame = view.frame.offsetBy(dx: xOffset * view.frame.size.width, dy: 0)
- } else {
- let yOffset = anchorPoint.y - 0.5
- view.frame = view.frame.offsetBy(dx: 0, dy: yOffset * view.frame.size.height)
- }
- }
- func rotate(angle: Double) -> CATransform3D {
- let axesValues = valuesForAxe(initialValue: 0.0, reverseValue: 1.0)
- return CATransform3DMakeRotation(CGFloat(angle), axesValues.0, axesValues.1, 0.0)
- }
- }
- // MARK: - Animates
- private extension FlipAnimator {
- func animateFlipTransition(flippedSectionOfFromView: (UIView, UIView),
- flippedSectionOfToView: (UIView, UIView),
- completion: @escaping AnimatableCompletion) {
- UIView.animateKeyframes(withDuration: transitionDuration, delay: 0, options: .layoutSubviews, animations: {
- UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.5, animations: {
- flippedSectionOfFromView.0.layer.transform = self.rotate(angle: (self.reverse ? -.pi : .pi) / 2)
- flippedSectionOfFromView.1.alpha = 1.0
- })
- UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5, animations: {
- flippedSectionOfToView.0.layer.transform = self.rotate(angle: self.reverse ? 0.001 : -0.001)
- flippedSectionOfToView.1.alpha = 0.0
- })
- }) { _ in
- completion()
- }
- }
- }
- // MARK: - Helpers
- private extension FlipAnimator {
- func removeOtherViews(viewToKeep: UIView) {
- let containerView = viewToKeep.superview
- containerView?.subviews.forEach {
- if $0 != viewToKeep {
- $0.removeFromSuperview()
- }
- }
- }
- func valuesForAxe(initialValue: CGFloat, reverseValue: CGFloat) -> (CGFloat, CGFloat) {
- return horizontal ? (initialValue, reverseValue) : (reverseValue, initialValue)
- }
- }
|