diff --git a/LCStarRatingView/LCStarRatingView.h b/LCStarRatingView/LCStarRatingView.h index aeb4487..cb2d1b6 100644 --- a/LCStarRatingView/LCStarRatingView.h +++ b/LCStarRatingView/LCStarRatingView.h @@ -32,6 +32,9 @@ #import +/** + * Enumerations to specify rating value type. + */ typedef NS_ENUM(NSInteger, LCStarRatingViewCountingType) { /** @@ -47,14 +50,38 @@ typedef NS_ENUM(NSInteger, LCStarRatingViewCountingType) { */ LCStarRatingViewCountingTypeHalfCutting, }; +/** + * proceeding callback typedefine + */ +typedef void(^LCStarRatingViewProgressDidChangedByUser)(CGFloat progress); +/// Class pre-declaration. +@class LCStarRatingView; +/** + * Delegate for LCStarRatingView + */ +@protocol LCStarRatingViewDelegate +@optional +/** + * Called on star rating view's progress changed by user. + * + * @param starRatingView LCStarRatingView + * @param progress CGFloat progress + */ +-(void) LCStarRatingView:(nonnull LCStarRatingView *) starRatingView progressDidChangedByUser:(CGFloat) progress; -typedef void(^LCStarRatingViewProgressDidChanedByUser)(CGFloat progress); +@end +/** + * Star Rating View + * You can touch and drag to rate for things. + */ @interface LCStarRatingView : UIView +/** + * delegate for LCStarRatingView + */ +@property(nonatomic, weak, nullable) id delegate; -///============================================================================= -/// @name Configuring UI style -///============================================================================= +#pragma mark - Configuring UI style /** Default is LCStarRatingViewCountingTypeInteger . @@ -66,45 +93,51 @@ typedef void(^LCStarRatingViewProgressDidChanedByUser)(CGFloat progress); @property(nonatomic, assign) LCStarRatingViewCountingType type; /** + The margin for starts Default is 5.0 . */ @property(nonatomic, assign) CGFloat starMargin; /** + The highlighted star color for the rating stars Default is Yellow(255, 198, 0) . */ @property(nullable, nonatomic, strong) UIColor * starColor; /** + Border color for highlighted stars Default is nil . */ @property(nullable, nonatomic, strong) UIColor * starBorderColor; /** + Border width for star Default is 0 . */ @property(nonatomic, assign) CGFloat starBorderWidth; /** + The darkened star color. Default is Light gray . */ @property(nullable, nonatomic, strong) UIColor * starPlaceHolderColor; /** + The darkened star border color. Default is nil . */ @property(nullable, nonatomic, strong) UIColor * starPlaceHolderBorderColor; /** + Border width for darkened stars. Default is 0 . */ @property(nonatomic, assign) CGFloat starPlaceHolderBorderWidth; -///============================================================================= -/// @name Main attribute -///============================================================================= +#pragma mark - Main attribute /** + Current rating value Default 3. this value pinned to [0, 5]. */ @property(nonatomic, assign) CGFloat progress; @@ -114,19 +147,10 @@ typedef void(^LCStarRatingViewProgressDidChanedByUser)(CGFloat progress); */ @property(nonatomic, assign) BOOL enabled; - +#pragma mark - Call back /** - Designated initializer. + * Call back when progress changed by user. */ --(nonnull instancetype) init NS_DESIGNATED_INITIALIZER; --(nonnull instancetype) initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER; --(nonnull instancetype) initWithCoder:(nullable NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER; - - -///============================================================================= -/// @name Call back -///============================================================================= - -@property(nonatomic, copy) LCStarRatingViewProgressDidChanedByUser _Nullable progressDidChangedByUser; +@property(nonatomic, copy) LCStarRatingViewProgressDidChangedByUser _Nullable progressDidChangedByUser; @end diff --git a/LCStarRatingView/LCStarRatingView.m b/LCStarRatingView/LCStarRatingView.m index 9e90b46..70e8043 100644 --- a/LCStarRatingView/LCStarRatingView.m +++ b/LCStarRatingView/LCStarRatingView.m @@ -33,7 +33,7 @@ #import "LCStarRatingView.h" - +#define SAFE_INVOKE(f, arg0) do{if(f) { (f)(arg0); }}while(0) @interface __LCStar : UIView @property(nullable, nonatomic, strong) UIColor * starColor; @@ -48,38 +48,21 @@ @interface __LCStar : UIView @implementation __LCStar --(instancetype) init -{ - if(self = [super initWithFrame:CGRectZero]){ - - [self initSelf]; +-(instancetype) initWithFrame:(CGRect)frame { + if(self = [super initWithFrame:frame]) { + [self __LCStarInitSelf]; } - return self; } --(instancetype) initWithFrame:(CGRect)frame -{ - if(self = [super initWithFrame:frame]){ - - [self initSelf]; +-(instancetype) initWithCoder:(NSCoder *)aDecoder { + if(self = [super initWithCoder:aDecoder]) { + [self __LCStarInitSelf]; } - return self; } --(instancetype) initWithCoder:(NSCoder *)aDecoder -{ - if(self = [super initWithCoder:aDecoder]){ - - [self initSelf]; - } - - return self; -} - --(void) initSelf -{ +-(void) __LCStarInitSelf { self.value = 1; self.starColor = [UIColor yellowColor]; self.starBorderColor = [UIColor clearColor]; @@ -88,52 +71,43 @@ -(void) initSelf self.opaque = NO; } --(void) setValue:(CGFloat)value -{ +-(void) setValue:(CGFloat)value { _value = value; [self setNeedsDisplay]; } --(void) setStarColor:(UIColor *)starColor -{ +-(void) setStarColor:(UIColor *)starColor { _starColor = starColor; [self setNeedsDisplay]; } --(void) setStarBorderColor:(UIColor *)starBorderColor -{ +-(void) setStarBorderColor:(UIColor *)starBorderColor { _starBorderColor = starBorderColor; [self setNeedsDisplay]; } --(void) setStarBorderWidth:(CGFloat)starBorderWidth -{ +-(void) setStarBorderWidth:(CGFloat)starBorderWidth { _starBorderWidth = starBorderWidth; [self setNeedsDisplay]; } --(void) setFrame:(CGRect)frame -{ - [super setFrame:frame]; - +-(void) layoutSubviews { + [super layoutSubviews]; [self setNeedsDisplay]; } --(void) setStarImage:(UIColor *)starImage -{ +-(void) setStarImage:(UIColor *)starImage { _starImage = starImage; [self setNeedsDisplay]; } -- (void)drawRect:(CGRect)rect -{ +-(void)drawRect:(CGRect)rect { if (self.starImage) { - [super drawRect:rect]; return; } @@ -202,36 +176,32 @@ @interface LCStarRatingView () @property(nullable, nonatomic, strong) UIImageView * progressImageView; @property(nullable, nonatomic, strong) UIImageView * backgroundImageView; +/** + * start positions of stars + */ +@property(nonatomic, copy) NSArray* starPositionCache; @end @implementation LCStarRatingView #pragma mark - Designated initializer -#pragma mark - --(instancetype) init -{ +-(instancetype) init { if(self = [super initWithFrame:CGRectZero]){ - [self initSelf]; } - return self; } --(instancetype) initWithFrame:(CGRect)frame -{ +-(instancetype) initWithFrame:(CGRect)frame { if(self = [super initWithFrame:frame]){ - [self initSelf]; } - return self; } --(instancetype) initWithCoder:(NSCoder *)aDecoder -{ +-(instancetype) initWithCoder:(NSCoder *)aDecoder { if(self = [super initWithCoder:aDecoder]){ [self initSelf]; @@ -279,19 +249,54 @@ -(void) initSelf [self loadStars]; } -#pragma mark - Action -#pragma mark - +#pragma mark - Actions --(void) tapAction:(UITapGestureRecognizer *)tap -{ - CGPoint tapPoint = [tap locationInView:self.backgroundImageView]; +-(void) didUserChangeProgress { + SAFE_INVOKE(self.progressDidChangedByUser, self.progress); + if(self.delegate && [self.delegate respondsToSelector:@selector(LCStarRatingView:progressDidChangedByUser:)]) { + [self.delegate LCStarRatingView:self progressDidChangedByUser:self.progress]; + } +} +/** + * calculate progress value for location in view + */ +-(CGFloat) progressForLocationInView:(CGPoint) location { + if(location.x < 0) { + return 0; + } + if(location.x > self.bounds.size.width) { + return 5; + } + // trim out star margin distance. + CGFloat totalWidth = self.bounds.size.width; + totalWidth -= 4 * self.starMargin; + double ratio = 5.0 / totalWidth; + // trim out star margin distance for location. + CGFloat touchX = location.x; + CGFloat lastStarX = self.bounds.size.width; + CGFloat starWidth = (self.frame.size.width - self.starMargin * 4) / 5; + for(NSInteger i = self.starPositionCache.count - 1; i >= 0; i--) { + CGFloat starPosition = [self.starPositionCache[i] doubleValue]; + if(starPosition < location.x) { + lastStarX = starPosition + starWidth; + if(touchX > lastStarX) { + // when not touching the last star check whether user's touching margin + if(i + 1 < self.starPositionCache.count && touchX <= [self.starPositionCache[i + 1] doubleValue]) { + touchX = lastStarX; + } + } + touchX -= i * self.starMargin; + break; + } + } + return ratio * touchX; +} - CGFloat progress = 5. / self.frame.size.width * tapPoint.x; - - self.progress = progress; - - if (self.progressDidChangedByUser) { - self.progressDidChangedByUser(self.progress); +-(void) tapAction:(UITapGestureRecognizer *)tap { + CGPoint tapPoint = [tap locationInView:self.backgroundImageView]; + self.progress = [self progressForLocationInView: tapPoint]; + if(tapPoint.x >= 0 && tapPoint.x <= self.bounds.size.width) { + [self didUserChangeProgress]; } } @@ -299,23 +304,19 @@ -(void) panAction:(UIPanGestureRecognizer *)pan { CGPoint panPoint = [pan locationInView:self.backgroundImageView]; - CGFloat progress = 5. / self.frame.size.width * panPoint.x; - - self.progress = progress; - - if (self.progressDidChangedByUser) { - self.progressDidChangedByUser(self.progress); + self.progress = [self progressForLocationInView: panPoint]; + // possible be negative on dragging + if(panPoint.x >= 0 && panPoint.x <= self.bounds.size.width) { + [self didUserChangeProgress]; } } --(void) loadStars -{ +-(void) loadStars { [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(loadStars) object:nil]; [self performSelector:@selector(loadStarsDelay) withObject:nil afterDelay:0]; } --(void) loadStarsDelay -{ +-(void) loadStarsDelay { self.backgroundImageView.frame = self.bounds; self.progressImageView.frame = self.bounds; self.progress = self.progress; @@ -327,17 +328,19 @@ -(void) loadStarsDelay backgroundCache.frame = self.bounds; // Background. + NSMutableArray* starPositionCache = [[NSMutableArray alloc] init]; for (NSInteger i = 0; i < 5; i++) { __LCStar * star = [[__LCStar alloc] init]; star.frame = CGRectMake(starWidth * i + self.starMargin * i, 0, starWidth, self.frame.size.height); + [starPositionCache addObject:@(CGRectGetMinX(star.frame))]; star.starColor = self.starPlaceHolderColor; star.starBorderColor = self.starPlaceHolderBorderColor; star.starBorderWidth = self.starPlaceHolderBorderWidth; [backgroundCache addSubview:star]; } - + self.starPositionCache = starPositionCache; self.backgroundImageView.image = [self convertViewToImage:backgroundCache]; @@ -348,7 +351,7 @@ -(void) loadStarsDelay for (NSInteger i = 0; i < 5; i++) { __LCStar * star = [[__LCStar alloc] init]; - star.frame = CGRectMake(starWidth * i + self.starMargin * i, 0, starWidth, self.frame.size.height); + star.frame = CGRectMake([starPositionCache[i] doubleValue], 0, starWidth, self.frame.size.height); star.starColor = self.starColor; star.starBorderColor = self.starBorderColor; star.starBorderWidth = self.starBorderWidth; @@ -359,8 +362,7 @@ -(void) loadStarsDelay self.progressImageView.image = [self convertViewToImage:starCache]; } --(UIImage *) convertViewToImage:(UIView *)view -{ +-(UIImage *) convertViewToImage:(UIView *)view { UIGraphicsBeginImageContextWithOptions(view.frame.size, NO, [UIScreen mainScreen].scale); [view.layer renderInContext:UIGraphicsGetCurrentContext()]; @@ -374,15 +376,12 @@ -(UIImage *) convertViewToImage:(UIView *)view #pragma mark - Overwrite -#pragma mark - --(void) dealloc -{ +-(void) dealloc { [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(loadStars) object:nil]; } --(void) setEnabled:(BOOL)enabled -{ +-(void) setEnabled:(BOOL)enabled { _enabled = enabled; for (UIGestureRecognizer * ges in self.backgroundImageView.gestureRecognizers) { @@ -391,71 +390,60 @@ -(void) setEnabled:(BOOL)enabled } } --(void) setStarMargin:(CGFloat)starMargin -{ +-(void) setStarMargin:(CGFloat)starMargin { _starMargin = starMargin; [self loadStars]; } --(void) setStarColor:(UIColor *)starColor -{ +-(void) setStarColor:(UIColor *)starColor { _starColor = starColor; [self loadStars]; } --(void) setStarBorderColor:(UIColor *)starBorderColor -{ +-(void) setStarBorderColor:(UIColor *)starBorderColor { _starBorderColor = starBorderColor; [self loadStars]; } --(void) setStarBorderWidth:(CGFloat)starBorderWidth -{ +-(void) setStarBorderWidth:(CGFloat)starBorderWidth { _starBorderWidth = starBorderWidth; [self loadStars]; } --(void) setStarPlaceHolderColor:(UIColor *)starPlaceHolderColor -{ +-(void) setStarPlaceHolderColor:(UIColor *)starPlaceHolderColor { _starPlaceHolderColor = starPlaceHolderColor; [self loadStars]; } --(void) setStarPlaceHolderBorderColor:(UIColor *)starPlaceHolderBorderColor -{ +-(void) setStarPlaceHolderBorderColor:(UIColor *)starPlaceHolderBorderColor { _starPlaceHolderBorderColor = starPlaceHolderBorderColor; [self loadStars]; } --(void) setStarPlaceHolderBorderWidth:(CGFloat)starPlaceHolderBorderWidth -{ +-(void) setStarPlaceHolderBorderWidth:(CGFloat)starPlaceHolderBorderWidth { _starPlaceHolderBorderWidth = starPlaceHolderBorderWidth; [self loadStars]; } --(void) setFrame:(CGRect)frame -{ - [super setFrame:frame]; - +-(void) layoutSubviews { + [super layoutSubviews]; [self loadStars]; } --(void) setType:(LCStarRatingViewCountingType)type -{ +-(void) setType:(LCStarRatingViewCountingType)type { _type = type; self.progress = self.progress; } --(void) setProgress:(CGFloat)progress -{ +-(void) setProgress:(CGFloat)progress { if (progress > 5. || progress < 0) { NSLog(@"Progress could not greater than 5.0"); @@ -472,24 +460,27 @@ -(void) setProgress:(CGFloat)progress break; case LCStarRatingViewCountingTypeHalfCutting: -#define HALF_CUTTING(number) else if (progress > number && progress <= number + 0.5) progress = number + 0.25 - if (progress <= 0.25) progress = 0; - HALF_CUTTING(0.25); - HALF_CUTTING(0.75); - HALF_CUTTING(1.25); - HALF_CUTTING(1.75); - HALF_CUTTING(2.25); - HALF_CUTTING(2.75); - HALF_CUTTING(3.25); - HALF_CUTTING(3.75); - HALF_CUTTING(4.25); - HALF_CUTTING(4.75); +#define HALF_CUTTING(number) if (progress > number - 0.25 && progress <= number + 0.25) progress = number + for(NSInteger i = 0; i <= 10; i++) { + HALF_CUTTING(i * 0.5); + } break; } _progress = progress; - - self.progressImageView.frame = CGRectMake(0, 0, self.frame.size.width / 5. * self.progress, self.frame.size.height); + CGFloat maskViewWidth = self.bounds.size.width; + // trim out star margin distance. + CGFloat totalWidth = self.bounds.size.width; + totalWidth -= 4 * self.starMargin; + CGFloat ratio = totalWidth / 5.0; + for(NSInteger i = 4; i >= 0; i--) { + CGFloat starPosition = [self.starPositionCache[i] doubleValue]; + if(progress >= i) { + maskViewWidth = starPosition + ratio * (progress - i); + break; + } + } + self.progressImageView.frame = CGRectMake(0, 0, maskViewWidth, self.frame.size.height); } diff --git a/LCStarRatingViewExample/LCStarRatingViewExample/LCStarRatingViewExample/ViewController.m b/LCStarRatingViewExample/LCStarRatingViewExample/LCStarRatingViewExample/ViewController.m index c8d4aa0..e7c6957 100644 --- a/LCStarRatingViewExample/LCStarRatingViewExample/LCStarRatingViewExample/ViewController.m +++ b/LCStarRatingViewExample/LCStarRatingViewExample/LCStarRatingViewExample/ViewController.m @@ -9,7 +9,7 @@ #import "ViewController.h" #import "LCStarRatingView.h" -@interface ViewController () +@interface ViewController () @property(nonatomic, strong) LCStarRatingView * ratingView; @@ -25,6 +25,7 @@ - (void)viewDidLoad { self.ratingView = [[LCStarRatingView alloc] init]; self.ratingView.frame = CGRectMake(100, 100, 200, 200); + self.ratingView.delegate = self; [self.view addSubview:self.ratingView]; @@ -81,4 +82,8 @@ -(IBAction)setType:(UISegmentedControl *)sender self.ratingView.type = sender.selectedSegmentIndex; } +-(void) LCStarRatingView:(LCStarRatingView *)starRatingView progressDidChangedByUser:(CGFloat)progress { + NSLog(@"Delegate output: %@", @(progress)); +} + @end diff --git a/README.md b/README.md index 4559927..b441133 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,24 @@ ![badge-pod] ![badge-languages] ![badge-platforms] ![badge-mit] -###Features +### Features > * No images. > * It supports multiple UI Setting. > * It supports gestures and call back block. > * It supports counting type switch. -###Counting type +### Value style + +There's three kinds of styles for you to choose. + +- LCStarRatingViewCountingTypeInteger [0, 1, 2, 3, 4, 5] +- LCStarRatingViewCountingTypeFloat [0, 0.1, ... 0.4, ... 1, 1.1, ... 4.9, 5] +- LCStarRatingViewCountingTypeHalfCutting [0, 0.5, 1, 1.5, 2, ...] + +To specify the value style you just simply code this: + ```objc -/** - * LCStarRatingViewCountingTypeInteger [0, 1, 2, 3, 4, 5] - * LCStarRatingViewCountingTypeFloat [0, 0.1, ... 0.4, ... 1, 1.1, ... 4.9, 5] - * LCStarRatingViewCountingTypeHalfCutting [0, 0.5, 1, 1.5, 2, ...] - */ +ratingView.type = LCStarRatingViewCountingTypeInteger; ``` ###Preview (GIF) @@ -22,19 +27,32 @@ ![image](https://github.com/titman/Pictures-of-the-warehouse/blob/master/LCStarRatingView1.gif?raw=false) - -###Call back +### Callback + +There're two ways to listen the changing of star value. + +#### Callback Functor ```objc ratingView.progressDidChangedByUser = ^(CGFloat progress){ - // to do something. }; ``` -###Update +#### Delegate - - 1.0 - * First commit. +Just conform protocol ```LCStarRatingViewDelegate```. Then redirect view's delegate to that object. + +```objc +ratingView.delegate = self; +``` +Implement ```[LCStarRatingViewDelegate LCStarRatingView:progressDidChangedByUser:]``` method. Then you are good to go. + +```objc +-(void) LCStarRatingView:(LCStarRatingView *)starRatingView progressDidChangedByUser:(CGFloat)progress { + NSLog(@"Delegate output: %@", @(progress)); +} +``` [badge-platforms]: https://img.shields.io/badge/platforms-iOS-lightgrey.svg