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.

459 lines
16 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. extension CALayer {
  24. internal static var heroAddedAnimations: [(CALayer, String, CAAnimation)]? = {
  25. let swizzling: (AnyClass, Selector, Selector) -> Void = { forClass, originalSelector, swizzledSelector in
  26. if let originalMethod = class_getInstanceMethod(forClass, originalSelector), let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) {
  27. method_exchangeImplementations(originalMethod, swizzledMethod)
  28. }
  29. }
  30. let originalSelector = #selector(add(_:forKey:))
  31. let swizzledSelector = #selector(hero_add(anim:forKey:))
  32. swizzling(CALayer.self, originalSelector, swizzledSelector)
  33. return nil
  34. }()
  35. @objc dynamic func hero_add(anim: CAAnimation, forKey: String?) {
  36. if CALayer.heroAddedAnimations != nil {
  37. let copiedAnim = anim.copy() as! CAAnimation
  38. copiedAnim.delegate = nil // having delegate resulted some weird animation behavior
  39. CALayer.heroAddedAnimations!.append((self, forKey!, copiedAnim))
  40. hero_add(anim: anim, forKey: forKey)
  41. } else {
  42. hero_add(anim: anim, forKey: forKey)
  43. }
  44. }
  45. }
  46. internal class HeroCoreAnimationViewContext: HeroAnimatorViewContext {
  47. var state = [String: (Any?, Any?)]()
  48. var timingFunction: CAMediaTimingFunction = .standard
  49. var animations: [(CALayer, String, CAAnimation)] = []
  50. // computed
  51. var contentLayer: CALayer? {
  52. let firstLayer = snapshot.layer.sublayers?.get(0)
  53. if firstLayer?.bounds == snapshot.bounds {
  54. return firstLayer
  55. }
  56. return nil
  57. }
  58. var overlayLayer: CALayer?
  59. override class func canAnimate(view: UIView, state: HeroTargetState, appearing: Bool) -> Bool {
  60. return state.position != nil ||
  61. state.size != nil ||
  62. state.transform != nil ||
  63. state.cornerRadius != nil ||
  64. state.opacity != nil ||
  65. state.overlay != nil ||
  66. state.backgroundColor != nil ||
  67. state.borderColor != nil ||
  68. state.borderWidth != nil ||
  69. state.shadowOpacity != nil ||
  70. state.shadowRadius != nil ||
  71. state.shadowOffset != nil ||
  72. state.shadowColor != nil ||
  73. state.shadowPath != nil ||
  74. state.contentsRect != nil ||
  75. state.forceAnimate
  76. }
  77. func getOverlayLayer() -> CALayer {
  78. if overlayLayer == nil {
  79. overlayLayer = CALayer()
  80. overlayLayer!.frame = snapshot.bounds
  81. overlayLayer!.opacity = 0
  82. snapshot.layer.addSublayer(overlayLayer!)
  83. }
  84. return overlayLayer!
  85. }
  86. func overlayKeyFor(key: String) -> String? {
  87. if key.hasPrefix("overlay.") {
  88. var key = key
  89. key.removeSubrange(key.startIndex..<key.index(key.startIndex, offsetBy: 8))
  90. return key
  91. }
  92. return nil
  93. }
  94. func currentValue(key: String) -> Any? {
  95. if let key = overlayKeyFor(key: key) {
  96. return (overlayLayer?.presentation() ?? overlayLayer)?.value(forKeyPath: key)
  97. }
  98. if snapshot.layer.animationKeys()?.isEmpty != false {
  99. return snapshot.layer.value(forKeyPath: key)
  100. }
  101. return (snapshot.layer.presentation() ?? snapshot.layer).value(forKeyPath: key)
  102. }
  103. func getAnimation(key: String, beginTime: TimeInterval, duration: TimeInterval, fromValue: Any?, toValue: Any?, ignoreArc: Bool = false) -> CAPropertyAnimation {
  104. let key = overlayKeyFor(key: key) ?? key
  105. let anim: CAPropertyAnimation
  106. if !ignoreArc, key == "position", let arcIntensity = targetState.arc,
  107. let fromPos = (fromValue as? NSValue)?.cgPointValue,
  108. let toPos = (toValue as? NSValue)?.cgPointValue,
  109. abs(fromPos.x - toPos.x) >= 1, abs(fromPos.y - toPos.y) >= 1 {
  110. let kanim = CAKeyframeAnimation(keyPath: key)
  111. let path = CGMutablePath()
  112. let maxControl = fromPos.y > toPos.y ? CGPoint(x: toPos.x, y: fromPos.y) : CGPoint(x: fromPos.x, y: toPos.y)
  113. let minControl = (toPos - fromPos) / 2 + fromPos
  114. path.move(to: fromPos)
  115. path.addQuadCurve(to: toPos, control: minControl + (maxControl - minControl) * arcIntensity)
  116. kanim.values = [fromValue!, toValue!]
  117. kanim.path = path
  118. kanim.duration = duration
  119. kanim.timingFunctions = [timingFunction]
  120. anim = kanim
  121. } else if #available(iOS 9.0, *), key != "cornerRadius", let (stiffness, damping) = targetState.spring {
  122. let sanim = CASpringAnimation(keyPath: key)
  123. sanim.stiffness = stiffness
  124. sanim.damping = damping
  125. sanim.duration = sanim.settlingDuration
  126. sanim.fromValue = fromValue
  127. sanim.toValue = toValue
  128. anim = sanim
  129. } else {
  130. let banim = CABasicAnimation(keyPath: key)
  131. banim.duration = duration
  132. banim.fromValue = fromValue
  133. banim.toValue = toValue
  134. banim.timingFunction = timingFunction
  135. anim = banim
  136. }
  137. anim.fillMode = CAMediaTimingFillMode.both
  138. anim.isRemovedOnCompletion = false
  139. anim.beginTime = beginTime
  140. return anim
  141. }
  142. func setSize(view: UIView, newSize: CGSize) {
  143. let oldSize = view.bounds.size
  144. if targetState.snapshotType != .noSnapshot {
  145. if oldSize.width == 0 || oldSize.height == 0 || newSize.width == 0 || newSize.height == 0 {
  146. for subview in view.subviews {
  147. subview.center = newSize.center
  148. subview.bounds.size = newSize
  149. setSize(view: subview, newSize: newSize)
  150. }
  151. } else {
  152. let sizeRatio = oldSize / newSize
  153. for subview in view.subviews {
  154. let center = subview.center
  155. let size = subview.bounds.size
  156. subview.center = center / sizeRatio
  157. subview.bounds.size = size / sizeRatio
  158. setSize(view: subview, newSize: size / sizeRatio)
  159. }
  160. }
  161. view.bounds.size = newSize
  162. } else {
  163. view.bounds.size = newSize
  164. view.layoutSubviews()
  165. }
  166. }
  167. func uiViewBasedAnimate(duration: TimeInterval, delay: TimeInterval, _ animations: @escaping () -> Void) {
  168. CALayer.heroAddedAnimations = []
  169. if let (stiffness, damping) = targetState.spring {
  170. UIView.animate(withDuration: duration, delay: delay, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: [], animations: animations, completion: nil)
  171. let addedAnimations = CALayer.heroAddedAnimations!
  172. CALayer.heroAddedAnimations = nil
  173. for (layer, key, anim) in addedAnimations {
  174. layer.removeAnimation(forKey: key)
  175. if #available(iOS 9.0, *), let anim = anim as? CASpringAnimation {
  176. anim.stiffness = stiffness
  177. anim.damping = damping
  178. self.addAnimation(anim, for: key, to: layer)
  179. } else {
  180. self.addAnimation(anim, for: key, to: layer)
  181. }
  182. }
  183. } else {
  184. CATransaction.begin()
  185. CATransaction.setAnimationTimingFunction(timingFunction)
  186. UIView.animate(withDuration: duration, delay: delay, options: [], animations: animations, completion: nil)
  187. let addedAnimations = CALayer.heroAddedAnimations!
  188. CALayer.heroAddedAnimations = nil
  189. for (layer, key, anim) in addedAnimations {
  190. layer.removeAnimation(forKey: key)
  191. self.addAnimation(anim, for: key, to: layer)
  192. }
  193. CATransaction.commit()
  194. }
  195. }
  196. func addAnimation(_ animation: CAAnimation, for key: String, to layer: CALayer) {
  197. let heroAnimationKey = "hero.\(key)"
  198. animations.append((layer, heroAnimationKey, animation))
  199. layer.add(animation, forKey: heroAnimationKey)
  200. }
  201. // return the completion duration of the animation (duration + initial delay, not counting the beginTime)
  202. func animate(key: String, beginTime: TimeInterval, duration: TimeInterval, fromValue: Any?, toValue: Any?) -> TimeInterval {
  203. let anim = getAnimation(key: key, beginTime: beginTime, duration: duration, fromValue: fromValue, toValue: toValue)
  204. if let overlayKey = overlayKeyFor(key: key) {
  205. addAnimation(anim, for: overlayKey, to: getOverlayLayer())
  206. } else {
  207. switch key {
  208. case "cornerRadius", "contentsRect", "contentsScale":
  209. addAnimation(anim, for: key, to: snapshot.layer)
  210. if let contentLayer = contentLayer {
  211. addAnimation(anim.copy() as! CAAnimation, for: key, to: contentLayer)
  212. }
  213. if let overlayLayer = overlayLayer {
  214. addAnimation(anim.copy() as! CAAnimation, for: key, to: overlayLayer)
  215. }
  216. case "bounds.size":
  217. guard let fromSize = (fromValue as? NSValue)?.cgSizeValue, let toSize = (toValue as? NSValue)?.cgSizeValue else {
  218. addAnimation(anim, for: key, to: snapshot.layer)
  219. break
  220. }
  221. setSize(view: snapshot, newSize: fromSize)
  222. uiViewBasedAnimate(duration: anim.duration, delay: beginTime - currentTime) {
  223. self.setSize(view: self.snapshot, newSize: toSize)
  224. }
  225. default:
  226. addAnimation(anim, for: key, to: snapshot.layer)
  227. }
  228. }
  229. return anim.duration + anim.beginTime - beginTime
  230. }
  231. /**
  232. - Returns: a CALayer [keyPath:value] map for animation
  233. */
  234. func viewState(targetState: HeroTargetState) -> [String: Any?] {
  235. var targetState = targetState
  236. var rtn = [String: Any?]()
  237. if let size = targetState.size {
  238. if targetState.useScaleBasedSizeChange ?? self.targetState.useScaleBasedSizeChange ?? false {
  239. let currentSize = snapshot.bounds.size
  240. targetState.append(.scale(x:size.width / currentSize.width,
  241. y:size.height / currentSize.height))
  242. } else {
  243. rtn["bounds.size"] = NSValue(cgSize: size)
  244. }
  245. }
  246. if let position = targetState.position {
  247. rtn["position"] = NSValue(cgPoint: position)
  248. }
  249. if let opacity = targetState.opacity, !(snapshot is UIVisualEffectView) {
  250. rtn["opacity"] = NSNumber(value: opacity)
  251. }
  252. if let cornerRadius = targetState.cornerRadius {
  253. rtn["cornerRadius"] = NSNumber(value: cornerRadius.native)
  254. }
  255. if let backgroundColor = targetState.backgroundColor {
  256. rtn["backgroundColor"] = backgroundColor
  257. }
  258. if let zPosition = targetState.zPosition {
  259. rtn["zPosition"] = NSNumber(value: zPosition.native)
  260. }
  261. if let borderWidth = targetState.borderWidth {
  262. rtn["borderWidth"] = NSNumber(value: borderWidth.native)
  263. }
  264. if let borderColor = targetState.borderColor {
  265. rtn["borderColor"] = borderColor
  266. }
  267. if let masksToBounds = targetState.masksToBounds {
  268. rtn["masksToBounds"] = masksToBounds
  269. }
  270. if targetState.displayShadow {
  271. if let shadowColor = targetState.shadowColor {
  272. rtn["shadowColor"] = shadowColor
  273. }
  274. if let shadowRadius = targetState.shadowRadius {
  275. rtn["shadowRadius"] = NSNumber(value: shadowRadius.native)
  276. }
  277. if let shadowOpacity = targetState.shadowOpacity {
  278. rtn["shadowOpacity"] = NSNumber(value: shadowOpacity)
  279. }
  280. if let shadowPath = targetState.shadowPath {
  281. rtn["shadowPath"] = shadowPath
  282. }
  283. if let shadowOffset = targetState.shadowOffset {
  284. rtn["shadowOffset"] = NSValue(cgSize: shadowOffset)
  285. }
  286. }
  287. if let contentsRect = targetState.contentsRect {
  288. rtn["contentsRect"] = NSValue(cgRect: contentsRect)
  289. }
  290. if let contentsScale = targetState.contentsScale {
  291. rtn["contentsScale"] = NSNumber(value: contentsScale.native)
  292. }
  293. if let transform = targetState.transform {
  294. rtn["transform"] = NSValue(caTransform3D: transform)
  295. }
  296. if let (color, opacity) = targetState.overlay {
  297. rtn["overlay.backgroundColor"] = color
  298. rtn["overlay.opacity"] = NSNumber(value: opacity.native)
  299. }
  300. return rtn
  301. }
  302. override func apply(state: HeroTargetState) {
  303. let targetState = viewState(targetState: state)
  304. for (key, targetValue) in targetState {
  305. if self.state[key] == nil {
  306. let current = currentValue(key: key)
  307. self.state[key] = (current, current)
  308. }
  309. let oldAnimations = animations
  310. animations = []
  311. _ = animate(key: key, beginTime: 0, duration: 100, fromValue: targetValue, toValue: targetValue)
  312. animations = oldAnimations
  313. }
  314. }
  315. override func changeTarget(state: HeroTargetState, isDestination: Bool) {
  316. let targetState = viewState(targetState: state)
  317. for (key, targetValue) in targetState {
  318. let from: Any?, to: Any?
  319. if let data = self.state[key] {
  320. from = data.0
  321. to = data.1
  322. } else {
  323. let data = currentValue(key: key)
  324. from = data
  325. to = data
  326. }
  327. if isDestination {
  328. self.state[key] = (from, targetValue)
  329. } else {
  330. self.state[key] = (targetValue, to)
  331. }
  332. }
  333. }
  334. override func resume(timePassed: TimeInterval, reverse: Bool) -> TimeInterval {
  335. for (key, (fromValue, toValue)) in state {
  336. let realToValue = !reverse ? toValue : fromValue
  337. let realFromValue = currentValue(key: key)
  338. state[key] = (realFromValue, realToValue)
  339. }
  340. if reverse {
  341. if timePassed > targetState.delay + duration {
  342. let backDelay = timePassed - (targetState.delay + duration)
  343. return animate(delay: backDelay, duration: duration)
  344. } else if timePassed > targetState.delay {
  345. return animate(delay: 0, duration: duration - (timePassed - targetState.delay))
  346. } else {
  347. return 0
  348. }
  349. } else {
  350. if timePassed <= targetState.delay {
  351. return animate(delay: targetState.delay - timePassed, duration: duration)
  352. } else if timePassed <= targetState.delay + duration {
  353. let timePassedDelay = timePassed - targetState.delay
  354. return animate(delay: 0, duration: duration - timePassedDelay)
  355. } else {
  356. return 0
  357. }
  358. }
  359. }
  360. func animate(delay: TimeInterval, duration: TimeInterval) -> TimeInterval {
  361. for (layer, key, _) in animations {
  362. layer.removeAnimation(forKey: key)
  363. }
  364. if let tf = targetState.timingFunction {
  365. timingFunction = tf
  366. }
  367. var timeUntilStop: TimeInterval = duration
  368. animations = []
  369. for (key, (fromValue, toValue)) in state {
  370. let neededTime = animate(key: key, beginTime: currentTime + delay, duration: duration, fromValue: fromValue, toValue: toValue)
  371. timeUntilStop = max(timeUntilStop, neededTime)
  372. }
  373. return timeUntilStop + delay
  374. }
  375. override func seek(timePassed: TimeInterval) {
  376. let timeOffset = timePassed - targetState.delay
  377. for (layer, key, anim) in animations {
  378. anim.speed = 0
  379. anim.timeOffset = timeOffset.clamp(0, anim.duration - 0.001)
  380. layer.removeAnimation(forKey: key)
  381. layer.add(anim, forKey: key)
  382. }
  383. }
  384. override func clean() {
  385. super.clean()
  386. overlayLayer = nil
  387. }
  388. override func startAnimations() -> TimeInterval {
  389. if let beginStateModifiers = targetState.beginState {
  390. let beginState = HeroTargetState(modifiers: beginStateModifiers)
  391. let appeared = viewState(targetState: beginState)
  392. for (key, value) in appeared {
  393. snapshot.layer.setValue(value, forKeyPath: key)
  394. }
  395. if let (color, opacity) = beginState.overlay {
  396. let overlay = getOverlayLayer()
  397. overlay.backgroundColor = color
  398. overlay.opacity = Float(opacity)
  399. }
  400. }
  401. let disappeared = viewState(targetState: targetState)
  402. for (key, disappearedState) in disappeared {
  403. let appearingState = currentValue(key: key)
  404. let toValue = appearing ? appearingState : disappearedState
  405. let fromValue = !appearing ? appearingState : disappearedState
  406. state[key] = (fromValue, toValue)
  407. }
  408. return animate(delay: targetState.delay, duration: duration)
  409. }
  410. }