In the recent development plan, the functional requirements of product scoring are listed in our development plan. I took a look at them in advance. There are too many such demos on the Internet, but they are not quite in line with our needs. First take a look at the final effect (there will be a demo at the end) :

First, let’s take a look at the implementation idea. My implementation idea is to use CALayer’s maskLayer to achieve this, which is similar to the general progress bar implementation idea, but this time I will use the view with stars as the mask. See the implementation below.

Let’s start with the maskLayer view, which is the view with the star:

#import <UIKit/UIKit.h>Typedef NS_ENUM(NSInteger, CYXRatingStarStyle) {RatingStarStyleFull = 0, // RatingStarStyleHalf = 1, // NS_ASSUME_NONNULL_BEGIN @interface CYXRatingStarMaskView : UIView /* number of stars */ -(instancetype)initWithStarNum:(NSInteger)starNum; @param starNum Specifies the number of stars. @param space Specifies the direct distance between stars. The default value is 15 @returnself */ -(instancetype)initWithStarNum:(NSInteger)starNum andSpace:(CGFloat)space; */ -(void)updateViewConstrains; /* touch */ -(void)touchesWithPoint:(CGPoint)touchPoint; / / @property (nonatomic,assign) CYXRatingStarStyle ratingStarStyle; / / @property (nonatomic,assign) NSInteger fullScore; */ @property (nonatomic,strong) void(^touchBlock)(CGPoint) transformPoint,NSInteger score); @endCopy the code

Related implementation (more I will not be verbose in the comments inside) :

#import "CYXRatingStarMaskView.h"@interface CYXRatingStarMaskView () /* At least two stars */ @property (nonatomic,assign) NSInteger starNum; /* Star spacing defaults to 15*/ @property (nonatomic,assign) CGFloat space; @property (nonatomic,strong) NSMutableArray *itemList; @end @implementation CYXRatingStarMaskView -(instancetype)initWithStarNum:(NSInteger)starNum{return [self initWithStarNum:starNum andSpace:15];
}

-(instancetype)initWithStarNum:(NSInteger)starNum andSpace:(CGFloat)space{
    if (self = [super init]) {
        self.starNum = starNum;
        self.space = space;
        [self createItems];
    }
    return self;
}
-(void)createItems{
    self.itemList = [NSMutableArray new];
    for (int i =0; i<self.starNum; i++) {
        UIImageView * item = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"starUnselected"]];
        [self.itemList addObject:item];
        [self addSubview:item];
    }
}
-(void)updateViewConstrains{
    if (self.starNum>1) {
        CGFloat itemWidth = (self.frame.size.width-(self.starNum -1)*self.space)/self.starNum;
        if (itemWidth>0) {
            CGFloat x = 0;
            for (UIImageView * item inself.itemList) { item.frame = CGRectMake(x, 0, itemWidth, self.frame.size.height); x+=itemWidth; x+=self.space; } } } } -(void)touchesWithPoint:(CGPoint)touchPoint{ [self transformPointWithTouchPoint:touchPoint]; } / * will contact point * / converted to populate the stars - (CGPoint) transformPointWithTouchPoint: CGPoint touchPoint {/ * full star average * / NSInteger business = self.fullScore/[self.itemList count]; /* touchPoint x*/ CGFloat x = touchPoint.x; /* score */ CGFloat score = 0; /* traverses in reverse order */for (NSInteger i = [self.itemList count]-1; i>=0; i--) {
        UIImageView * item = self.itemList[i];
        if (x>item.frame.origin.x) {
            switch (self.ratingStarStyle) {
                caseRatingStarStyleFull:{// Score = (I +1)*average; x = item.frame.origin.x+item.frame.size.width; }break;
                caseRatingStarStyleHalf:{// Support half starif(x>(item.frame.origin. X +item.frame.size. Width /2)) {// Score = (I +1)*average; x = item.frame.origin.x+item.frame.size.width; }else{ score = (i+1)*average - (average/2); x = item.frame.origin.x+item.frame.size.width/2; }}break;
                default:
                    break;
            }
            break;
        }
    }
    CGPoint transformPoint = CGPointMake(x, touchPoint.y);
    if (self.touchBlock) {
        self.touchBlock(transformPoint, score);
    }
    return CGPointZero;
}
Copy the code

Then create a View for scoring to leverage this MaskView implementation:

#import <UIKit/UIKit.h>
#import "CYXRatingStarMaskView.h"NS_ASSUME_NONNULL_BEGIN @interface CYXRatingStarView : UIView /** initialization method @param frame frame @param ratingStarStyle scoring style @param fullScore fullScore @returnself */ -(instancetype)initWithFrame:(CGRect)frame andRatingStarStyle:(CYXRatingStarStyle)ratingStarStyle andFullScore:(NSInteger)fullScore; /** Initialize layer */ -(void)initLayers; /* selectScore */ @property (nonatomic,strong) void (^selectScore)(NSInteger score); @endCopy the code

Related implementation (no difficulty to have a comment to see for yourself) :

#import "CYXRatingStarView.h"@interface CYXRatingStarView() /* Background red */ @property (nonatomic,strong) CAShapeLayer *backColorLayer; @property (nonatomic,strong) CYXRatingStarMaskView *maskView; @end @implementation CYXRatingStarView -(instancetype)initWithFrame:(CGRect)frame andRatingStarStyle:(CYXRatingStarStyle)ratingStarStyle andFullScore:(NSInteger)fullScore{if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor whiteColor];
        self.maskView.fullScore = fullScore;
        self.maskView.ratingStarStyle = ratingStarStyle;
        [self.layer addSublayer:self.backColorLayer];
        
    }
    returnself; } -(void)initLayers{ self.maskView.frame = self.bounds; [self.maskView updateViewConstrains]; self.backColorLayer.frame = self.bounds; UIBezierPath *path = [UIBezierPath bezierPath]; [path moveToPoint:CGPointMake(0, self.frame.size.height/2)]; [path addLineToPoint:CGPointMake(self.frame.size.width, self.frame.size.height/2)]; self.backColorLayer.path = path.CGPath; self.backColorLayer.lineWidth = self.frame.size.height; self.backColorLayer.mask = self.maskView.layer; self.backColorLayer.strokeEnd = 0; } /* Set the selected star */ -(void)setStrokeWithTransformPoint:(CGPoint)transformPoint{
    CGPoint newPoint = [self convertPoint:transformPoint fromView:self.maskView];
    NSLog(@"%f",newPoint.x); self.backColorLayer.strokeEnd = newPoint.x/self.frame.size.width; } /* One or more fingers start touching the view, */ -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event  = [touches anyObject]; TouchPoint = [touch locationInView:self]; CGPoint newPoint = [self convertPoint:touchPoint toView:self.maskView]; [self.maskView touchesWithPoint:newPoint]; }#pragma mark ---G
-(CAShapeLayer*)backColorLayer{
    if(! _backColorLayer){ _backColorLayer = [[CAShapeLayer alloc] init]; _backColorLayer.backgroundColor = [UIColor grayColor].CGColor; _backcolorLayer.fillcolor = [UIColor clearColor].cgcolor; // _backcolorlayer. lineCap = kCALineCapSquare; / / set the line to the rounded _backColorLayer. StrokeColor = [UIColor redColor]. CGColor; // Path color (fill color)}return _backColorLayer;
}
-(CYXRatingStarMaskView*)maskView{
    if(! _maskView){ __weak __typeof(&*self)weakSelf = self; _maskView = [[CYXRatingStarMaskView alloc] initWithStarNum:5]; _maskView.touchBlock = ^(CGPoint transformPoint, NSInteger score) { NSLog(@"% @",NSStringFromCGPoint(transformPoint));
            NSLog(@"%ld",(long)score);
            [weakSelf setStrokeWithTransformPoint:transformPoint];
            if(weakSelf.selectScore) { weakSelf.selectScore(score); }}; }return _maskView;
}
Copy the code

Writing down the overall idea is very clear, and there is no difficult and complicated disease in the process of implementation, what problems are welcome to discuss.

Demo address: github.com/SionChen/CY…