FHSRangeSlider.m 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. //
  2. // FHSRangeSlider.m
  3. // FLEX
  4. //
  5. // Created by Tanner Bennett on 1/7/20.
  6. // Copyright © 2020 FLEX Team. All rights reserved.
  7. //
  8. #import "FHSRangeSlider.h"
  9. #import "FLEXResources.h"
  10. #import "FLEXUtility.h"
  11. @interface FHSRangeSlider ()
  12. @property (nonatomic, readonly) UIImageView *track;
  13. @property (nonatomic, readonly) UIImageView *fill;
  14. @property (nonatomic, readonly) UIImageView *leftHandle;
  15. @property (nonatomic, readonly) UIImageView *rightHandle;
  16. @property (nonatomic, getter=isTrackingLeftHandle) BOOL trackingLeftHandle;
  17. @property (nonatomic, getter=isTrackingRightHandle) BOOL trackingRightHandle;
  18. @end
  19. @implementation FHSRangeSlider
  20. #pragma mark - Initialization
  21. - (instancetype)initWithFrame:(CGRect)frame {
  22. self = [super initWithFrame:frame];
  23. if (self) {
  24. _allowedMaxValue = 1.f;
  25. _maxValue = 1.f;
  26. [self initSubviews];
  27. }
  28. return self;
  29. }
  30. - (void)initSubviews {
  31. self.userInteractionEnabled = YES;
  32. UIImageView * (^newSubviewImageView)() = ^UIImageView *(UIImage *image) {
  33. UIImageView *iv = [UIImageView new];
  34. iv.image = image;
  35. // iv.userInteractionEnabled = YES;
  36. [self addSubview:iv];
  37. return iv;
  38. };
  39. _track = newSubviewImageView(FLEXResources.rangeSliderTrack);
  40. _fill = newSubviewImageView(FLEXResources.rangeSliderFill);
  41. _leftHandle = newSubviewImageView(FLEXResources.rangeSliderLeftHandle);
  42. _rightHandle = newSubviewImageView(FLEXResources.rangeSliderRightHandle);
  43. }
  44. #pragma mark - Setters / Private
  45. - (CGFloat)valueAt:(CGFloat)x {
  46. CGFloat minX = self.leftHandle.image.size.width;
  47. CGFloat maxX = self.bounds.size.width - self.rightHandle.image.size.width;
  48. CGFloat cappedX = MIN(MAX(x, minX), maxX);
  49. CGFloat delta = maxX - minX;
  50. CGFloat maxDelta = self.allowedMaxValue - self.allowedMinValue;
  51. return ((delta > 0) ? (cappedX - minX) / delta : 0) * maxDelta + self.allowedMinValue;
  52. }
  53. - (void)setAllowedMinValue:(CGFloat)allowedMinValue {
  54. _allowedMinValue = allowedMinValue;
  55. if (self.minValue < self.allowedMaxValue) {
  56. self.minValue = self.allowedMaxValue;
  57. } else {
  58. [self setNeedsLayout];
  59. }
  60. }
  61. - (void)setAllowedMaxValue:(CGFloat)allowedMaxValue {
  62. _allowedMaxValue = allowedMaxValue;
  63. if (self.maxValue > self.allowedMaxValue) {
  64. self.maxValue = self.allowedMaxValue;
  65. } else {
  66. [self valuesChanged:NO];
  67. }
  68. }
  69. - (void)setMinValue:(CGFloat)minValue {
  70. _minValue = minValue;
  71. [self valuesChanged:YES];
  72. }
  73. - (void)setMaxValue:(CGFloat)maxValue {
  74. _maxValue = maxValue;
  75. [self valuesChanged:YES];
  76. }
  77. - (void)valuesChanged:(BOOL)sendActions {
  78. if (NSThread.isMainThread) {
  79. if (sendActions) {
  80. [self sendActionsForControlEvents:UIControlEventValueChanged];
  81. }
  82. [self setNeedsLayout];
  83. }
  84. }
  85. #pragma mark - Overrides
  86. - (CGSize)intrinsicContentSize {
  87. return CGSizeMake(UIViewNoIntrinsicMetric, self.leftHandle.image.size.height);
  88. }
  89. - (void)layoutSubviews {
  90. [super layoutSubviews];
  91. CGSize lhs = self.leftHandle.image.size;
  92. CGSize rhs = self.rightHandle.image.size;
  93. CGSize trackSize = self.track.image.size;
  94. CGFloat delta = self.allowedMaxValue - self.allowedMinValue;
  95. CGFloat minPercent, maxPercent;
  96. if (delta <= 0) {
  97. minPercent = maxPercent = 0;
  98. } else {
  99. minPercent = MAX(0, (self.minValue - self.allowedMinValue) / delta);
  100. maxPercent = MAX(minPercent, (self.maxValue - self.allowedMinValue) / delta);
  101. }
  102. CGFloat rangeSliderWidth = self.bounds.size.width - lhs.width - rhs.width;
  103. self.leftHandle.frame = FLEXRectMake(
  104. rangeSliderWidth * minPercent,
  105. CGRectGetMidY(self.bounds) - (lhs.height / 2.f) + 3.f,
  106. lhs.width,
  107. lhs.height
  108. );
  109. self.rightHandle.frame = FLEXRectMake(
  110. lhs.width + (rangeSliderWidth * maxPercent),
  111. CGRectGetMidY(self.bounds) - (rhs.height / 2.f) + 3.f,
  112. rhs.width,
  113. rhs.height
  114. );
  115. self.track.frame = FLEXRectMake(
  116. lhs.width / 2.f,
  117. CGRectGetMidY(self.bounds) - trackSize.height / 2.f,
  118. self.bounds.size.width - (lhs.width / 2.f) - (rhs.width / 2.f),
  119. trackSize.height
  120. );
  121. self.fill.frame = FLEXRectMake(
  122. CGRectGetMidX(self.leftHandle.frame),
  123. CGRectGetMinY(self.track.frame),
  124. CGRectGetMidX(self.rightHandle.frame) - CGRectGetMidX(self.leftHandle.frame),
  125. self.track.frame.size.height
  126. );
  127. }
  128. - (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
  129. CGPoint loc = [touch locationInView:self];
  130. if (CGRectContainsPoint(self.leftHandle.frame, loc)) {
  131. self.trackingLeftHandle = YES;
  132. self.trackingRightHandle = NO;
  133. } else if (CGRectContainsPoint(self.rightHandle.frame, loc)) {
  134. self.trackingLeftHandle = NO;
  135. self.trackingRightHandle = YES;
  136. } else {
  137. return NO;
  138. }
  139. return YES;
  140. }
  141. - (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
  142. CGPoint loc = [touch locationInView:self];
  143. if (self.isTrackingLeftHandle) {
  144. self.minValue = MIN(MAX(self.allowedMinValue, [self valueAt:loc.x]), self.maxValue);
  145. } else if (self.isTrackingRightHandle) {
  146. self.maxValue = MAX(MIN(self.allowedMaxValue, [self valueAt:loc.x]), self.minValue);
  147. } else {
  148. return NO;
  149. }
  150. [self setNeedsLayout];
  151. [self layoutIfNeeded];
  152. return YES;
  153. }
  154. - (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
  155. self.trackingLeftHandle = NO;
  156. self.trackingRightHandle = NO;
  157. }
  158. - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
  159. return NO;
  160. }
  161. @end