HCSStarRatingView.m 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. // HCSStarRatingView.m
  2. //
  3. // Copyright (c) 2015 Hugo Sousa
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining a copy
  6. // of this software and associated documentation files (the "Software"), to deal
  7. // in the Software without restriction, including without limitation the rights
  8. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. // copies of the Software, and to permit persons to whom the Software is
  10. // furnished to do so, subject to the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be included in
  13. // all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. // THE SOFTWARE.
  22. #import "HCSStarRatingView.h"
  23. @implementation HCSStarRatingView {
  24. NSUInteger _minimumValue;
  25. NSUInteger _maximumValue;
  26. CGFloat _value;
  27. NSUInteger _previousValue;
  28. }
  29. @dynamic minimumValue;
  30. @dynamic maximumValue;
  31. @dynamic value;
  32. #pragma mark - Initialization
  33. - (instancetype)initWithFrame:(CGRect)frame {
  34. self = [super initWithFrame:frame];
  35. if (self) {
  36. [self _customInit];
  37. }
  38. return self;
  39. }
  40. - (id)initWithCoder:(NSCoder *)aDecoder {
  41. self = [super initWithCoder:aDecoder];
  42. if (self) {
  43. [self _customInit];
  44. }
  45. return self;
  46. }
  47. - (void)_customInit {
  48. self.backgroundColor = [UIColor clearColor];
  49. self.exclusiveTouch = YES;
  50. _minimumValue = 1;
  51. _maximumValue = 5;
  52. _value = 0;
  53. _spacing = 5.f;
  54. }
  55. #pragma mark - Properties
  56. - (NSUInteger)minimumValue {
  57. return MAX(_minimumValue, 0);
  58. }
  59. - (void)setMinimumValue:(NSUInteger)minimumValue {
  60. if (_minimumValue != minimumValue) {
  61. _minimumValue = minimumValue;
  62. [self setNeedsDisplay];
  63. }
  64. }
  65. - (NSUInteger)maximumValue {
  66. return MAX(_minimumValue, _maximumValue);
  67. }
  68. - (void)setMaximumValue:(NSUInteger)maximumValue {
  69. if (_maximumValue != maximumValue) {
  70. _maximumValue = maximumValue;
  71. [self setNeedsDisplay];
  72. [self invalidateIntrinsicContentSize];
  73. }
  74. }
  75. - (CGFloat)value {
  76. return MIN(MAX(_value, _minimumValue), _maximumValue);
  77. }
  78. - (void)setValue:(CGFloat)value {
  79. if (_value != value) {
  80. _value = value;
  81. [self sendActionsForControlEvents:UIControlEventValueChanged];
  82. [self setNeedsDisplay];
  83. }
  84. }
  85. - (void)setSpacing:(CGFloat)spacing {
  86. _spacing = MAX(spacing, 0);
  87. [self setNeedsDisplay];
  88. }
  89. - (void)setAllowsHalfStars:(BOOL)allowsHalfStars {
  90. if (_allowsHalfStars != allowsHalfStars) {
  91. _allowsHalfStars = allowsHalfStars;
  92. [self setNeedsDisplay];
  93. }
  94. }
  95. #pragma mark - Drawing
  96. - (void)_drawStarWithFrame:(CGRect)frame tintColor:(UIColor*)tintColor highlighted:(BOOL)highlighted {
  97. UIBezierPath* starShapePath = UIBezierPath.bezierPath;
  98. [starShapePath moveToPoint: CGPointMake(CGRectGetMinX(frame) + 0.62723 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.37309 * CGRectGetHeight(frame))];
  99. [starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.50000 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.02500 * CGRectGetHeight(frame))];
  100. [starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.37292 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.37309 * CGRectGetHeight(frame))];
  101. [starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.02500 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.39112 * CGRectGetHeight(frame))];
  102. [starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.30504 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.62908 * CGRectGetHeight(frame))];
  103. [starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.20642 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.97500 * CGRectGetHeight(frame))];
  104. [starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.50000 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.78265 * CGRectGetHeight(frame))];
  105. [starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.79358 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.97500 * CGRectGetHeight(frame))];
  106. [starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.69501 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.62908 * CGRectGetHeight(frame))];
  107. [starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.97500 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.39112 * CGRectGetHeight(frame))];
  108. [starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.62723 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.37309 * CGRectGetHeight(frame))];
  109. [starShapePath closePath];
  110. starShapePath.miterLimit = 4;
  111. if (highlighted) {
  112. [tintColor setFill];
  113. [starShapePath fill];
  114. }
  115. [tintColor setStroke];
  116. starShapePath.lineWidth = 1;
  117. [starShapePath stroke];
  118. }
  119. - (void)_drawHalfStarWithFrame:(CGRect)frame tintcolor:(UIColor *)tintColor highlighted:(BOOL)highlighted {
  120. UIBezierPath* starShapePath = UIBezierPath.bezierPath;
  121. [starShapePath moveToPoint: CGPointMake(CGRectGetMinX(frame) + 0.50000 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.02500 * CGRectGetHeight(frame))];
  122. [starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.37292 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.37309 * CGRectGetHeight(frame))];
  123. [starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.02500 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.39112 * CGRectGetHeight(frame))];
  124. [starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.30504 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.62908 * CGRectGetHeight(frame))];
  125. [starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.20642 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.97500 * CGRectGetHeight(frame))];
  126. [starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.50000 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.78265 * CGRectGetHeight(frame))];
  127. [starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.50000 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.02500 * CGRectGetHeight(frame))];
  128. [starShapePath closePath];
  129. starShapePath.miterLimit = 4;
  130. if (highlighted) {
  131. [tintColor setFill];
  132. [starShapePath fill];
  133. }
  134. [tintColor setStroke];
  135. starShapePath.lineWidth = 1;
  136. [starShapePath stroke];
  137. }
  138. - (void)drawRect:(CGRect)rect {
  139. CGContextRef context = UIGraphicsGetCurrentContext();
  140. CGContextSetFillColorWithColor(context, self.backgroundColor.CGColor);
  141. CGContextFillRect(context, rect);
  142. CGFloat availableWidth = rect.size.width - (_spacing * (_maximumValue + 1));
  143. CGFloat cellWidth = (availableWidth / _maximumValue);
  144. CGFloat starSide = (cellWidth <= rect.size.height) ? cellWidth : rect.size.height;
  145. for (int idx = 0; idx < _maximumValue; idx++) {
  146. CGPoint center = CGPointMake(cellWidth*idx + cellWidth/2 + _spacing*(idx+1), rect.size.height/2);
  147. CGRect frame = CGRectMake(center.x - starSide/2, center.y - starSide/2, starSide, starSide);
  148. BOOL highlighted = (idx+1 <= ceilf(_value));
  149. BOOL halfStar = highlighted ? (idx+1 > _value) : NO;
  150. if (halfStar && _allowsHalfStars) {
  151. [self _drawStarWithFrame:frame tintColor:self.tintColor highlighted:NO];
  152. [self _drawHalfStarWithFrame:frame tintcolor:self.tintColor highlighted:highlighted];
  153. } else {
  154. [self _drawStarWithFrame:frame tintColor:self.tintColor highlighted:highlighted];
  155. }
  156. }
  157. }
  158. - (void)setNeedsLayout {
  159. [super setNeedsLayout];
  160. [self setNeedsDisplay];
  161. }
  162. #pragma mark - Touches
  163. - (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
  164. [super beginTrackingWithTouch:touch withEvent:event];
  165. if (![self isFirstResponder]) {
  166. [self becomeFirstResponder];
  167. }
  168. _previousValue = _value;
  169. [self _handleTouch:touch];
  170. return YES;
  171. }
  172. - (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
  173. [super continueTrackingWithTouch:touch withEvent:event];
  174. [self _handleTouch:touch];
  175. return YES;
  176. }
  177. - (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
  178. [super endTrackingWithTouch:touch withEvent:event];
  179. if ([self isFirstResponder]) {
  180. [self resignFirstResponder];
  181. }
  182. [self _handleTouch:touch];
  183. if (_value != _previousValue) {
  184. [self sendActionsForControlEvents:UIControlEventValueChanged];
  185. }
  186. }
  187. - (void)cancelTrackingWithEvent:(UIEvent *)event {
  188. [super cancelTrackingWithEvent:event];
  189. if ([self isFirstResponder]) {
  190. [self resignFirstResponder];
  191. }
  192. }
  193. - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
  194. return !self.isUserInteractionEnabled;
  195. }
  196. - (void)_handleTouch:(UITouch *)touch {
  197. CGFloat cellWidth = self.bounds.size.width / _maximumValue;
  198. CGPoint location = [touch locationInView:self];
  199. CGFloat value = location.x / cellWidth;
  200. if (_allowsHalfStars && value+.5f < ceilf(value)) {
  201. _value = floor(value)+.5f;
  202. } else {
  203. _value = ceilf(value);
  204. }
  205. [self setNeedsDisplay];
  206. }
  207. #pragma mark - First responder
  208. - (BOOL)canBecomeFirstResponder {
  209. return YES;
  210. }
  211. #pragma mark - Intrinsic Content Size
  212. - (CGSize)intrinsicContentSize {
  213. CGFloat height = 44.f;
  214. return CGSizeMake(_maximumValue * height + (_maximumValue+1) * _spacing, height);
  215. }
  216. @end