You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

139 lines
5.4 KiB

  1. // The MIT License (MIT)
  2. //
  3. // Copyright (c) 2016 Luke Zhao <me@lkzhao.com>
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining a copy
  6. // of this software and associated documentation files (the "Software"), to deal
  7. // in the Software without restriction, including without limitation the rights
  8. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. // copies of the Software, and to permit persons to whom the Software is
  10. // furnished to do so, subject to the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be included in
  13. // all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. // THE SOFTWARE.
  22. import UIKit
  23. internal extension UIView {
  24. func optimizedDurationTo(position: CGPoint?, size: CGSize?, transform: CATransform3D?) -> TimeInterval {
  25. let fromPos = (layer.presentation() ?? layer).position
  26. let toPos = position ?? fromPos
  27. let fromSize = (layer.presentation() ?? layer).bounds.size
  28. let toSize = size ?? fromSize
  29. let fromTransform = (layer.presentation() ?? layer).transform
  30. let toTransform = transform ?? fromTransform
  31. let realFromPos = CGPoint.zero.transform(fromTransform) + fromPos
  32. let realToPos = CGPoint.zero.transform(toTransform) + toPos
  33. let realFromSize = fromSize.transform(fromTransform)
  34. let realToSize = toSize.transform(toTransform)
  35. let movePoints = (realFromPos.distance(realToPos) + realFromSize.point.distance(realToSize.point))
  36. // duration is 0.2 @ 0 to 0.375 @ 500
  37. let duration = 0.208 + Double(movePoints.clamp(0, 500)) / 3000
  38. return duration
  39. }
  40. }
  41. internal class HeroDefaultAnimator<ViewContext: HeroAnimatorViewContext>: HeroAnimator {
  42. weak public var hero: HeroTransition!
  43. public var context: HeroContext! {
  44. return hero?.context
  45. }
  46. var viewContexts: [UIView: ViewContext] = [:]
  47. public func seekTo(timePassed: TimeInterval) {
  48. for viewContext in viewContexts.values {
  49. viewContext.seek(timePassed: timePassed)
  50. }
  51. }
  52. public func resume(timePassed: TimeInterval, reverse: Bool) -> TimeInterval {
  53. var duration: TimeInterval = 0
  54. for (_, viewContext) in viewContexts {
  55. if viewContext.targetState.duration == nil {
  56. viewContext.duration = max(viewContext.duration,
  57. calculateOptimizedDuration(snapshot: viewContext.snapshot,
  58. targetState: viewContext.targetState) + timePassed)
  59. }
  60. let timeUntilStopped = viewContext.resume(timePassed: timePassed, reverse: reverse)
  61. duration = max(duration, timeUntilStopped)
  62. }
  63. return duration
  64. }
  65. public func apply(state: HeroTargetState, to view: UIView) {
  66. if let context = viewContexts[view] {
  67. context.apply(state: state)
  68. }
  69. }
  70. public func changeTarget(state: HeroTargetState, isDestination: Bool, to view: UIView) {
  71. if let context = viewContexts[view] {
  72. context.changeTarget(state: state, isDestination: isDestination)
  73. }
  74. }
  75. public func canAnimate(view: UIView, appearing: Bool) -> Bool {
  76. guard let state = context[view] else { return false }
  77. return ViewContext.canAnimate(view: view, state: state, appearing: appearing)
  78. }
  79. public func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval {
  80. var maxDuration: TimeInterval = 0
  81. for v in fromViews { createViewContext(view: v, appearing: false) }
  82. for v in toViews { createViewContext(view: v, appearing: true) }
  83. for viewContext in viewContexts.values {
  84. if let duration = viewContext.targetState.duration, duration != .infinity {
  85. viewContext.duration = duration
  86. maxDuration = max(maxDuration, duration)
  87. } else {
  88. let duration = calculateOptimizedDuration(snapshot: viewContext.snapshot, targetState: viewContext.targetState)
  89. if viewContext.targetState.duration == nil {
  90. viewContext.duration = duration
  91. }
  92. maxDuration = max(maxDuration, duration)
  93. }
  94. }
  95. for viewContext in viewContexts.values {
  96. if viewContext.targetState.duration == .infinity {
  97. viewContext.duration = maxDuration
  98. }
  99. let timeUntilStopped = viewContext.startAnimations()
  100. maxDuration = max(maxDuration, timeUntilStopped)
  101. }
  102. return maxDuration
  103. }
  104. func calculateOptimizedDuration(snapshot: UIView, targetState: HeroTargetState) -> TimeInterval {
  105. return snapshot.optimizedDurationTo(position: targetState.position,
  106. size: targetState.size,
  107. transform: targetState.transform)
  108. }
  109. func createViewContext(view: UIView, appearing: Bool) {
  110. let snapshot = context.snapshotView(for: view)
  111. let viewContext = ViewContext(animator: self, snapshot: snapshot, targetState: context[view]!, appearing: appearing)
  112. viewContexts[view] = viewContext
  113. }
  114. public func clean() {
  115. for vc in viewContexts.values {
  116. vc.clean()
  117. }
  118. viewContexts.removeAll()
  119. }
  120. }