iOS-实现星级评分(star score)

我们知道,很多app都有星星评分的功能,特别是商城app,需要你对商品质量、发货速度、服务态度等进行打分。
项目开发的app正好也需要这个功能,于是自己进行了封装,使用起来也是很简单,满足大部分功能需要,功能如下:

  • 可全星打分
  • 可半星打分
  • 可不完整星打分
  • 可点击、可滑动打分
  • 可设置星星数量、大小、间隔等
    一切根据你的需要来定制,看一下效果图。


    星星打分效果图.png

使用也是非常简单的

GBStarRateView *starRateView = [[GBStarRateView alloc] initWithFrame:CGRectMake(150, 30, 200, 10) style:GBStarRateViewStyleHalfStar numberOfStars:5 isAnimation:YES delegate:self];
            [cell.contentView addSubview:starRateView];
            starRateView.numberOfStars = 5;
            starRateView.style = GBStarRateViewStyleHalfStar;

首先看.h头文件

typedef NS_ENUM(NSInteger, GBStarRateViewStyle){
    
    GBStarRateViewStyleWholeStar = 0,//全星评分
    GBStarRateViewStyleHalfStar = 1, //可半星评分
    GBStarRateViewStyleIncompleteStar = 2,//不完整星评分
};
@class GBStarRateView;
@protocol GBStarRateViewDelegate 
@optional
- (void)starRateView:(GBStarRateView *)starRateView didSelecteStarAtStarRate:(CGFloat)starRate;
@end

typedef void(^GBStarRateDidSelectStarBlock)(GBStarRateView *starRateView, CGFloat starRate);

@interface GBStarRateView : UIView

@property (nonatomic, assign) GBStarRateViewStyle style; //星星评分样式
@property (nonatomic, weak) id  delegate; //代理
@property (nonatomic, assign) NSInteger numberOfStars; //星星数量 默认为5
@property (nonatomic, assign) CGFloat currentStarRate; //当前打分 默认为0.0
@property (nonatomic, assign) CGFloat spacingBetweenStars; //星星间隔 默认为10
@property (nonatomic, assign) CGSize starSize; //星星尺寸 默认 {24,24}
@property (nonatomic, strong) UIImage *starImage; //未选中star image 有默认图片
@property (nonatomic, strong) UIImage *currentStarImage; //选中star image 有默认图片
@property (nonatomic, assign) BOOL isAnimation; //是否动画 默认YES
@property (nonatomic, assign) BOOL allowSlideScore; //是否滑动打分 默认NO 适用于不完整星星打分
@property (nonatomic, assign) BOOL allowClickScore; //是否点击打分 默认YES
@property (nonatomic, copy) GBStarRateDidSelectStarBlock didSelectStarBlock;//点击或滑动打分的回调

- (instancetype)initWithFrame:(CGRect)frame style:(GBStarRateViewStyle)style numberOfStars:(NSInteger)numbersOfStars isAnimation:(BOOL)isAnimation delegate:(id )delegate;

- (instancetype)initWithFrame:(CGRect)frame style:(GBStarRateViewStyle)style numberOfStars:(NSInteger)numbersOfStars isAnimation:(BOOL)isAnimation finish:(GBStarRateDidSelectStarBlock)finish;
@end

然后再看一下.m的实现

static CGFloat const kAnimatinDuration = 0.3;

@interface GBStarRateView ()

@property (nonatomic, strong) NSMutableArray  *starsContentViews;

@end


@implementation GBStarRateView

@synthesize starSize = _starSize;

- (instancetype)initWithFrame:(CGRect)frame style:(GBStarRateViewStyle)style numberOfStars:(NSInteger)numbersOfStars isAnimation:(BOOL)isAnimation finish:(GBStarRateDidSelectStarBlock)finish {
    
    if (self = [super initWithFrame:frame]) {
        [self config];
        self.style = style;
        self.numberOfStars = numbersOfStars;
        self.isAnimation = isAnimation;
        self.didSelectStarBlock = finish;
    }
    return self;
}

- (instancetype)initWithFrame:(CGRect)frame style:(GBStarRateViewStyle)style numberOfStars:(NSInteger)numbersOfStars isAnimation:(BOOL)isAnimation delegate:(id)delegate {
    
    if (self = [super initWithFrame:frame]) {
        [self config];
        self.style = style;
        self.numberOfStars = numbersOfStars;
        self.isAnimation = isAnimation;
        self.delegate = delegate;
    }
    return self;
}

- (id)init {
    
    if (self = [super init]) {
        
        [self config];
    }
    return self;
}

- (id)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        
        [self config];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    
    if (self = [super initWithCoder:aDecoder]) {
        
        [self config];
    }
    return self;
}
#pragma mark - 基本配置
- (void)config {
    
    self.style = GBStarRateViewStyleWholeStar;
    self.numberOfStars = 5;
    self.spacingBetweenStars = 10;
    _starSize = CGSizeMake(24, 24);
    self.currentStarImage = [UIImage imageNamed:@"ic_star_selected"];
    self.starImage = [UIImage imageNamed:@"ic_star_default"];
    self.isAnimation = YES;
    self.allowClickScore = YES;
    self.allowSlideScore = NO;
}

#pragma mark - 创建视图
- (void)resetStarsContentView {
    
    for (UIView *starContentView in self.starsContentViews) {
        [starContentView removeFromSuperview];
    }
    [self.starsContentViews removeAllObjects];
    
    [self createStarsContentView:self.starImage starRate:_numberOfStars];
    [self createStarsContentView:self.currentStarImage starRate:_currentStarRate];
}

- (void)createStarsContentView:(UIImage *)starImage starRate:(CGFloat)starRate {
    
    if (self.numberOfStars == 0) {
        return;
    }
    CGRect frame = [self frameForStarsContentViewAtCurrentStarRate:starRate];
    UIView *starsContentView = [[UIView alloc] initWithFrame:frame];
    starsContentView.clipsToBounds = YES;//必须要设置,不设试试效果
    [self addSubview:starsContentView];
    
    for (int i = 0; i < self.numberOfStars; i++) {
        
        UIImageView *imageView = [[UIImageView alloc] initWithImage:starImage];
        imageView.frame = CGRectMake((self.starSize.width + self.spacingBetweenStars) * i, 0, self.starSize.width, self.starSize.height);
        imageView.contentMode = UIViewContentModeScaleAspectFit;
        [starsContentView addSubview:imageView];
    }
    
    [self.starsContentViews addObject:starsContentView];
}
#pragma mark - StarsContentView frame
- (CGRect)frameForStarsContentViewAtCurrentStarRate:(CGFloat)currentStarRate {
    
    NSInteger index = (NSInteger)floor(currentStarRate);
    CGFloat w = (self.starSize.width + self.spacingBetweenStars) * index + (currentStarRate - index) * self.starSize.width;
    CGFloat x = (CGRectGetWidth(self.bounds) - [self sizeForNumberOfStar:self.numberOfStars].width) * 0.5;
    CGFloat y = (CGRectGetHeight(self.bounds) - [self sizeForNumberOfStar:self.numberOfStars].height) * 0.5;
    CGFloat h = self.starSize.height;
    return CGRectMake(x, y, w, h);
}

#pragma mark - setter
- (void)setNumberOfStars:(NSInteger)numberOfStars {
    
    if (_numberOfStars != numberOfStars) {
        _numberOfStars = numberOfStars;
        [self resetStarsContentView];
    }
}

- (void)setSpacingBetweenStars:(CGFloat)spacingBetweenStars {
    
    if (_spacingBetweenStars != spacingBetweenStars) {
        _spacingBetweenStars = spacingBetweenStars;
        [self resetStarsContentView];
    }
}

- (void)setStarImage:(UIImage *)starImage {
    
    if (_starImage != starImage) {
        _starImage = starImage;
        [self resetStarsContentView];
    }
}
- (void)setCurrentStarImage:(UIImage *)currentStarImage {
    
    if (_currentStarImage != currentStarImage) {
        _currentStarImage = currentStarImage;
        [self resetStarsContentView];
    }
}

- (void)setStarSize:(CGSize)starSize {
    
    if (!CGSizeEqualToSize(_starSize, starSize)) {
        _starSize = starSize;
        [self resetStarsContentView];
    }
}

- (CGSize)starSize {

    if (CGSizeEqualToSize(_starSize, CGSizeZero)) {

        _starSize = self.starImage.size;
    }
    return _starSize;
}

- (void)setCurrentStarRate:(CGFloat)currentStarRate {
    
    if (self.starsContentViews.count == 0 || _currentStarRate == currentStarRate) {
        return;
    }
    if (currentStarRate  < 0) {
        return;
    } else if (currentStarRate > self.numberOfStars) {
        
        _currentStarRate = self.numberOfStars;
    } else {
        _currentStarRate = currentStarRate;
    }

    UIView *starsContentView = self.starsContentViews[1];
    [UIView animateWithDuration:_isAnimation ? kAnimatinDuration : 0.0 animations:^{
        starsContentView.frame = [self frameForStarsContentViewAtCurrentStarRate:currentStarRate];
    }];
    if (self.didSelectStarBlock) {
        self.didSelectStarBlock(self, currentStarRate);
    }
    if ([self.delegate respondsToSelector:@selector(starRateView:didSelecteStarAtStarRate:)]) {
        [self.delegate starRateView:self didSelecteStarAtStarRate:currentStarRate];
    }
}

#pragma mark - event
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    if (_allowClickScore) {
        UITouch *touch = [touches anyObject];
        UIView *view = touch.view;
        if (view != self) {
            CGPoint point = [touch locationInView:view];
            [self setupScoreWithOffsetX:point.x];
        }
    }
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    if (_allowSlideScore) {
        UITouch *touch = [touches anyObject];
        UIView *view = touch.view;
        if (view != self && [self.starsContentViews containsObject:view]) {
            CGPoint point = [touch locationInView:view];
            [self setupScoreWithOffsetX:point.x];
        }
    }
}

#pragma mark - 根据offsetx计算分数
- (void)setupScoreWithOffsetX:(CGFloat)offsetX {
    
    NSInteger index = offsetX / (self.starSize.width + self.spacingBetweenStars);
    CGFloat mathOffsetX =  (index + 1) * self.starSize.width + index * self.spacingBetweenStars;
    CGFloat score = (offsetX - index * self.spacingBetweenStars)/(self.starSize.width);
    if (offsetX > mathOffsetX) {
        score = index + 1;
    }
    self.currentStarRate = [self currentStarRateWithScore:score];
    NSLog(@"offsetX=%f,index=%ld, score=%f, starRate=%f", offsetX, index, score, self.currentStarRate);

}

- (CGFloat)currentStarRateWithScore:(CGFloat)score {
    
    switch (self.style) {
        case GBStarRateViewStyleWholeStar: //全星
            score = ceil(score);
            break;
        case GBStarRateViewStyleHalfStar: //半星
            score = round(score) > score ? round(score) : (score < (ceil(score)-0.5) ? (ceil(score)-0.5) : ceil(score));
            break;
        case GBStarRateViewStyleIncompleteStar: //不完整星
            score = score;
            break;
    }
    return score;
}


- (CGSize)sizeForNumberOfStar:(NSInteger)starCount {
    
    CGFloat w = (self.starSize.width + self.spacingBetweenStars)*starCount - self.spacingBetweenStars;
    return CGSizeMake(w, self.starSize.height);
}


- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    
    //实现在星星范围内也能响应 即使父视图高度少于星星高度
    if (point.y <= (self.starImage.size.height*0.5) && point.y >= - (self.starImage.size.height*0.5 - self.bounds.size.height*0.5)) {
        return YES;
    }
    return [super pointInside:point withEvent:event];
}


#pragma mark - getter
- (NSMutableArray *)starsContentViews {
    
    if (!_starsContentViews) {
        _starsContentViews = [NSMutableArray array];
    }
    return _starsContentViews;
}

欢迎大家下载使用传送门, 比心~~~

你可能感兴趣的:(iOS-实现星级评分(star score))