FlipAnimator.swift 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. //
  2. // Created by Tom Baranes on 05/05/16.
  3. // Copyright © 2016 IBAnimatable. All rights reserved.
  4. //
  5. import UIKit
  6. public class FlipAnimator: NSObject, AnimatedTransitioning {
  7. // MARK: - AnimatorProtocol
  8. public var transitionAnimationType: TransitionAnimationType
  9. public var transitionDuration: Duration = defaultTransitionDuration
  10. public var reverseAnimationType: TransitionAnimationType?
  11. public var interactiveGestureType: InteractiveGestureType?
  12. // MARK: - Private params
  13. fileprivate var fromDirection: TransitionAnimationType.Direction
  14. // MARK: - Private fold transition
  15. fileprivate var transform: CATransform3D = CATransform3DIdentity
  16. fileprivate var reverse: Bool = false
  17. fileprivate var horizontal: Bool = false
  18. // MARK: - Life cycle
  19. public init(from direction: TransitionAnimationType.Direction, duration: Duration) {
  20. fromDirection = direction
  21. transitionDuration = duration
  22. horizontal = fromDirection.isHorizontal
  23. transitionAnimationType = .flip(from: direction)
  24. reverseAnimationType = .flip(from: direction.opposite)
  25. interactiveGestureType = .pan(from: direction.opposingGesture)
  26. reverse = direction == .right
  27. super.init()
  28. }
  29. }
  30. extension FlipAnimator: UIViewControllerAnimatedTransitioning {
  31. public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
  32. return retrieveTransitionDuration(transitionContext: transitionContext)
  33. }
  34. public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
  35. let (tempfromView, tempToView, tempContainerView) = retrieveViews(transitionContext: transitionContext)
  36. guard let fromView = tempfromView, let toView = tempToView, let containerView = tempContainerView else {
  37. transitionContext.completeTransition(true)
  38. return
  39. }
  40. containerView.insertSubview(toView, at: 0)
  41. transform.m34 = -0.002
  42. containerView.layer.sublayerTransform = transform
  43. toView.frame = fromView.frame
  44. let flipViews = makeSnapshots(toView: toView, fromView: fromView, containerView: containerView)
  45. animateFlipTransition(flippedSectionOfFromView: flipViews.0, flippedSectionOfToView: flipViews.1) {
  46. if transitionContext.transitionWasCancelled {
  47. self.removeOtherViews(viewToKeep: fromView)
  48. } else {
  49. self.removeOtherViews(viewToKeep: toView)
  50. }
  51. transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
  52. }
  53. }
  54. }
  55. // MARK: - Setup flip transition
  56. private extension FlipAnimator {
  57. func makeSnapshots(toView: UIView, fromView: UIView, containerView: UIView) -> ((UIView, UIView), (UIView, UIView)) {
  58. let toViewSnapshots = makeSnapshot(from: toView, afterUpdates: true)
  59. var flippedSectionOfToView = toViewSnapshots[reverse ? 0 : 1]
  60. let fromViewSnapshots = makeSnapshot(from: fromView, afterUpdates: false)
  61. var flippedSectionOfFromView = fromViewSnapshots[reverse ? 1 : 0]
  62. flippedSectionOfFromView = addShadow(to: flippedSectionOfFromView, reverse: !reverse)
  63. let flippedSectionOfFromViewShadow = flippedSectionOfFromView.subviews[1]
  64. flippedSectionOfFromViewShadow.alpha = 0.0
  65. flippedSectionOfToView = addShadow(to: flippedSectionOfToView, reverse: reverse)
  66. let flippedSectionOfToViewShadow = flippedSectionOfToView.subviews[1]
  67. flippedSectionOfToViewShadow.alpha = 1.0
  68. var axesValues = valuesForAxe(initialValue: reverse ? 0.0 : 1.0, reverseValue: 0.5)
  69. updateAnchorPointAndOffset(anchorPoint: CGPoint(x: axesValues.0, y: axesValues.1), view: flippedSectionOfFromView)
  70. axesValues = valuesForAxe(initialValue: reverse ? 1.0 : 0.0, reverseValue: 0.5)
  71. updateAnchorPointAndOffset(anchorPoint: CGPoint(x: axesValues.0, y: axesValues.1), view: flippedSectionOfToView)
  72. flippedSectionOfToView.layer.transform = rotate(angle: (reverse ? .pi : -.pi) / 2)
  73. return ((flippedSectionOfFromView, flippedSectionOfFromViewShadow), (flippedSectionOfToView, flippedSectionOfToViewShadow))
  74. }
  75. func makeSnapshot(from view: UIView, afterUpdates: Bool) -> [UIView] {
  76. let containerView = view.superview
  77. let width = valuesForAxe(initialValue: view.frame.size.width / 2, reverseValue: view.frame.size.width)
  78. let height = valuesForAxe(initialValue: view.frame.size.height, reverseValue: view.frame.size.height / 2)
  79. var snapshotRegion = CGRect(x: 0, y: 0, width: width.0, height: height.0)
  80. let leftHandView = view.resizableSnapshotView(from: snapshotRegion, afterScreenUpdates: afterUpdates, withCapInsets: .zero)
  81. leftHandView?.frame = snapshotRegion
  82. containerView?.addSubview(leftHandView!)
  83. let x = valuesForAxe(initialValue: view.frame.size.width / 2, reverseValue: 0)
  84. let y = valuesForAxe(initialValue: 0, reverseValue: view.frame.size.height / 2)
  85. snapshotRegion = CGRect(x: x.0, y: y.0, width: width.0, height: height.0)
  86. let rightHandView = view.resizableSnapshotView(from: snapshotRegion, afterScreenUpdates: afterUpdates, withCapInsets: .zero)
  87. rightHandView?.frame = snapshotRegion
  88. containerView?.addSubview(rightHandView!)
  89. containerView?.sendSubviewToBack(view)
  90. return [leftHandView!, rightHandView!]
  91. }
  92. func addShadow(to view: UIView, reverse: Bool) -> UIView {
  93. let containerView = view.superview
  94. let viewWithShadow = UIView(frame: view.frame)
  95. containerView?.insertSubview(viewWithShadow, aboveSubview: view)
  96. view.removeFromSuperview()
  97. let shadowView = UIView(frame: viewWithShadow.bounds)
  98. let gradient = CAGradientLayer()
  99. gradient.frame = shadowView.bounds
  100. // swiftlint:disable:next object_literal
  101. gradient.colors = [UIColor(white: 0.0, alpha: 0.0), UIColor(white: 0.0, alpha: 0.5)]
  102. if horizontal {
  103. var axesValues = valuesForAxe(initialValue: reverse ? 0.0 : 1.0, reverseValue: reverse ? 0.2 : 0.0)
  104. gradient.startPoint = CGPoint(x: axesValues.0, y: axesValues.1)
  105. axesValues = valuesForAxe(initialValue: reverse ? 1.0 : 0.0, reverseValue: reverse ? 0.0 : 1.0)
  106. gradient.endPoint = CGPoint(x: axesValues.0, y: axesValues.1)
  107. } else {
  108. var axesValues = valuesForAxe(initialValue: reverse ? 0.2 : 0.0, reverseValue: reverse ? 0.0 : 1.0)
  109. gradient.startPoint = CGPoint(x: axesValues.0, y: axesValues.1)
  110. axesValues = valuesForAxe(initialValue: reverse ? 0.0 : 1.0, reverseValue: reverse ? 1.0 : 0.0)
  111. gradient.endPoint = CGPoint(x: axesValues.0, y: axesValues.1)
  112. }
  113. shadowView.layer.insertSublayer(gradient, at: 1)
  114. view.frame = view.bounds
  115. viewWithShadow.addSubview(view)
  116. viewWithShadow.addSubview(shadowView)
  117. return viewWithShadow
  118. }
  119. func updateAnchorPointAndOffset(anchorPoint: CGPoint, view: UIView) {
  120. view.layer.anchorPoint = anchorPoint
  121. if horizontal {
  122. let xOffset = anchorPoint.x - 0.5
  123. view.frame = view.frame.offsetBy(dx: xOffset * view.frame.size.width, dy: 0)
  124. } else {
  125. let yOffset = anchorPoint.y - 0.5
  126. view.frame = view.frame.offsetBy(dx: 0, dy: yOffset * view.frame.size.height)
  127. }
  128. }
  129. func rotate(angle: Double) -> CATransform3D {
  130. let axesValues = valuesForAxe(initialValue: 0.0, reverseValue: 1.0)
  131. return CATransform3DMakeRotation(CGFloat(angle), axesValues.0, axesValues.1, 0.0)
  132. }
  133. }
  134. // MARK: - Animates
  135. private extension FlipAnimator {
  136. func animateFlipTransition(flippedSectionOfFromView: (UIView, UIView),
  137. flippedSectionOfToView: (UIView, UIView),
  138. completion: @escaping AnimatableCompletion) {
  139. UIView.animateKeyframes(withDuration: transitionDuration, delay: 0, options: .layoutSubviews, animations: {
  140. UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.5, animations: {
  141. flippedSectionOfFromView.0.layer.transform = self.rotate(angle: (self.reverse ? -.pi : .pi) / 2)
  142. flippedSectionOfFromView.1.alpha = 1.0
  143. })
  144. UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5, animations: {
  145. flippedSectionOfToView.0.layer.transform = self.rotate(angle: self.reverse ? 0.001 : -0.001)
  146. flippedSectionOfToView.1.alpha = 0.0
  147. })
  148. }) { _ in
  149. completion()
  150. }
  151. }
  152. }
  153. // MARK: - Helpers
  154. private extension FlipAnimator {
  155. func removeOtherViews(viewToKeep: UIView) {
  156. let containerView = viewToKeep.superview
  157. containerView?.subviews.forEach {
  158. if $0 != viewToKeep {
  159. $0.removeFromSuperview()
  160. }
  161. }
  162. }
  163. func valuesForAxe(initialValue: CGFloat, reverseValue: CGFloat) -> (CGFloat, CGFloat) {
  164. return horizontal ? (initialValue, reverseValue) : (reverseValue, initialValue)
  165. }
  166. }