Animatable.swift 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877
  1. //
  2. // Created by Jake Lin on 11/19/15.
  3. // Copyright © 2015 IBAnimatable. All rights reserved.
  4. //
  5. import UIKit
  6. // swiftlint:disable file_length
  7. private typealias AnimationValues = (x: CGFloat, y: CGFloat, scaleX: CGFloat, scaleY: CGFloat)
  8. public protocol Animatable: class {
  9. /**
  10. `AnimationType` enum
  11. */
  12. var animationType: AnimationType { get set }
  13. /**
  14. Auto run flag, if `true` it will automatically start animation when `layoutSubviews`. Default should be `true`
  15. */
  16. var autoRun: Bool { get set }
  17. /**
  18. Animation duration (in seconds)
  19. */
  20. var duration: TimeInterval { get set }
  21. /**
  22. Animation delay (in seconds, default value should be 0)
  23. */
  24. var delay: TimeInterval { get set }
  25. /**
  26. Spring animation damping (0 ~ 1, default value should be 0.7)
  27. */
  28. var damping: CGFloat { get set }
  29. /**
  30. Spring animation velocity (default value should be 0.7)
  31. */
  32. var velocity: CGFloat { get set }
  33. /**
  34. Animation force (default value should be 1)
  35. */
  36. var force: CGFloat { get set }
  37. /**
  38. Animation function as a timing curve. (default value should be none)
  39. */
  40. var timingFunction: TimingFunctionType { get set }
  41. }
  42. public extension Animatable {
  43. func configureAnimatableProperties() {
  44. // Apply default values
  45. if duration.isNaN {
  46. duration = 0.7
  47. }
  48. if delay.isNaN {
  49. delay = 0
  50. }
  51. if damping.isNaN {
  52. damping = 0.7
  53. }
  54. if velocity.isNaN {
  55. velocity = 0.7
  56. }
  57. if force.isNaN {
  58. force = 1
  59. }
  60. }
  61. }
  62. public extension Animatable where Self: UIView {
  63. @discardableResult
  64. func animate(_ animation: AnimationType,
  65. duration: TimeInterval? = nil,
  66. damping: CGFloat? = nil,
  67. velocity: CGFloat? = nil,
  68. force: CGFloat? = nil) -> AnimationPromise<Self> {
  69. return AnimationPromise(view: self).delay(delay).then(animation, duration: duration, damping: damping, velocity: velocity, force: force)
  70. }
  71. func delay(_ delay: TimeInterval) -> AnimationPromise<Self> {
  72. let promise = AnimationPromise(view: self)
  73. return promise.delay(delay)
  74. }
  75. internal func doAnimation(_ animation: AnimationType? = nil, configuration: AnimationConfiguration, promise: AnimationPromise<Self>) {
  76. let completion = {
  77. promise.animationCompleted()
  78. }
  79. doAnimation(animation ?? self.animationType, configuration: configuration, completion: completion)
  80. }
  81. /**
  82. `autoRunAnimation` method, should be called in layoutSubviews() method
  83. */
  84. func autoRunAnimation() {
  85. if autoRun {
  86. autoRun = false
  87. animate(animationType)
  88. }
  89. }
  90. }
  91. fileprivate extension UIView {
  92. func doAnimation(_ animation: AnimationType, configuration: AnimationConfiguration, completion: @escaping () -> Void) {
  93. switch animation {
  94. case let .slide(way, direction):
  95. slide(way, direction: direction, configuration: configuration, completion: completion)
  96. case let .squeeze(way, direction):
  97. squeeze(way, direction: direction, configuration: configuration, completion: completion)
  98. case let .squeezeFade(way, direction):
  99. squeezeFade(way, direction: direction, configuration: configuration, completion: completion)
  100. case let .slideFade(way, direction):
  101. slideFade(way, direction: direction, configuration: configuration, completion: completion)
  102. case let .fade(way):
  103. fade(way, configuration: configuration, completion: completion)
  104. case let .zoom(way):
  105. zoom(way, configuration: configuration, completion: completion)
  106. case let .zoomInvert(way):
  107. zoom(way, invert: true, configuration: configuration, completion: completion)
  108. case let .shake(repeatCount):
  109. shake(repeatCount: repeatCount, configuration: configuration, completion: completion)
  110. case let .pop(repeatCount):
  111. pop(repeatCount: repeatCount, configuration: configuration, completion: completion)
  112. case let .squash(repeatCount):
  113. squash(repeatCount: repeatCount, configuration: configuration, completion: completion)
  114. case let .flip(axis):
  115. flip(axis: axis, configuration: configuration, completion: completion)
  116. case let .morph(repeatCount):
  117. morph(repeatCount: repeatCount, configuration: configuration, completion: completion)
  118. case let .flash(repeatCount):
  119. flash(repeatCount: repeatCount, configuration: configuration, completion: completion)
  120. case let .wobble(repeatCount):
  121. wobble(repeatCount: repeatCount, configuration: configuration, completion: completion)
  122. case let .swing(repeatCount):
  123. swing(repeatCount: repeatCount, configuration: configuration, completion: completion)
  124. case let .rotate(direction, repeatCount):
  125. rotate(direction: direction, repeatCount: repeatCount, configuration: configuration, completion: completion)
  126. case let .moveBy(x, y):
  127. moveBy(x: x, y: y, configuration: configuration, completion: completion)
  128. case let .moveTo(x, y):
  129. moveTo(x: x, y: y, configuration: configuration, completion: completion)
  130. case let .scale(fromX, fromY, toX, toY):
  131. scale(fromX: fromX, fromY: fromY, toX: toX, toY: toY, configuration: configuration, completion: completion)
  132. case let .spin(repeatCount):
  133. spin(repeatCount: repeatCount, configuration: configuration, completion: completion)
  134. case let .compound(animations, run):
  135. let animations = animations.filter {
  136. if case .none = $0 {
  137. return false
  138. }
  139. return true
  140. }
  141. guard !animations.isEmpty else {
  142. completion()
  143. return
  144. }
  145. switch run {
  146. case .sequential:
  147. let launch = animations.reversed().reduce(completion) { result, animation in {
  148. self.doAnimation(animation, configuration: configuration, completion: result)
  149. }
  150. }
  151. launch()
  152. case .parallel:
  153. var finalized = 0
  154. let finalCompletion: () -> Void = {
  155. finalized += 1
  156. if finalized == animations.count {
  157. completion()
  158. }
  159. }
  160. for animation in animations {
  161. self.doAnimation(animation, configuration: configuration, completion: finalCompletion)
  162. }
  163. }
  164. case .none:
  165. break
  166. }
  167. }
  168. // MARK: - Animation methods
  169. func slide(_ way: AnimationType.Way,
  170. direction: AnimationType.Direction,
  171. configuration: AnimationConfiguration,
  172. completion: AnimatableCompletion? = nil) {
  173. let values = computeValues(way: way, direction: direction, configuration: configuration, shouldScale: false)
  174. switch way {
  175. case .in:
  176. animateIn(animationValues: values, alpha: 1, configuration: configuration, completion: completion)
  177. case .out:
  178. animateOut(animationValues: values, alpha: 1, configuration: configuration, completion: completion)
  179. }
  180. }
  181. func squeeze(_ way: AnimationType.Way,
  182. direction: AnimationType.Direction,
  183. configuration: AnimationConfiguration,
  184. completion: AnimatableCompletion? = nil) {
  185. let values = computeValues(way: way, direction: direction, configuration: configuration, shouldScale: true)
  186. switch way {
  187. case .in:
  188. animateIn(animationValues: values, alpha: 1, configuration: configuration, completion: completion)
  189. case .out:
  190. animateOut(animationValues: values, alpha: 1, configuration: configuration, completion: completion)
  191. }
  192. }
  193. func rotate(direction: AnimationType.RotationDirection,
  194. repeatCount: Int,
  195. configuration: AnimationConfiguration,
  196. completion: AnimatableCompletion? = nil) {
  197. CALayer.animate({
  198. let animation = CABasicAnimation(keyPath: .rotation)
  199. animation.fromValue = direction == .cw ? 0 : CGFloat.pi * 2
  200. animation.toValue = direction == .cw ? CGFloat.pi * 2 : 0
  201. animation.duration = configuration.duration
  202. animation.repeatCount = Float(repeatCount)
  203. animation.autoreverses = false
  204. animation.beginTime = self.layer.currentMediaTime + configuration.delay
  205. self.layer.add(animation, forKey: "rotate")
  206. }, completion: completion)
  207. }
  208. func moveTo(x: Double, y: Double, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  209. if x.isNaN && y.isNaN {
  210. return
  211. }
  212. if case .none = configuration.timingFunction {
  213. // Get the absolute position
  214. let absolutePosition = frame.origin
  215. var xOffsetToMove: CGFloat
  216. if x.isNaN {
  217. xOffsetToMove = 0
  218. } else {
  219. xOffsetToMove = CGFloat(x) - absolutePosition.x
  220. }
  221. var yOffsetToMove: CGFloat
  222. if y.isNaN {
  223. yOffsetToMove = 0
  224. } else {
  225. yOffsetToMove = CGFloat(y) - absolutePosition.y
  226. }
  227. animateBy(x: xOffsetToMove, y: yOffsetToMove, configuration: configuration, completion: completion)
  228. } else {
  229. let position = center
  230. var xToMove: CGFloat
  231. if x.isNaN {
  232. xToMove = position.x
  233. } else {
  234. xToMove = CGFloat(x) + frame.width / 2
  235. }
  236. var yToMove: CGFloat
  237. if y.isNaN {
  238. yToMove = position.y
  239. } else {
  240. yToMove = CGFloat(y) + frame.height / 2
  241. }
  242. let path = UIBezierPath()
  243. path.move(to: position)
  244. path.addLine(to: CGPoint(x: xToMove, y: yToMove))
  245. animatePosition(path: path, configuration: configuration, completion: completion)
  246. }
  247. }
  248. func slideFade(_ way: AnimationType.Way,
  249. direction: AnimationType.Direction,
  250. configuration: AnimationConfiguration,
  251. completion: AnimatableCompletion? = nil) {
  252. let values = computeValues(way: way, direction: direction, configuration: configuration, shouldScale: false)
  253. switch way {
  254. case .in:
  255. alpha = 0
  256. animateIn(animationValues: values, alpha: 1, configuration: configuration, completion: completion)
  257. case .out:
  258. animateOut(animationValues: values, alpha: 0, configuration: configuration, completion: completion)
  259. }
  260. }
  261. func fade(_ way: AnimationType.FadeWay, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  262. switch way {
  263. case .outIn:
  264. fadeOutIn(configuration: configuration, completion: completion)
  265. case .inOut:
  266. fadeInOut(configuration: configuration, completion: completion)
  267. case .in:
  268. alpha = 0
  269. animateIn(animationValues: AnimationValues(x: 0, y: 0, scaleX: 1, scaleY: 1), alpha: 1, configuration: configuration, completion: completion)
  270. case .out:
  271. alpha = 1
  272. animateOut(animationValues: AnimationValues(x: 0, y: 0, scaleX: 1, scaleY: 1), alpha: 0, configuration: configuration, completion: completion)
  273. }
  274. }
  275. func squeezeFade(_ way: AnimationType.Way,
  276. direction: AnimationType.Direction,
  277. configuration: AnimationConfiguration,
  278. completion: AnimatableCompletion? = nil) {
  279. let values = computeValues(way: way, direction: direction, configuration: configuration, shouldScale: true)
  280. switch way {
  281. case .in:
  282. alpha = 0
  283. animateIn(animationValues: values, alpha: 1, configuration: configuration, completion: completion)
  284. case .out:
  285. animateOut(animationValues: values, alpha: 0, configuration: configuration, completion: completion)
  286. }
  287. }
  288. func zoom(_ way: AnimationType.Way, invert: Bool = false, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  289. let toAlpha: CGFloat
  290. switch way {
  291. case .in where invert:
  292. let scale = configuration.force
  293. alpha = 0
  294. toAlpha = 1
  295. transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
  296. animateIn(animationValues: AnimationValues(x: 0, y: 0, scaleX: scale / 2, scaleY: scale / 2),
  297. alpha: toAlpha,
  298. configuration: configuration,
  299. completion: completion)
  300. case .in:
  301. let scale = 2 * configuration.force
  302. alpha = 0
  303. toAlpha = 1
  304. animateIn(animationValues: AnimationValues(x: 0, y: 0, scaleX: scale, scaleY: scale),
  305. alpha: toAlpha,
  306. configuration: configuration,
  307. completion: completion)
  308. case .out:
  309. let scale = (invert ? 0.1 : 2) * configuration.force
  310. toAlpha = 0
  311. animateOut(animationValues: AnimationValues(x: 0, y: 0, scaleX: scale, scaleY: scale),
  312. alpha: toAlpha,
  313. configuration: configuration,
  314. completion: completion)
  315. }
  316. }
  317. func flip(axis: AnimationType.Axis, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  318. let scaleX: CGFloat
  319. let scaleY: CGFloat
  320. switch axis {
  321. case .x:
  322. scaleX = 1
  323. scaleY = -1
  324. case .y:
  325. scaleX = -1
  326. scaleY = 1
  327. }
  328. animateIn(animationValues: AnimationValues(x: 0, y: 0, scaleX: scaleX, scaleY: scaleY),
  329. alpha: 1,
  330. configuration: configuration,
  331. completion: completion)
  332. }
  333. func shake(repeatCount: Int, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  334. CALayer.animate({
  335. let animation = CAKeyframeAnimation(keyPath: .positionX)
  336. animation.values = [0, 30 * configuration.force, -30 * configuration.force, 30 * configuration.force, 0]
  337. animation.keyTimes = [0, 0.2, 0.4, 0.6, 0.8, 1]
  338. animation.timingFunctionType = configuration.timingFunction ?? .easeInOut
  339. animation.duration = configuration.duration
  340. animation.isAdditive = true
  341. animation.repeatCount = Float(repeatCount)
  342. animation.beginTime = self.layer.currentMediaTime + configuration.delay
  343. self.layer.add(animation, forKey: "shake")
  344. }, completion: completion)
  345. }
  346. func pop(repeatCount: Int, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  347. CALayer.animate({
  348. let animation = CAKeyframeAnimation(keyPath: .scale)
  349. animation.values = [0, 0.2 * configuration.force, -0.2 * configuration.force, 0.2 * configuration.force, 0]
  350. animation.keyTimes = [0, 0.2, 0.4, 0.6, 0.8, 1]
  351. animation.timingFunctionType = configuration.timingFunction ?? .easeInOut
  352. animation.duration = configuration.duration
  353. animation.isAdditive = true
  354. animation.repeatCount = Float(repeatCount)
  355. animation.beginTime = self.layer.currentMediaTime + configuration.delay
  356. self.layer.add(animation, forKey: "pop")
  357. }, completion: completion)
  358. }
  359. func squash(repeatCount: Int, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  360. CALayer.animate({
  361. let squashX = CAKeyframeAnimation(keyPath: .scaleX)
  362. squashX.values = [1, 1.5 * configuration.force, 0.5, 1.5 * configuration.force, 1]
  363. squashX.keyTimes = [0, 0.2, 0.4, 0.6, 0.8, 1]
  364. squashX.timingFunctionType = configuration.timingFunction ?? .easeInOut
  365. let squashY = CAKeyframeAnimation(keyPath: .scaleY)
  366. squashY.values = [1, 0.5, 1, 0.5, 1]
  367. squashY.keyTimes = [0, 0.2, 0.4, 0.6, 0.8, 1]
  368. squashY.timingFunctionType = configuration.timingFunction ?? .easeInOut
  369. let animationGroup = CAAnimationGroup()
  370. animationGroup.animations = [squashX, squashY]
  371. animationGroup.duration = configuration.duration
  372. animationGroup.repeatCount = Float(repeatCount)
  373. animationGroup.beginTime = self.layer.currentMediaTime + configuration.delay
  374. self.layer.add(animationGroup, forKey: "squash")
  375. }, completion: completion)
  376. }
  377. func morph(repeatCount: Int, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  378. CALayer.animate({
  379. let morphX = CAKeyframeAnimation(keyPath: .scaleX)
  380. morphX.values = [1, 1.3 * configuration.force, 0.7, 1.3 * configuration.force, 1]
  381. morphX.keyTimes = [0, 0.2, 0.4, 0.6, 0.8, 1]
  382. morphX.timingFunctionType = configuration.timingFunction ?? .easeInOut
  383. let morphY = CAKeyframeAnimation(keyPath: .scaleY)
  384. morphY.values = [1, 0.7, 1.3 * configuration.force, 0.7, 1]
  385. morphY.keyTimes = [0, 0.2, 0.4, 0.6, 0.8, 1]
  386. morphY.timingFunctionType = configuration.timingFunction ?? .easeInOut
  387. let animationGroup = CAAnimationGroup()
  388. animationGroup.animations = [morphX, morphY]
  389. animationGroup.duration = configuration.duration
  390. animationGroup.repeatCount = Float(repeatCount)
  391. animationGroup.beginTime = self.layer.currentMediaTime + configuration.delay
  392. self.layer.add(animationGroup, forKey: "morph")
  393. }, completion: completion)
  394. }
  395. func squeeze(repeatCount: Int, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  396. CALayer.animate({
  397. let squeezeX = CAKeyframeAnimation(keyPath: .scaleX)
  398. squeezeX.values = [1, 1.5 * configuration.force, 0.5, 1.5 * configuration.force, 1]
  399. squeezeX.keyTimes = [0, 0.2, 0.4, 0.6, 0.8, 1]
  400. squeezeX.timingFunctionType = configuration.timingFunction ?? .easeInOut
  401. let squeezeY = CAKeyframeAnimation(keyPath: .scaleY)
  402. squeezeY.values = [1, 0.5, 1, 0.5, 1]
  403. squeezeY.keyTimes = [0, 0.2, 0.4, 0.6, 0.8, 1]
  404. squeezeY.timingFunctionType = configuration.timingFunction ?? .easeInOut
  405. let animationGroup = CAAnimationGroup()
  406. animationGroup.animations = [squeezeX, squeezeY]
  407. animationGroup.duration = configuration.duration
  408. animationGroup.repeatCount = Float(repeatCount)
  409. animationGroup.beginTime = self.layer.currentMediaTime + configuration.delay
  410. self.layer.add(animationGroup, forKey: "squeeze")
  411. }, completion: completion)
  412. }
  413. func flash(repeatCount: Int, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  414. CALayer.animate({
  415. let animation = CABasicAnimation(keyPath: .opacity)
  416. animation.fromValue = 1
  417. animation.toValue = 0
  418. animation.duration = configuration.duration
  419. animation.repeatCount = Float(repeatCount) * 2.0
  420. animation.autoreverses = true
  421. animation.beginTime = self.layer.currentMediaTime + configuration.delay
  422. self.layer.add(animation, forKey: "flash")
  423. }, completion: completion)
  424. }
  425. func wobble(repeatCount: Int, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  426. CALayer.animate({
  427. let rotation = CAKeyframeAnimation(keyPath: .rotation)
  428. rotation.values = [0, 0.3 * configuration.force, -0.3 * configuration.force, 0.3 * configuration.force, 0]
  429. rotation.keyTimes = [0, 0.2, 0.4, 0.6, 0.8, 1]
  430. rotation.isAdditive = true
  431. let positionX = CAKeyframeAnimation(keyPath: .positionX)
  432. positionX.values = [0, 30 * configuration.force, -30 * configuration.force, 30 * configuration.force, 0]
  433. positionX.keyTimes = [0, 0.2, 0.4, 0.6, 0.8, 1]
  434. positionX.timingFunctionType = configuration.timingFunction ?? .easeInOut
  435. positionX.isAdditive = true
  436. let animationGroup = CAAnimationGroup()
  437. animationGroup.animations = [rotation, positionX]
  438. animationGroup.duration = configuration.duration
  439. animationGroup.beginTime = self.layer.currentMediaTime + configuration.delay
  440. animationGroup.repeatCount = Float(repeatCount)
  441. self.layer.add(animationGroup, forKey: "wobble")
  442. }, completion: completion)
  443. }
  444. func swing(repeatCount: Int, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  445. CALayer.animate({
  446. let animation = CAKeyframeAnimation(keyPath: .rotation)
  447. animation.values = [0, 0.3 * configuration.force, -0.3 * configuration.force, 0.3 * configuration.force, 0]
  448. animation.keyTimes = [0, 0.2, 0.4, 0.6, 0.8, 1]
  449. animation.duration = configuration.duration
  450. animation.isAdditive = true
  451. animation.repeatCount = Float(repeatCount)
  452. animation.beginTime = self.layer.currentMediaTime + configuration.delay
  453. self.layer.add(animation, forKey: "swing")
  454. }, completion: completion)
  455. }
  456. func moveBy(x: Double, y: Double, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  457. if x.isNaN && y.isNaN {
  458. return
  459. }
  460. if case .none = configuration.timingFunction {
  461. // spring animation
  462. let xOffsetToMove = x.isNaN ? 0: CGFloat(x)
  463. let yOffsetToMove = y.isNaN ? 0: CGFloat(y)
  464. animateBy(x: xOffsetToMove, y: yOffsetToMove, configuration: configuration, completion: completion)
  465. } else {
  466. let position = self.center
  467. var xToMove: CGFloat
  468. if x.isNaN {
  469. xToMove = position.x
  470. } else {
  471. xToMove = position.x + CGFloat(x)
  472. }
  473. var yToMove: CGFloat
  474. if y.isNaN {
  475. yToMove = position.y
  476. } else {
  477. yToMove = position.y + CGFloat(y)
  478. }
  479. let path = UIBezierPath()
  480. path.move(to: position)
  481. path.addLine(to: CGPoint(x: xToMove, y: yToMove))
  482. animatePosition(path: path, configuration: configuration, completion: completion)
  483. }
  484. }
  485. func scale(fromX: Double, fromY: Double, toX: Double, toY: Double, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  486. if fromX.isNaN || fromY.isNaN || toX.isNaN || toY.isNaN {
  487. return
  488. }
  489. if case .none = configuration.timingFunction {
  490. springScale(fromX: fromX, fromY: fromY, toX: toX, toY: toY, configuration: configuration, completion: completion)
  491. } else {
  492. layerScale(fromX: fromX, fromY: fromY, toX: toX, toY: toY, configuration: configuration, completion: completion)
  493. }
  494. }
  495. private func springScale(fromX: Double,
  496. fromY: Double,
  497. toX: Double,
  498. toY: Double,
  499. configuration: AnimationConfiguration,
  500. completion: AnimatableCompletion? = nil) {
  501. transform = CGAffineTransform(scaleX: CGFloat(fromX), y: CGFloat(fromY))
  502. UIView.animate(with: configuration, animations: {
  503. self.transform = CGAffineTransform(scaleX: CGFloat(toX), y: CGFloat(toY))
  504. }, completion: { completed in
  505. if completed {
  506. completion?()
  507. }
  508. })
  509. }
  510. private func layerScale(fromX: Double,
  511. fromY: Double,
  512. toX: Double,
  513. toY: Double,
  514. configuration: AnimationConfiguration,
  515. completion: AnimatableCompletion? = nil) {
  516. CALayer.animate({
  517. let scaleX = CAKeyframeAnimation(keyPath: .scaleX)
  518. scaleX.values = [fromX, toX]
  519. scaleX.keyTimes = [0, 1]
  520. scaleX.timingFunctionType = configuration.timingFunction ?? .easeInOut
  521. let scaleY = CAKeyframeAnimation(keyPath: .scaleY)
  522. scaleY.values = [fromY, toY]
  523. scaleY.keyTimes = [0, 1]
  524. scaleY.timingFunctionType = configuration.timingFunction ?? .easeInOut
  525. let animationGroup = CAAnimationGroup()
  526. animationGroup.animations = [scaleX, scaleY]
  527. animationGroup.duration = configuration.duration
  528. animationGroup.beginTime = self.layer.currentMediaTime + configuration.delay
  529. self.layer.add(animationGroup, forKey: "scale")
  530. }, completion: completion)
  531. }
  532. func computeValues(way: AnimationType.Way,
  533. direction: AnimationType.Direction,
  534. configuration: AnimationConfiguration,
  535. shouldScale: Bool) -> AnimationValues {
  536. let scale = 3 * configuration.force
  537. var scaleX: CGFloat = 1
  538. var scaleY: CGFloat = 1
  539. var frame: CGRect
  540. if let window = window {
  541. frame = window.convert(self.frame, to: window)
  542. } else {
  543. frame = self.frame
  544. }
  545. var x: CGFloat = 0
  546. var y: CGFloat = 0
  547. switch (way, direction) {
  548. case (.in, .left), (.out, .right):
  549. x = screenSize.width - frame.minX
  550. case (.in, .right), (.out, .left):
  551. x = -frame.maxX
  552. case (.in, .up), (.out, .down):
  553. y = screenSize.height - frame.minY
  554. case (.in, .down), (.out, .up):
  555. y = -frame.maxY
  556. }
  557. x *= configuration.force
  558. y *= configuration.force
  559. if shouldScale && direction.isVertical() {
  560. scaleY = scale
  561. } else if shouldScale {
  562. scaleX = scale
  563. }
  564. return (x: x, y: y, scaleX: scaleX, scaleY: scaleY)
  565. }
  566. func spin(repeatCount: Int,
  567. configuration: AnimationConfiguration,
  568. completion: AnimatableCompletion? = nil) {
  569. CALayer.animate({
  570. let rotationX = CABasicAnimation(keyPath: .rotationX)
  571. rotationX.toValue = CGFloat.pi * 2
  572. rotationX.fromValue = 0
  573. rotationX.timingFunctionType = configuration.timingFunction ?? .easeInOut
  574. let rotationY = CABasicAnimation(keyPath: .rotationY)
  575. rotationY.toValue = CGFloat.pi * 2
  576. rotationY.fromValue = 0
  577. rotationY.timingFunctionType = configuration.timingFunction ?? .easeInOut
  578. let rotationZ = CABasicAnimation(keyPath: .rotationZ)
  579. rotationZ.toValue = CGFloat.pi * 2
  580. rotationZ.fromValue = 0
  581. rotationZ.timingFunctionType = configuration.timingFunction ?? .easeInOut
  582. let animationGroup = CAAnimationGroup()
  583. animationGroup.animations = [rotationX, rotationY, rotationZ]
  584. animationGroup.duration = configuration.duration
  585. animationGroup.repeatCount = Float(repeatCount)
  586. animationGroup.beginTime = CACurrentMediaTime() + configuration.delay
  587. self.layer.add(animationGroup, forKey: "rotation")
  588. }, completion: completion)
  589. }
  590. func fadeOutIn(configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  591. CALayer.animate({
  592. let animation = CABasicAnimation(keyPath: .opacity)
  593. animation.fromValue = 1
  594. animation.toValue = 0
  595. animation.timingFunctionType = configuration.timingFunction ?? .easeInOut
  596. animation.duration = configuration.duration
  597. animation.beginTime = self.layer.currentMediaTime + configuration.delay
  598. animation.autoreverses = true
  599. self.layer.add(animation, forKey: "fade")
  600. }, completion: completion)
  601. }
  602. func fadeInOut(configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  603. CALayer.animate({
  604. let animation = CABasicAnimation(keyPath: .opacity)
  605. animation.fromValue = 0
  606. animation.toValue = 1
  607. animation.timingFunctionType = configuration.timingFunction ?? .easeInOut
  608. animation.duration = configuration.duration
  609. animation.beginTime = self.layer.currentMediaTime + configuration.delay
  610. animation.autoreverses = true
  611. animation.isRemovedOnCompletion = false
  612. self.layer.add(animation, forKey: "fade")
  613. },
  614. completion: {
  615. self.alpha = 0
  616. completion?()
  617. }
  618. )
  619. }
  620. func animateBy(x: CGFloat, y: CGFloat, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  621. let translate = CGAffineTransform(translationX: x, y: y)
  622. UIView.animate(with: configuration, animations: {
  623. self.transform = translate
  624. }, completion: { completed in
  625. if completed {
  626. completion?()
  627. }
  628. })
  629. }
  630. func animatePosition(path: UIBezierPath, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  631. CALayer.animate({
  632. let animation = CAKeyframeAnimation(keyPath: .position)
  633. animation.timingFunctionType = configuration.timingFunction ?? .easeInOut
  634. animation.duration = configuration.duration
  635. animation.beginTime = self.layer.currentMediaTime + configuration.delay
  636. animation.path = path.cgPath
  637. self.layer.add(animation, forKey: "animate position")
  638. }, completion: completion)
  639. }
  640. func animateIn(animationValues: AnimationValues, alpha: CGFloat, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  641. let translate = CGAffineTransform(translationX: animationValues.x, y: animationValues.y)
  642. let scale = CGAffineTransform(scaleX: animationValues.scaleX, y: animationValues.scaleY)
  643. let translateAndScale = translate.concatenating(scale)
  644. transform = translateAndScale
  645. UIView.animate(with: configuration, animations: {
  646. self.transform = .identity
  647. self.alpha = alpha
  648. }, completion: { completed in
  649. if completed {
  650. completion?()
  651. }
  652. })
  653. }
  654. func animateOut(animationValues: AnimationValues, alpha: CGFloat, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  655. let translate = CGAffineTransform(translationX: animationValues.x, y: animationValues.y)
  656. let scale = CGAffineTransform(scaleX: animationValues.scaleX, y: animationValues.scaleY)
  657. let translateAndScale = translate.concatenating(scale)
  658. UIView.animate(with: configuration, animations: {
  659. self.transform = translateAndScale
  660. self.alpha = alpha
  661. }, completion: { completed in
  662. if completed {
  663. completion?()
  664. }
  665. })
  666. }
  667. var screenSize: CGSize {
  668. return window?.screen.bounds.size ?? .zero
  669. }
  670. }
  671. // Animations for `UIBarItem`
  672. public extension Animatable where Self: UIBarItem {
  673. func animate(_ animation: AnimationType? = nil,
  674. duration: TimeInterval? = nil,
  675. damping: CGFloat? = nil,
  676. velocity: CGFloat? = nil,
  677. force: CGFloat? = nil,
  678. view: UIView,
  679. completion: AnimatableCompletion? = nil) {
  680. let configuration = AnimationConfiguration(damping: damping ?? self.damping,
  681. velocity: velocity ?? self.velocity,
  682. duration: duration ?? self.duration,
  683. delay: 0,
  684. force: force ?? self.force,
  685. timingFunction: timingFunction ?? self.timingFunction)
  686. view.doAnimation(animation ?? self.animationType, configuration: configuration) {
  687. completion?()
  688. }
  689. }
  690. }
  691. public extension AnimationType {
  692. /// This animation use damping and velocity parameters.
  693. var isSpring: Bool {
  694. switch self {
  695. case .moveBy, .moveTo, .scale:
  696. return true
  697. case .squeeze, .squeezeFade, .slide, .slideFade, .zoom, .zoomInvert:
  698. return true
  699. case .fade(way: .in), .fade(way: .out):
  700. return true
  701. case .rotate, .shake, .flip, .pop, .squash, .morph, .swing, .wobble, .flash, .spin:
  702. return false
  703. case .fade(way: .inOut), .fade(way: .outIn):
  704. return false
  705. case .compound(let animations, _):
  706. return animations.contains { $0.isSpring }
  707. case .none:
  708. return false
  709. }
  710. }
  711. /// This animation use timing function parameter.
  712. var isCubic: Bool {
  713. switch self {
  714. case .moveBy, .moveTo, .scale:
  715. return true
  716. case .rotate, .shake, .flip, .pop, .squash, .morph, .swing, .wobble, .flash, .spin:
  717. return true
  718. case .fade(.inOut), .fade(.outIn):
  719. return true
  720. case .squeeze, .squeezeFade, .slide, .slideFade, .zoom, .zoomInvert:
  721. return false
  722. case .fade(way: .in), .fade(way: .out):
  723. return false
  724. case .compound(let animations, _):
  725. return animations.contains { $0.isCubic }
  726. case .none:
  727. return false
  728. }
  729. }
  730. }
  731. /// Enumeration for Core Animation key path.
  732. enum AnimationKeyPath: String {
  733. // Positions
  734. case position = "position"
  735. case positionX = "position.x"
  736. case positionY = "position.y"
  737. // Transforms
  738. case transform = "transform"
  739. case rotation = "transform.rotation"
  740. case rotationX = "transform.rotation.x"
  741. case rotationY = "transform.rotation.y"
  742. case rotationZ = "transform.rotation.z"
  743. case scale = "transform.scale"
  744. case scaleX = "transform.scale.x"
  745. case scaleY = "transform.scale.y"
  746. case scaleZ = "transform.scale.z"
  747. case translation = "transform.translation"
  748. case translationX = "transform.translation.x"
  749. case translationY = "transform.translation.y"
  750. case translationZ = "transform.translation.z"
  751. // Stroke
  752. case strokeEnd = "strokeEnd"
  753. case strokeStart = "strokeStart"
  754. // Other properties
  755. case opacity = "opacity"
  756. case path = "path"
  757. case lineWidth = "lineWidth"
  758. }
  759. extension CABasicAnimation {
  760. convenience init(keyPath: AnimationKeyPath) {
  761. self.init(keyPath: keyPath.rawValue)
  762. }
  763. }
  764. extension CAKeyframeAnimation {
  765. convenience init(keyPath: AnimationKeyPath) {
  766. self.init(keyPath: keyPath.rawValue)
  767. }
  768. }
  769. extension UIView {
  770. /// Animate view using `AnimationConfiguration`.
  771. class func animate(with configuration: AnimationConfiguration, animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
  772. if configuration.timingFunction.isCurveOption {
  773. UIView.animate(withDuration: configuration.duration,
  774. delay: configuration.delay,
  775. options: configuration.options,
  776. animations: animations,
  777. completion: completion)
  778. } else {
  779. UIView.animate(withDuration: configuration.duration,
  780. delay: configuration.delay,
  781. usingSpringWithDamping: configuration.damping,
  782. initialSpringVelocity: configuration.velocity,
  783. options: configuration.options,
  784. animations: animations,
  785. completion: completion)
  786. }
  787. }
  788. }