UIView+Shake.m 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. //
  2. // UIView+Shake.m
  3. // Animations
  4. //
  5. // Created by YouXianMing on 16/2/25.
  6. // Copyright © 2016年 YouXianMing. All rights reserved.
  7. //
  8. #import "UIView+Shake.h"
  9. #import <objc/runtime.h>
  10. #define HAS_OPT(options, option) ((options & option) == option)
  11. @interface SCShakeInfo : NSObject
  12. @property (assign, nonatomic) CGAffineTransform baseTransform;
  13. @property (assign, nonatomic) BOOL shaking;
  14. @property (assign, nonatomic) SCShakeOptions options;
  15. @property (assign, nonatomic) CGFloat force;
  16. @property (assign, nonatomic) CGFloat duration;
  17. @property (assign, nonatomic) CGFloat iterationDuration;
  18. @property (assign, nonatomic) CFTimeInterval startTime;
  19. @property (strong, nonatomic) ShakeCompletionHandler completionHandler;
  20. @property (assign, nonatomic) BOOL reversed;
  21. @property (readonly, nonatomic) CGFloat completionRatio;
  22. @end
  23. @implementation SCShakeInfo
  24. - (CGFloat)completionRatio {
  25. return (CACurrentMediaTime() - self.startTime) / self.duration;
  26. }
  27. @end
  28. @implementation UIView (Shake)
  29. static const char *ShakeInfoKey = "ShakeInfo";
  30. - (void)shake {
  31. [self shakeWithOptions:kDefaultShakeOptions
  32. force:kDefaultShakeForce
  33. duration:kDefaultShakeDuration
  34. iterationDuration:kDefaultShakeIterationDuration
  35. completionHandler:nil];
  36. }
  37. - (void)shakeWithOptions:(SCShakeOptions)shakeOptions
  38. force:(CGFloat)force
  39. duration:(CGFloat)duration
  40. iterationDuration:(CGFloat)iterationDuration
  41. completionHandler:(ShakeCompletionHandler)completionHandler {
  42. SCShakeInfo *shakeInfo = [self shakeInfo];
  43. shakeInfo.options = shakeOptions;
  44. shakeInfo.force = force;
  45. shakeInfo.startTime = CACurrentMediaTime();
  46. shakeInfo.duration = duration;
  47. shakeInfo.iterationDuration = iterationDuration;
  48. shakeInfo.completionHandler = completionHandler;
  49. if (!shakeInfo.shaking) {
  50. shakeInfo.baseTransform = self.transform;
  51. shakeInfo.shaking = YES;
  52. [self _doAnimation:1];
  53. }
  54. }
  55. - (CGFloat)_getInterpolationRatio:(CGFloat)completionRatio options:(SCShakeOptions)options {
  56. CGFloat (*interpFunc)(CGFloat) = nil;
  57. if (HAS_OPT(options, SCShakeOptionsForceInterpolationRandom)) {
  58. interpFunc =& InterpolateRandom;
  59. } else if (HAS_OPT(options, SCShakeOptionsForceInterpolationExpDown)) {
  60. interpFunc =& InterpolateExpDown;
  61. } else if (HAS_OPT(options, SCShakeOptionsForceInterpolationExpUp)) {
  62. interpFunc =& InterpolateExpUp;
  63. } else if (HAS_OPT(options, SCShakeOptionsForceInterpolationLinearDown)) {
  64. interpFunc =& InterpolateLinearDown;
  65. } else if (HAS_OPT(options, SCShakeOptionsForceInterpolationLinearUp)) {
  66. interpFunc =& InterpolateLinearUp;
  67. } else {
  68. interpFunc =& InterpolateNone;
  69. }
  70. return interpFunc(completionRatio);
  71. }
  72. - (void)_animate:(CGFloat)force shakeInfo:(SCShakeInfo *)shakeInfo {
  73. CGAffineTransform baseTransform = shakeInfo.baseTransform;
  74. SCShakeOptions options = shakeInfo.options;
  75. if (HAS_OPT(options, SCShakeOptionsDirectionHorizontalAndVertical)) {
  76. if (arc4random_uniform(2) == 1) {
  77. self.transform = CGAffineTransformTranslate(baseTransform, 0, force * self.bounds.size.height);
  78. } else {
  79. self.transform = CGAffineTransformTranslate(baseTransform, force * self.bounds.size.width, 0);
  80. }
  81. } else if (HAS_OPT(options, SCShakeOptionsDirectionVertical)) {
  82. self.transform = CGAffineTransformTranslate(baseTransform, 0, force * self.bounds.size.height);
  83. } else if (HAS_OPT(options, SCShakeOptionsDirectionHorizontal)) {
  84. self.transform = CGAffineTransformTranslate(baseTransform, force * self.bounds.size.width, 0);
  85. } else {
  86. self.transform = CGAffineTransformRotate(baseTransform, force * M_PI_2);
  87. }
  88. }
  89. - (void)_doAnimation:(CGFloat)direction {
  90. SCShakeInfo *shakeInfo = [self shakeInfo];
  91. SCShakeOptions options = shakeInfo.options;
  92. CGFloat completionRatio = shakeInfo.completionRatio;
  93. if (completionRatio > 1) {
  94. completionRatio = 1;
  95. }
  96. if (shakeInfo.reversed) {
  97. completionRatio = 1 - completionRatio;
  98. }
  99. CGFloat interpolationRatio = [self _getInterpolationRatio:completionRatio options:options];
  100. CGFloat force = shakeInfo.force * interpolationRatio * direction;
  101. CGFloat iterationDuration = shakeInfo.iterationDuration;
  102. [UIView animateWithDuration:iterationDuration animations:^{
  103. [self _animate:force shakeInfo:shakeInfo];
  104. } completion:^(BOOL finished) {
  105. if (shakeInfo.shaking) {
  106. BOOL shouldRecurse = YES;
  107. if (shakeInfo.completionRatio > 1) {
  108. if (HAS_OPT(shakeInfo.options, SCShakeOptionsAutoreverse)) {
  109. shakeInfo.reversed = !shakeInfo.reversed;
  110. }
  111. if (shakeInfo.reversed || HAS_OPT(shakeInfo.options, SCShakeOptionsAtEndRestart)) {
  112. shakeInfo.startTime = CACurrentMediaTime();
  113. } else if (!HAS_OPT(shakeInfo.options, SCShakeOptionsAtEndContinue)) {
  114. shouldRecurse = NO;
  115. [self endShake];
  116. }
  117. }
  118. if (shouldRecurse) {
  119. [self _doAnimation:direction * -1];
  120. }
  121. }
  122. }];
  123. }
  124. - (void)endShake {
  125. SCShakeInfo *shakeInfo = [self shakeInfo];
  126. if (shakeInfo.shaking) {
  127. shakeInfo.shaking = NO;
  128. self.transform = shakeInfo.baseTransform;
  129. ShakeCompletionHandler completionHandler = shakeInfo.completionHandler;
  130. shakeInfo.completionHandler = nil;
  131. if (completionHandler != nil) {
  132. completionHandler();
  133. }
  134. }
  135. }
  136. - (BOOL)isShaking {
  137. return [self shakeInfo].shaking;
  138. }
  139. - (SCShakeInfo *)shakeInfo {
  140. SCShakeInfo *shakeInfo = objc_getAssociatedObject(self, ShakeInfoKey);
  141. if (shakeInfo == nil) {
  142. shakeInfo = [SCShakeInfo new];
  143. objc_setAssociatedObject(self, ShakeInfoKey, shakeInfo, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  144. }
  145. return shakeInfo;
  146. }
  147. #pragma Interpolations functions
  148. static CGFloat InterpolateLinearUp(CGFloat input) {
  149. return input;
  150. }
  151. static CGFloat InterpolateLinearDown(CGFloat input) {
  152. return 1 - input;
  153. }
  154. static CGFloat Exp(CGFloat a, int power) {
  155. if (a < 0.5f) {
  156. return (float)pow(a * 2, power) / 2;
  157. } else {
  158. return (float)pow((a - 1) * 2, power) / (power % 2 == 0 ? -2 : 2) + 1;
  159. }
  160. }
  161. static CGFloat InterpolateExpUp(CGFloat input) {
  162. return Exp(input, 4);
  163. }
  164. static CGFloat InterpolateExpDown(CGFloat input) {
  165. return Exp(1 - input, 4);
  166. }
  167. static CGFloat InterpolateNone(CGFloat input) {
  168. return 1;
  169. }
  170. static CGFloat InterpolateRandom(CGFloat input) {
  171. CGFloat randNb = arc4random_uniform(10000);
  172. return randNb / 10000.0;
  173. }
  174. @end