NAKPlaybackIndicatorContentView.m 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. //
  2. // NAKPlaybackIndicatorContentView.m
  3. // PlaybackIndicator
  4. //
  5. // Created by Yuji Nakayama on 1/28/14.
  6. // Copyright (c) 2014 Yuji Nakayama. All rights reserved.
  7. //
  8. #import "NAKPlaybackIndicatorContentView.h"
  9. static const CFTimeInterval kMinBaseOscillationPeriod = 0.6;
  10. static const CFTimeInterval kMaxBaseOscillationPeriod = 0.8;
  11. static NSString* const kOscillationAnimationKey = @"oscillation";
  12. static const CFTimeInterval kDecayDuration = 0.3;
  13. static NSString* const kDecayAnimationKey = @"decay";
  14. @interface NAKPlaybackIndicatorContentView ()
  15. @property (nonatomic, readonly) NSArray<CALayer*>* barLayers;
  16. @property (nonatomic, assign) BOOL hasInstalledConstraints;
  17. @end
  18. @implementation NAKPlaybackIndicatorContentView
  19. #pragma mark - Initialization
  20. - (instancetype)initWithStyle:(NAKPlaybackIndicatorViewStyle*)style
  21. {
  22. self = [super init];
  23. if (self) {
  24. _style = style;
  25. self.translatesAutoresizingMaskIntoConstraints = NO;
  26. [self prepareBarLayers];
  27. [self tintColorDidChange];
  28. [self setNeedsUpdateConstraints];
  29. }
  30. return self;
  31. }
  32. - (void)prepareBarLayers
  33. {
  34. NSMutableArray<CALayer*>* barLayers = [NSMutableArray array];
  35. CGFloat xOffset = 0.0;
  36. for (NSInteger i = 0; i < self.style.barCount; i++) {
  37. CALayer* layer = [self createBarLayerWithXOffset:xOffset];
  38. [barLayers addObject:layer];
  39. [self.layer addSublayer:layer];
  40. xOffset = CGRectGetMaxX(layer.frame) + self.style.actualBarSpacing;
  41. }
  42. _barLayers = barLayers;
  43. }
  44. - (CALayer*)createBarLayerWithXOffset:(CGFloat)xOffset
  45. {
  46. CALayer* layer = [CALayer layer];
  47. layer.anchorPoint = CGPointMake(0.0, 1.0); // At the bottom-left corner
  48. layer.position = CGPointMake(xOffset, self.style.maxPeakBarHeight); // In superview's coordinate
  49. layer.bounds = CGRectMake(0.0, 0.0, self.style.barWidth, self.style.idleBarHeight);// In its own coordinate
  50. layer.allowsEdgeAntialiasing = YES;
  51. return layer;
  52. }
  53. #pragma mark - Tint Color
  54. - (void)tintColorDidChange
  55. {
  56. for (CALayer* layer in self.barLayers) {
  57. layer.backgroundColor = self.tintColor.CGColor;
  58. }
  59. }
  60. #pragma mark - Auto Layout
  61. - (CGSize)intrinsicContentSize
  62. {
  63. CGRect unionFrame = CGRectZero;
  64. for (CALayer* layer in self.barLayers) {
  65. unionFrame = CGRectUnion(unionFrame, layer.frame);
  66. }
  67. return unionFrame.size;
  68. }
  69. - (void)updateConstraints
  70. {
  71. if (!self.hasInstalledConstraints) {
  72. CGSize size = [self intrinsicContentSize];
  73. [self addConstraint:[NSLayoutConstraint constraintWithItem:self
  74. attribute:NSLayoutAttributeWidth
  75. relatedBy:NSLayoutRelationEqual
  76. toItem:nil
  77. attribute:NSLayoutAttributeNotAnAttribute
  78. multiplier:0.0
  79. constant:size.width]];
  80. [self addConstraint:[NSLayoutConstraint constraintWithItem:self
  81. attribute:NSLayoutAttributeHeight
  82. relatedBy:NSLayoutRelationEqual
  83. toItem:nil
  84. attribute:NSLayoutAttributeNotAnAttribute
  85. multiplier:0.0
  86. constant:size.height]];
  87. self.hasInstalledConstraints = YES;
  88. }
  89. [super updateConstraints];
  90. }
  91. #pragma mark - Animations
  92. - (void)startOscillation
  93. {
  94. CFTimeInterval basePeriod = kMinBaseOscillationPeriod + (drand48() * (kMaxBaseOscillationPeriod - kMinBaseOscillationPeriod));
  95. for (CALayer* layer in self.barLayers) {
  96. [self startOscillatingBarLayer:layer basePeriod:basePeriod];
  97. }
  98. }
  99. - (void)stopOscillation
  100. {
  101. for (CALayer* layer in self.barLayers) {
  102. [layer removeAnimationForKey:kOscillationAnimationKey];
  103. }
  104. }
  105. - (BOOL)isOscillating
  106. {
  107. CAAnimation* animation = [self.barLayers.firstObject animationForKey:kOscillationAnimationKey];
  108. return (animation != nil);
  109. }
  110. - (void)startDecay
  111. {
  112. for (CALayer* layer in self.barLayers) {
  113. [self startDecayingBarLayer:layer];
  114. }
  115. }
  116. - (void)stopDecay
  117. {
  118. for (CALayer* layer in self.barLayers) {
  119. [layer removeAnimationForKey:kDecayAnimationKey];
  120. }
  121. }
  122. - (void)startOscillatingBarLayer:(CALayer*)layer basePeriod:(CFTimeInterval)basePeriod
  123. {
  124. // arc4random_uniform() will return a uniformly distributed random number **less** upper_bound.
  125. CGFloat peakHeight = self.style.minPeakBarHeight + arc4random_uniform(self.style.maxPeakBarHeight - self.style.minPeakBarHeight + 1);
  126. CGRect toBounds = layer.bounds;
  127. toBounds.size.height = peakHeight;
  128. CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"bounds"];
  129. animation.fromValue = [NSValue valueWithCGRect:layer.bounds];
  130. animation.toValue = [NSValue valueWithCGRect:toBounds];
  131. animation.repeatCount = HUGE_VALF; // Forever
  132. animation.autoreverses = YES;
  133. animation.duration = (basePeriod / 2) * (self.style.maxPeakBarHeight / peakHeight);
  134. animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
  135. [layer addAnimation:animation forKey:kOscillationAnimationKey];
  136. }
  137. - (void)startDecayingBarLayer:(CALayer*)layer
  138. {
  139. CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"bounds"];
  140. animation.fromValue = [NSValue valueWithCGRect:((CALayer*)layer.presentationLayer).bounds];
  141. animation.toValue = [NSValue valueWithCGRect:layer.bounds];
  142. animation.duration = kDecayDuration;
  143. animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
  144. [layer addAnimation:animation forKey:kDecayAnimationKey];
  145. }
  146. @end