Animatable.swift 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885
  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. // swiftlint:disable variable_name_min_length
  457. func moveBy(x: Double, y: Double, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  458. if x.isNaN && y.isNaN {
  459. return
  460. }
  461. if case .none = configuration.timingFunction {
  462. // spring animation
  463. let xOffsetToMove = x.isNaN ? 0: CGFloat(x)
  464. let yOffsetToMove = y.isNaN ? 0: CGFloat(y)
  465. animateBy(x: xOffsetToMove, y: yOffsetToMove, configuration: configuration, completion: completion)
  466. } else {
  467. let position = self.center
  468. var xToMove: CGFloat
  469. if x.isNaN {
  470. xToMove = position.x
  471. } else {
  472. xToMove = position.x + CGFloat(x)
  473. }
  474. var yToMove: CGFloat
  475. if y.isNaN {
  476. yToMove = position.y
  477. } else {
  478. yToMove = position.y + CGFloat(y)
  479. }
  480. let path = UIBezierPath()
  481. path.move(to: position)
  482. path.addLine(to: CGPoint(x: xToMove, y: yToMove))
  483. animatePosition(path: path, configuration: configuration, completion: completion)
  484. }
  485. }
  486. func scale(fromX: Double, fromY: Double, toX: Double, toY: Double, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  487. if fromX.isNaN || fromY.isNaN || toX.isNaN || toY.isNaN {
  488. return
  489. }
  490. if case .none = configuration.timingFunction {
  491. springScale(fromX: fromX, fromY: fromY, toX: toX, toY: toY, configuration: configuration, completion: completion)
  492. } else {
  493. layerScale(fromX: fromX, fromY: fromY, toX: toX, toY: toY, configuration: configuration, completion: completion)
  494. }
  495. }
  496. private func springScale(fromX: Double,
  497. fromY: Double,
  498. toX: Double,
  499. toY: Double,
  500. configuration: AnimationConfiguration,
  501. completion: AnimatableCompletion? = nil) {
  502. transform = CGAffineTransform(scaleX: CGFloat(fromX), y: CGFloat(fromY))
  503. UIView.animate(with: configuration, animations: {
  504. self.transform = CGAffineTransform(scaleX: CGFloat(toX), y: CGFloat(toY))
  505. }, completion: { completed in
  506. if completed {
  507. completion?()
  508. }
  509. })
  510. }
  511. private func layerScale(fromX: Double,
  512. fromY: Double,
  513. toX: Double,
  514. toY: Double,
  515. configuration: AnimationConfiguration,
  516. completion: AnimatableCompletion? = nil) {
  517. CALayer.animate({
  518. let scaleX = CAKeyframeAnimation(keyPath: .scaleX)
  519. scaleX.values = [fromX, toX]
  520. scaleX.keyTimes = [0, 1]
  521. scaleX.timingFunctionType = configuration.timingFunction ?? .easeInOut
  522. let scaleY = CAKeyframeAnimation(keyPath: .scaleY)
  523. scaleY.values = [fromY, toY]
  524. scaleY.keyTimes = [0, 1]
  525. scaleY.timingFunctionType = configuration.timingFunction ?? .easeInOut
  526. let animationGroup = CAAnimationGroup()
  527. animationGroup.animations = [scaleX, scaleY]
  528. animationGroup.duration = configuration.duration
  529. animationGroup.beginTime = self.layer.currentMediaTime + configuration.delay
  530. self.layer.add(animationGroup, forKey: "scale")
  531. }, completion: completion)
  532. }
  533. // swiftlint:enable variable_name_min_length
  534. func computeValues(way: AnimationType.Way,
  535. direction: AnimationType.Direction,
  536. configuration: AnimationConfiguration,
  537. shouldScale: Bool) -> AnimationValues {
  538. let scale = 3 * configuration.force
  539. var scaleX: CGFloat = 1
  540. var scaleY: CGFloat = 1
  541. var frame: CGRect
  542. if let window = window {
  543. frame = window.convert(self.frame, to: window)
  544. } else {
  545. frame = self.frame
  546. }
  547. var x: CGFloat = 0
  548. var y: CGFloat = 0
  549. switch (way, direction) {
  550. case (.in, .left), (.out, .right):
  551. x = screenSize.width - frame.minX
  552. case (.in, .right), (.out, .left):
  553. x = -frame.maxX
  554. case (.in, .up), (.out, .down):
  555. y = screenSize.height - frame.minY
  556. case (.in, .down), (.out, .up):
  557. y = -frame.maxY
  558. }
  559. x *= configuration.force
  560. y *= configuration.force
  561. if shouldScale && direction.isVertical() {
  562. scaleY = scale
  563. } else if shouldScale {
  564. scaleX = scale
  565. }
  566. return (x: x, y: y, scaleX: scaleX, scaleY: scaleY)
  567. }
  568. func spin(repeatCount: Int,
  569. configuration: AnimationConfiguration,
  570. completion: AnimatableCompletion? = nil) {
  571. CALayer.animate({
  572. let rotationX = CABasicAnimation(keyPath: .rotationX)
  573. rotationX.toValue = CGFloat.pi * 2
  574. rotationX.fromValue = 0
  575. rotationX.timingFunctionType = configuration.timingFunction ?? .easeInOut
  576. let rotationY = CABasicAnimation(keyPath: .rotationY)
  577. rotationY.toValue = CGFloat.pi * 2
  578. rotationY.fromValue = 0
  579. rotationY.timingFunctionType = configuration.timingFunction ?? .easeInOut
  580. let rotationZ = CABasicAnimation(keyPath: .rotationZ)
  581. rotationZ.toValue = CGFloat.pi * 2
  582. rotationZ.fromValue = 0
  583. rotationZ.timingFunctionType = configuration.timingFunction ?? .easeInOut
  584. let animationGroup = CAAnimationGroup()
  585. animationGroup.animations = [rotationX, rotationY, rotationZ]
  586. animationGroup.duration = configuration.duration
  587. animationGroup.repeatCount = Float(repeatCount)
  588. animationGroup.beginTime = CACurrentMediaTime() + configuration.delay
  589. self.layer.add(animationGroup, forKey: "rotation")
  590. }, completion: completion)
  591. }
  592. func fadeOutIn(configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  593. CALayer.animate({
  594. let animation = CABasicAnimation(keyPath: .opacity)
  595. animation.fromValue = 1
  596. animation.toValue = 0
  597. animation.timingFunctionType = configuration.timingFunction ?? .easeInOut
  598. animation.duration = configuration.duration
  599. animation.beginTime = self.layer.currentMediaTime + configuration.delay
  600. animation.autoreverses = true
  601. self.layer.add(animation, forKey: "fade")
  602. }, completion: completion)
  603. }
  604. func fadeInOut(configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  605. CALayer.animate({
  606. let animation = CABasicAnimation(keyPath: .opacity)
  607. animation.fromValue = 0
  608. animation.toValue = 1
  609. animation.timingFunctionType = configuration.timingFunction ?? .easeInOut
  610. animation.duration = configuration.duration
  611. animation.beginTime = self.layer.currentMediaTime + configuration.delay
  612. animation.autoreverses = true
  613. animation.isRemovedOnCompletion = false
  614. self.layer.add(animation, forKey: "fade")
  615. },
  616. completion: {
  617. self.alpha = 0
  618. completion?()
  619. }
  620. )
  621. }
  622. // swiftlint:disable:next variable_name_min_length
  623. func animateBy(x: CGFloat, y: CGFloat, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  624. let translate = CGAffineTransform(translationX: x, y: y)
  625. UIView.animate(with: configuration, animations: {
  626. self.transform = translate
  627. }, completion: { completed in
  628. if completed {
  629. completion?()
  630. }
  631. })
  632. }
  633. func animatePosition(path: UIBezierPath, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  634. CALayer.animate({
  635. let animation = CAKeyframeAnimation(keyPath: .position)
  636. animation.timingFunctionType = configuration.timingFunction ?? .easeInOut
  637. animation.duration = configuration.duration
  638. animation.beginTime = self.layer.currentMediaTime + configuration.delay
  639. animation.path = path.cgPath
  640. self.layer.add(animation, forKey: "animate position")
  641. }, completion: completion)
  642. }
  643. func animateIn(animationValues: AnimationValues, alpha: CGFloat, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  644. let translate = CGAffineTransform(translationX: animationValues.x, y: animationValues.y)
  645. let scale = CGAffineTransform(scaleX: animationValues.scaleX, y: animationValues.scaleY)
  646. let translateAndScale = translate.concatenating(scale)
  647. transform = translateAndScale
  648. UIView.animate(with: configuration, animations: {
  649. self.transform = .identity
  650. self.alpha = alpha
  651. }, completion: { completed in
  652. if completed {
  653. completion?()
  654. }
  655. })
  656. }
  657. func animateOut(animationValues: AnimationValues, alpha: CGFloat, configuration: AnimationConfiguration, completion: AnimatableCompletion? = nil) {
  658. let translate = CGAffineTransform(translationX: animationValues.x, y: animationValues.y)
  659. let scale = CGAffineTransform(scaleX: animationValues.scaleX, y: animationValues.scaleY)
  660. let translateAndScale = translate.concatenating(scale)
  661. UIView.animate(with: configuration, animations: {
  662. self.transform = translateAndScale
  663. self.alpha = alpha
  664. }, completion: { completed in
  665. if completed {
  666. completion?()
  667. }
  668. })
  669. }
  670. var screenSize: CGSize {
  671. return window?.screen.bounds.size ?? .zero
  672. }
  673. }
  674. // swiftlint:enable variable_name_min_length
  675. // Animations for `UIBarItem`
  676. public extension Animatable where Self: UIBarItem {
  677. func animate(_ animation: AnimationType? = nil,
  678. duration: TimeInterval? = nil,
  679. damping: CGFloat? = nil,
  680. velocity: CGFloat? = nil,
  681. force: CGFloat? = nil,
  682. view: UIView,
  683. completion: AnimatableCompletion? = nil) {
  684. let configuration = AnimationConfiguration(damping: damping ?? self.damping,
  685. velocity: velocity ?? self.velocity,
  686. duration: duration ?? self.duration,
  687. delay: 0,
  688. force: force ?? self.force,
  689. timingFunction: timingFunction ?? self.timingFunction)
  690. view.doAnimation(animation ?? self.animationType, configuration: configuration) {
  691. completion?()
  692. }
  693. }
  694. }
  695. public extension AnimationType {
  696. /// This animation use damping and velocity parameters.
  697. var isSpring: Bool {
  698. switch self {
  699. case .moveBy, .moveTo, .scale:
  700. return true
  701. case .squeeze, .squeezeFade, .slide, .slideFade, .zoom, .zoomInvert:
  702. return true
  703. case .fade(way: .in), .fade(way: .out):
  704. return true
  705. case .rotate, .shake, .flip, .pop, .squash, .morph, .swing, .wobble, .flash, .spin:
  706. return false
  707. case .fade(way: .inOut), .fade(way: .outIn):
  708. return false
  709. case .compound(let animations, _):
  710. return animations.reduce(false) { result, animation in
  711. result || animation.isSpring
  712. }
  713. case .none:
  714. return false
  715. }
  716. }
  717. /// This animation use timing function parameter.
  718. var isCubic: Bool {
  719. switch self {
  720. case .moveBy, .moveTo, .scale:
  721. return true
  722. case .rotate, .shake, .flip, .pop, .squash, .morph, .swing, .wobble, .flash, .spin:
  723. return true
  724. case .fade(.inOut), .fade(.outIn):
  725. return true
  726. case .squeeze, .squeezeFade, .slide, .slideFade, .zoom, .zoomInvert:
  727. return false
  728. case .fade(way: .in), .fade(way: .out):
  729. return false
  730. case .compound(let animations, _):
  731. return animations.reduce(false) { result, animation in
  732. result || animation.isCubic
  733. }
  734. case .none:
  735. return false
  736. }
  737. }
  738. }
  739. /// Enumeration for Core Animation key path.
  740. enum AnimationKeyPath: String {
  741. // Positions
  742. case position = "position"
  743. case positionX = "position.x"
  744. case positionY = "position.y"
  745. // Transforms
  746. case transform = "transform"
  747. case rotation = "transform.rotation"
  748. case rotationX = "transform.rotation.x"
  749. case rotationY = "transform.rotation.y"
  750. case rotationZ = "transform.rotation.z"
  751. case scale = "transform.scale"
  752. case scaleX = "transform.scale.x"
  753. case scaleY = "transform.scale.y"
  754. case scaleZ = "transform.scale.z"
  755. case translation = "transform.translation"
  756. case translationX = "transform.translation.x"
  757. case translationY = "transform.translation.y"
  758. case translationZ = "transform.translation.z"
  759. // Stroke
  760. case strokeEnd = "strokeEnd"
  761. case strokeStart = "strokeStart"
  762. // Other properties
  763. case opacity = "opacity"
  764. case path = "path"
  765. case lineWidth = "lineWidth"
  766. }
  767. extension CABasicAnimation {
  768. convenience init(keyPath: AnimationKeyPath) {
  769. self.init(keyPath: keyPath.rawValue)
  770. }
  771. }
  772. extension CAKeyframeAnimation {
  773. convenience init(keyPath: AnimationKeyPath) {
  774. self.init(keyPath: keyPath.rawValue)
  775. }
  776. }
  777. extension UIView {
  778. /// Animate view using `AnimationConfiguration`.
  779. class func animate(with configuration: AnimationConfiguration, animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
  780. if configuration.timingFunction.isCurveOption {
  781. UIView.animate(withDuration: configuration.duration,
  782. delay: configuration.delay,
  783. options: configuration.options,
  784. animations: animations,
  785. completion: completion)
  786. } else {
  787. UIView.animate(withDuration: configuration.duration,
  788. delay: configuration.delay,
  789. usingSpringWithDamping: configuration.damping,
  790. initialSpringVelocity: configuration.velocity,
  791. options: configuration.options,
  792. animations: animations,
  793. completion: completion)
  794. }
  795. }
  796. }