Arquivos
Emosic/App/aff-song/Style/HCSStarRatingView.m
T
Charlie Hewitt 42d34544b3 fix paths
2018-04-26 13:39:03 +01:00

464 linhas
16 KiB
Objective-C
Arquivo Executável

// HCSStarRatingView.m
//
// Copyright (c) 2015 Hugo Sousa
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "HCSStarRatingView.h"
@interface HCSStarRatingView ()
@property (nonatomic, readonly) BOOL shouldUseImages;
@end
@implementation HCSStarRatingView {
CGFloat _minimumValue;
NSUInteger _maximumValue;
CGFloat _value;
UIColor *_starBorderColor;
}
@dynamic minimumValue;
@dynamic maximumValue;
@dynamic value;
@dynamic shouldUseImages;
@dynamic starBorderColor;
#pragma mark - Initialization
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self _customInit];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self _customInit];
}
return self;
}
- (void)_customInit {
self.exclusiveTouch = YES;
_minimumValue = 0;
_maximumValue = 5;
_value = 0;
_spacing = 5.f;
_continuous = YES;
_starBorderWidth = 1.0f;
_emptyStarColor = [UIColor clearColor];
[self _updateAppearanceForState:self.enabled];
}
- (void)setNeedsLayout {
[super setNeedsLayout];
[self setNeedsDisplay];
}
#pragma mark - Properties
- (UIColor *)backgroundColor {
if ([super backgroundColor]) {
return [super backgroundColor];
} else {
return self.isOpaque ? [UIColor whiteColor] : [UIColor clearColor];
};
}
- (CGFloat)minimumValue {
return MAX(_minimumValue, 0);
}
- (void)setMinimumValue:(CGFloat)minimumValue {
if (_minimumValue != minimumValue) {
_minimumValue = minimumValue;
[self setNeedsDisplay];
}
}
- (NSUInteger)maximumValue {
return MAX(_minimumValue, _maximumValue);
}
- (void)setMaximumValue:(NSUInteger)maximumValue {
if (_maximumValue != maximumValue) {
_maximumValue = maximumValue;
[self setNeedsDisplay];
[self invalidateIntrinsicContentSize];
}
}
- (CGFloat)value {
return MIN(MAX(_value, _minimumValue), _maximumValue);
}
- (void)setValue:(CGFloat)value {
[self setValue:value sendValueChangedAction:NO];
}
- (void)setValue:(CGFloat)value sendValueChangedAction:(BOOL)sendAction {
[self willChangeValueForKey:NSStringFromSelector(@selector(value))];
if (_value != value && value >= _minimumValue && value <= _maximumValue) {
_value = value;
if (sendAction) [self sendActionsForControlEvents:UIControlEventValueChanged];
[self setNeedsDisplay];
}
[self didChangeValueForKey:NSStringFromSelector(@selector(value))];
}
- (void)setSpacing:(CGFloat)spacing {
_spacing = MAX(spacing, 0);
[self setNeedsDisplay];
}
- (void)setAllowsHalfStars:(BOOL)allowsHalfStars {
if (_allowsHalfStars != allowsHalfStars) {
_allowsHalfStars = allowsHalfStars;
[self setNeedsDisplay];
}
}
- (void)setAccurateHalfStars:(BOOL)accurateHalfStars {
if (_accurateHalfStars != accurateHalfStars) {
_accurateHalfStars = accurateHalfStars;
[self setNeedsDisplay];
}
}
- (void)setEmptyStarImage:(UIImage *)emptyStarImage {
if (_emptyStarImage != emptyStarImage) {
_emptyStarImage = emptyStarImage;
[self setNeedsDisplay];
}
}
- (void)setHalfStarImage:(UIImage *)halfStarImage {
if (_halfStarImage != halfStarImage) {
_halfStarImage = halfStarImage;
[self setNeedsDisplay];
}
}
- (void)setFilledStarImage:(UIImage *)filledStarImage {
if (_filledStarImage != filledStarImage) {
_filledStarImage = filledStarImage;
[self setNeedsDisplay];
}
}
- (void)setEmptyStarColor:(UIColor *)emptyStarColor {
if (_emptyStarColor != emptyStarColor) {
_emptyStarColor = emptyStarColor;
[self setNeedsDisplay];
}
}
- (void)setStarBorderColor:(UIColor *)starBorderColor {
if (_starBorderColor != starBorderColor) {
_starBorderColor = starBorderColor;
[self setNeedsDisplay];
}
}
- (UIColor *)starBorderColor {
if (_starBorderColor == nil) {
return self.tintColor;
} else {
return _starBorderColor;
}
}
- (void)setStarBorderWidth:(CGFloat)starBorderWidth {
_starBorderWidth = MAX(0, starBorderWidth);
[self setNeedsDisplay];
}
- (BOOL)shouldUseImages {
return (self.emptyStarImage!=nil && self.filledStarImage!=nil);
}
#pragma mark - State
- (void)setEnabled:(BOOL)enabled
{
[self _updateAppearanceForState:enabled];
[super setEnabled:enabled];
}
- (void)_updateAppearanceForState:(BOOL)enabled
{
self.alpha = enabled ? 1.f : .5f;
}
#pragma mark - Image Drawing
- (void)_drawStarImageWithFrame:(CGRect)frame tintColor:(UIColor*)tintColor highlighted:(BOOL)highlighted {
UIImage *image = highlighted ? self.filledStarImage : self.emptyStarImage;
[self _drawImage:image frame:frame tintColor:tintColor];
}
- (void)_drawHalfStarImageWithFrame:(CGRect)frame tintColor:(UIColor *)tintColor {
[self _drawAccurateHalfStarImageWithFrame:frame tintColor:tintColor progress:.5f];
}
- (void)_drawAccurateHalfStarImageWithFrame:(CGRect)frame tintColor:(UIColor *)tintColor progress:(CGFloat)progress {
UIImage *image = self.halfStarImage;
if (image == nil) {
// first draw star outline
[self _drawStarImageWithFrame:frame tintColor:tintColor highlighted:NO];
image = self.filledStarImage;
CGRect imageFrame = CGRectMake(0, 0, image.size.width * image.scale * progress, image.size.height * image.scale);
frame.size.width *= progress;
CGImageRef imageRef = CGImageCreateWithImageInRect(image.CGImage, imageFrame);
UIImage *halfImage = [UIImage imageWithCGImage:imageRef scale:image.scale orientation:image.imageOrientation];
image = [halfImage imageWithRenderingMode:image.renderingMode];
CGImageRelease(imageRef);
}
[self _drawImage:image frame:frame tintColor:tintColor];
}
- (void)_drawImage:(UIImage *)image frame:(CGRect)frame tintColor:(UIColor *)tintColor {
if (image.renderingMode == UIImageRenderingModeAlwaysTemplate) {
[tintColor setFill];
}
[image drawInRect:frame];
}
#pragma mark - Shape Drawing
- (void)_drawStarShapeWithFrame:(CGRect)frame tintColor:(UIColor*)tintColor highlighted:(BOOL)highlighted {
[self _drawAccurateHalfStarShapeWithFrame:frame tintColor:tintColor progress:highlighted ? 1.f : 0.f];
}
- (void)_drawHalfStarShapeWithFrame:(CGRect)frame tintColor:(UIColor *)tintColor {
[self _drawAccurateHalfStarShapeWithFrame:frame tintColor:tintColor progress:.5f];
}
- (void)_drawAccurateHalfStarShapeWithFrame:(CGRect)frame tintColor:(UIColor *)tintColor progress:(CGFloat)progress {
UIBezierPath* starShapePath = UIBezierPath.bezierPath;
[starShapePath moveToPoint: CGPointMake(CGRectGetMinX(frame) + 0.62723 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.37309 * CGRectGetHeight(frame))];
[starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.50000 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.02500 * CGRectGetHeight(frame))];
[starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.37292 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.37309 * CGRectGetHeight(frame))];
[starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.02500 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.39112 * CGRectGetHeight(frame))];
[starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.30504 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.62908 * CGRectGetHeight(frame))];
[starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.20642 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.97500 * CGRectGetHeight(frame))];
[starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.50000 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.78265 * CGRectGetHeight(frame))];
[starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.79358 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.97500 * CGRectGetHeight(frame))];
[starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.69501 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.62908 * CGRectGetHeight(frame))];
[starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.97500 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.39112 * CGRectGetHeight(frame))];
[starShapePath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.62723 * CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.37309 * CGRectGetHeight(frame))];
[starShapePath closePath];
starShapePath.miterLimit = 4;
CGFloat frameWidth = frame.size.width;
CGRect rightRectOfStar = CGRectMake(frame.origin.x + progress * frameWidth, frame.origin.y, frameWidth - progress * frameWidth, frame.size.height);
UIBezierPath *clipPath = [UIBezierPath bezierPathWithRect:CGRectInfinite];
[clipPath appendPath:[UIBezierPath bezierPathWithRect:rightRectOfStar]];
clipPath.usesEvenOddFillRule = YES;
[_emptyStarColor setFill];
[starShapePath fill];
CGContextSaveGState(UIGraphicsGetCurrentContext()); {
[clipPath addClip];
[tintColor setFill];
[starShapePath fill];
}
CGContextRestoreGState(UIGraphicsGetCurrentContext());
[self.starBorderColor setStroke];
starShapePath.lineWidth = _starBorderWidth;
[starShapePath stroke];
}
#pragma mark - Drawing
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, self.backgroundColor.CGColor);
CGContextFillRect(context, rect);
CGFloat availableWidth = rect.size.width - (_spacing * (_maximumValue - 1)) - 2;
CGFloat cellWidth = (availableWidth / _maximumValue);
CGFloat starSide = (cellWidth <= rect.size.height) ? cellWidth : rect.size.height;
starSide = (self.shouldUseImages) ? starSide : (starSide - _starBorderWidth);
for (int idx = 0; idx < _maximumValue; idx++) {
CGPoint center = CGPointMake(cellWidth*idx + cellWidth/2 + _spacing*idx + 1, rect.size.height/2);
CGRect frame = CGRectMake(center.x - starSide/2, center.y - starSide/2, starSide, starSide);
BOOL highlighted = (idx+1 <= ceilf(_value));
if (_allowsHalfStars && highlighted && (idx+1 > _value)) {
if (_accurateHalfStars) {
[self _drawAccurateStarWithFrame:frame tintColor:self.tintColor progress:_value - idx];
}
else {
[self _drawHalfStarWithFrame:frame tintColor:self.tintColor];
}
} else {
[self _drawStarWithFrame:frame tintColor:self.tintColor highlighted:highlighted];
}
}
}
- (void)_drawStarWithFrame:(CGRect)frame tintColor:(UIColor *)tintColor highlighted:(BOOL)highlighted {
if (self.shouldUseImages) {
[self _drawStarImageWithFrame:frame tintColor:tintColor highlighted:highlighted];
} else {
[self _drawStarShapeWithFrame:frame tintColor:tintColor highlighted:highlighted];
}
}
- (void)_drawHalfStarWithFrame:(CGRect)frame tintColor:(UIColor *)tintColor {
if (self.shouldUseImages) {
[self _drawHalfStarImageWithFrame:frame tintColor:tintColor];
} else {
[self _drawHalfStarShapeWithFrame:frame tintColor:tintColor];
}
}
- (void)_drawAccurateStarWithFrame:(CGRect)frame tintColor:(UIColor *)tintColor progress:(CGFloat)progress {
if (self.shouldUseImages) {
[self _drawAccurateHalfStarImageWithFrame:frame tintColor:tintColor progress:progress];
} else {
[self _drawAccurateHalfStarShapeWithFrame:frame tintColor:tintColor progress:progress];
}
}
#pragma mark - Touches
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
if (self.isEnabled) {
[super beginTrackingWithTouch:touch withEvent:event];
if (_shouldBecomeFirstResponder && ![self isFirstResponder]) {
[self becomeFirstResponder];
}
[self _handleTouch:touch];
return YES;
} else {
return NO;
}
}
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
if (self.isEnabled) {
[super continueTrackingWithTouch:touch withEvent:event];
[self _handleTouch:touch];
return YES;
} else {
return NO;
}
}
- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
[super endTrackingWithTouch:touch withEvent:event];
if (_shouldBecomeFirstResponder && [self isFirstResponder]) {
[self resignFirstResponder];
}
[self _handleTouch:touch];
if (!_continuous) {
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
}
- (void)cancelTrackingWithEvent:(UIEvent *)event {
[super cancelTrackingWithEvent:event];
if (_shouldBecomeFirstResponder && [self isFirstResponder]) {
[self resignFirstResponder];
}
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if ([gestureRecognizer.view isEqual:self]) {
return !self.isUserInteractionEnabled;
}
return self.shouldBeginGestureRecognizerBlock ? self.shouldBeginGestureRecognizerBlock(gestureRecognizer) : NO;
}
- (void)_handleTouch:(UITouch *)touch {
CGFloat cellWidth = self.bounds.size.width / _maximumValue;
CGPoint location = [touch locationInView:self];
CGFloat value = location.x / cellWidth;
if (_allowsHalfStars) {
if (_accurateHalfStars) {
value = value;
}
else {
if (value+.5f < ceilf(value)) {
value = floor(value)+.5f;
} else {
value = ceilf(value);
}
}
} else {
value = ceilf(value);
}
[self setValue:value sendValueChangedAction:_continuous];
}
#pragma mark - First responder
- (BOOL)canBecomeFirstResponder {
return _shouldBecomeFirstResponder;
}
#pragma mark - Intrinsic Content Size
- (CGSize)intrinsicContentSize {
CGFloat height = 44.f;
return CGSizeMake(_maximumValue * height + (_maximumValue-1) * _spacing, height);
}
#pragma mark - Accessibility
- (BOOL)isAccessibilityElement {
return YES;
}
- (NSString *)accessibilityLabel {
return [super accessibilityLabel] ?: NSLocalizedString(@"Rating", @"Accessibility label for star rating control.");
}
- (UIAccessibilityTraits)accessibilityTraits {
return ([super accessibilityTraits] | UIAccessibilityTraitAdjustable);
}
- (NSString *)accessibilityValue {
return [@(self.value) description];
}
- (BOOL)accessibilityActivate {
return YES;
}
- (void)accessibilityIncrement {
CGFloat value = self.value + (self.allowsHalfStars ? .5f : 1.f);
[self setValue:value sendValueChangedAction:YES];
}
- (void)accessibilityDecrement {
CGFloat value = self.value - (self.allowsHalfStars ? .5f : 1.f);
[self setValue:value sendValueChangedAction:YES];
}
@end