FoldAnimator.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. //
  2. // Created by Tom Baranes on 09/04/16.
  3. // Copyright © 2016 IBAnimatable. All rights reserved.
  4. //
  5. import UIKit
  6. public class FoldAnimator: 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. fileprivate var folds: Int
  15. // MARK: - Private fold transition
  16. fileprivate var transform: CATransform3D = CATransform3DIdentity
  17. fileprivate var reverse: Bool = false
  18. fileprivate var horizontal: Bool = false
  19. fileprivate var size: CGSize = .zero
  20. fileprivate var foldSize: CGFloat = 0.0
  21. fileprivate var width: CGFloat {
  22. return horizontal ? size.width : size.height
  23. }
  24. fileprivate var height: CGFloat {
  25. return horizontal ? size.height : size.width
  26. }
  27. // MARK: - Life cycle
  28. public init(from direction: TransitionAnimationType.Direction, folds: Int?, duration: Duration) {
  29. fromDirection = direction
  30. horizontal = fromDirection.isHorizontal
  31. transitionDuration = duration
  32. self.folds = folds ?? 2
  33. transitionAnimationType = .fold(from: direction, folds: folds)
  34. reverseAnimationType = .fold(from: direction.opposite, folds: folds)
  35. interactiveGestureType = .pan(from: direction.opposingGesture)
  36. reverse = direction == .right || direction == .bottom
  37. super.init()
  38. }
  39. }
  40. extension FoldAnimator: UIViewControllerAnimatedTransitioning {
  41. public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
  42. return retrieveTransitionDuration(transitionContext: transitionContext)
  43. }
  44. public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
  45. let (tempfromView, tempToView, tempContainerView) = retrieveViews(transitionContext: transitionContext)
  46. guard let fromView = tempfromView, let toView = tempToView, let containerView = tempContainerView else {
  47. transitionContext.completeTransition(true)
  48. return
  49. }
  50. toView.frame = toView.frame.offsetBy(dx: toView.frame.size.width, dy: 0)
  51. containerView.addSubview(toView)
  52. transform.m34 = -0.005
  53. containerView.layer.sublayerTransform = transform
  54. size = toView.frame.size
  55. foldSize = width * 0.5 / CGFloat(folds)
  56. let viewFolds = makeSnapshots(toView: toView, fromView: fromView, containerView: containerView)
  57. animateFoldTransition(fromView: fromView, toViewFolds: viewFolds[0], fromViewFolds: viewFolds[1], completion: {
  58. if !transitionContext.transitionWasCancelled {
  59. toView.frame = containerView.bounds
  60. fromView.frame = containerView.bounds
  61. } else {
  62. fromView.frame = containerView.bounds
  63. }
  64. transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
  65. }
  66. )
  67. }
  68. }
  69. // MARK: - Setup fold transition
  70. private extension FoldAnimator {
  71. func makeSnapshots(toView: UIView, fromView: UIView, containerView: UIView) -> [[UIView]] {
  72. var fromViewFolds = [UIView]()
  73. var toViewFolds = [UIView]()
  74. for i in 0..<folds {
  75. let offset = CGFloat(i) * foldSize * 2
  76. let leftFromViewFold = makeSnapshot(from: fromView, afterUpdates: false, offset: offset, left: true)
  77. var axesValues = valuesForAxe(initialValue: offset, reverseValue: height / 2)
  78. leftFromViewFold.layer.position = CGPoint(x: axesValues.0, y: axesValues.1)
  79. fromViewFolds.append(leftFromViewFold)
  80. leftFromViewFold.subviews[1].alpha = 0.0
  81. let rightFromViewFold = makeSnapshot(from: fromView, afterUpdates: false, offset: offset + foldSize, left: false)
  82. axesValues = valuesForAxe(initialValue: offset + foldSize * 2, reverseValue: height / 2)
  83. rightFromViewFold.layer.position = CGPoint(x: axesValues.0, y: axesValues.1)
  84. fromViewFolds.append(rightFromViewFold)
  85. rightFromViewFold.subviews[1].alpha = 0.0
  86. let leftToViewFold = makeSnapshot(from: toView, afterUpdates: true, offset: offset, left: true)
  87. axesValues = valuesForAxe(initialValue: reverse ? width : 0.0, reverseValue: height / 2)
  88. leftToViewFold.layer.position = CGPoint(x: axesValues.0, y: axesValues.1)
  89. axesValues = valuesForAxe(initialValue: 0.0, reverseValue: 1.0)
  90. leftToViewFold.layer.transform = CATransform3DMakeRotation(.pi / 2, axesValues.0, axesValues.1, 0.0)
  91. toViewFolds.append(leftToViewFold)
  92. let rightToViewFold = makeSnapshot(from: toView, afterUpdates: true, offset: offset + foldSize, left: false)
  93. axesValues = valuesForAxe(initialValue: reverse ? width : 0.0, reverseValue: height / 2)
  94. rightToViewFold.layer.position = CGPoint(x: axesValues.0, y: axesValues.1)
  95. axesValues = valuesForAxe(initialValue: 0.0, reverseValue: 1.0)
  96. rightToViewFold.layer.transform = CATransform3DMakeRotation(-.pi / 2, axesValues.0, axesValues.1, 0.0)
  97. toViewFolds.append(rightToViewFold)
  98. }
  99. return [toViewFolds, fromViewFolds]
  100. }
  101. func makeSnapshot(from view: UIView, afterUpdates: Bool, offset: CGFloat, left: Bool) -> UIView {
  102. let containerView = view.superview
  103. var snapshotView: UIView
  104. var axesValues = valuesForAxe(initialValue: offset, reverseValue: 0.0)
  105. let axesValues2 = valuesForAxe(initialValue: foldSize, reverseValue: height)
  106. let snapshotRegion = CGRect(x: axesValues.0, y: axesValues.1, width: axesValues2.0, height: axesValues2.1)
  107. if !afterUpdates {
  108. snapshotView = view.resizableSnapshotView(from: snapshotRegion, afterScreenUpdates: afterUpdates, withCapInsets: .zero)!
  109. } else {
  110. axesValues = valuesForAxe(initialValue: foldSize, reverseValue: height)
  111. snapshotView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: axesValues.0, height: axesValues.1))
  112. snapshotView.backgroundColor = view.backgroundColor
  113. let subSnapshotView = view.resizableSnapshotView(from: snapshotRegion, afterScreenUpdates: afterUpdates, withCapInsets: .zero)
  114. snapshotView.addSubview(subSnapshotView!)
  115. }
  116. let snapshotWithShadowView = addShadow(to: snapshotView, reverse: left)
  117. containerView?.addSubview(snapshotWithShadowView)
  118. axesValues = valuesForAxe(initialValue: left ? 0.0 : 1.0, reverseValue: 0.5)
  119. snapshotWithShadowView.layer.anchorPoint = CGPoint(x: axesValues.0, y: axesValues.1)
  120. return snapshotWithShadowView
  121. }
  122. func addShadow(to view: UIView, reverse: Bool) -> UIView {
  123. let viewWithShadow = UIView(frame: view.frame)
  124. let shadowView = UIView(frame: viewWithShadow.bounds)
  125. let gradient = CAGradientLayer()
  126. gradient.frame = shadowView.bounds
  127. // swiftlint:disable:next object_literal
  128. gradient.colors = [UIColor(white: 0.0, alpha: 0.0).cgColor, UIColor(white: 0.0, alpha: 1.0).cgColor]
  129. if horizontal {
  130. var axesValues = valuesForAxe(initialValue: reverse ? 0.0 : 1.0, reverseValue: reverse ? 0.2 : 0.0)
  131. gradient.startPoint = CGPoint(x: axesValues.0, y: axesValues.1)
  132. axesValues = valuesForAxe(initialValue: reverse ? 1.0 : 0.0, reverseValue: reverse ? 0.0 : 1.0)
  133. gradient.endPoint = CGPoint(x: axesValues.0, y: axesValues.1)
  134. } else {
  135. var axesValues = valuesForAxe(initialValue: reverse ? 0.2 : 0.0, reverseValue: reverse ? 0.0 : 1.0)
  136. gradient.startPoint = CGPoint(x: axesValues.0, y: axesValues.1)
  137. axesValues = valuesForAxe(initialValue: reverse ? 0.0 : 1.0, reverseValue: reverse ? 1.0 : 0.0)
  138. gradient.endPoint = CGPoint(x: axesValues.0, y: axesValues.1)
  139. }
  140. shadowView.layer.insertSublayer(gradient, at: 1)
  141. view.frame = view.bounds
  142. viewWithShadow.addSubview(view)
  143. viewWithShadow.addSubview(shadowView)
  144. return viewWithShadow
  145. }
  146. }
  147. // MARK: - Animates
  148. private extension FoldAnimator {
  149. func animateFoldTransition(fromView view: UIView, toViewFolds: [UIView], fromViewFolds: [UIView], completion: @escaping AnimatableCompletion) {
  150. view.frame = view.frame.offsetBy(dx: view.frame.width, dy: 0)
  151. UIView.animate(withDuration: transitionDuration, animations: {
  152. for i in 0..<self.folds {
  153. let offset = CGFloat(i) * self.foldSize * 2
  154. let leftFromView = fromViewFolds[i * 2]
  155. var axesValues = self.valuesForAxe(initialValue: self.reverse ? 0.0 : self.width, reverseValue: self.height / 2)
  156. leftFromView.layer.position = CGPoint(x: axesValues.0, y: axesValues.1)
  157. axesValues = self.valuesForAxe(initialValue: 0.0, reverseValue: 1.0)
  158. leftFromView.layer.transform = CATransform3DRotate(self.transform, .pi / 2, axesValues.0, axesValues.1, 0)
  159. leftFromView.subviews[1].alpha = 1.0
  160. let rightFromView = fromViewFolds[i * 2 + 1]
  161. axesValues = self.valuesForAxe(initialValue: self.reverse ? 0.0 : self.width, reverseValue: self.height / 2)
  162. rightFromView.layer.position = CGPoint(x: axesValues.0, y: axesValues.1)
  163. axesValues = self.valuesForAxe(initialValue: 0.0, reverseValue: 1.0)
  164. rightFromView.layer.transform = CATransform3DRotate(self.transform, -.pi / 2, axesValues.0, axesValues.1, 0)
  165. rightFromView.subviews[1].alpha = 1.0
  166. let leftToView = toViewFolds[i * 2]
  167. axesValues = self.valuesForAxe(initialValue: offset, reverseValue: self.height / 2)
  168. leftToView.layer.position = CGPoint(x: axesValues.0, y: axesValues.1)
  169. leftToView.layer.transform = CATransform3DIdentity
  170. leftToView.subviews[1].alpha = 0.0
  171. let rightToView = toViewFolds[i * 2 + 1]
  172. axesValues = self.valuesForAxe(initialValue: offset + self.foldSize * 2, reverseValue: self.height / 2)
  173. rightToView.layer.position = CGPoint(x: axesValues.0, y: axesValues.1)
  174. rightToView.layer.transform = CATransform3DIdentity
  175. rightToView.subviews[1].alpha = 0.0
  176. }
  177. },
  178. completion: { _ in
  179. toViewFolds.forEach {
  180. $0.removeFromSuperview()
  181. }
  182. fromViewFolds.forEach {
  183. $0.removeFromSuperview()
  184. }
  185. completion()
  186. })
  187. }
  188. }
  189. // MARK: - Helpers
  190. private extension FoldAnimator {
  191. func valuesForAxe(initialValue: CGFloat, reverseValue: CGFloat) -> (CGFloat, CGFloat) {
  192. return horizontal ? (initialValue, reverseValue) : (reverseValue, initialValue)
  193. }
  194. }