引言
原理:利用CoreGraphics进行自定义转盘的绘制
视频:https://live.csdn.net/v/158749
demo: https://download.csdn.net/download/u011018979/16651799
I 、概率抽奖算法 & 转盘算法
iOS概率抽奖算法 & 转盘算法 &轮盘边框动画丨蓄力计划https://kunnan.blog.csdn.net/article/details/115630759
II 、转盘主视图的实现
2.1 子视图
- 属性
@interface KNTurntableView() /** 转盘视图 */ @property (strong, nonatomic) SubTurntableView *turntable; /** 开始抽奖按钮 */ @property (nonatomic, weak) UIButton *startButton; /** 点击抽奖文字视图 */ @property (nonatomic, weak) UIImageView *textImgView; /** 指针视图 */ @property (nonatomic, weak) UIImageView *needleImgView;
- 初始化转盘视图
- (instancetype)initWithFrame:(CGRect)frame { return [self initWithFrame:frame ViewModel:nil]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { return [self initWithFrame:CGRectZero ViewModel:nil]; } - (instancetype)initWithViewModel:(id)ViewModel { return [self initWithFrame:CGRectZero ViewModel:ViewModel]; } - (instancetype)initWithFrame:(CGRect)frame ViewModel:(id)viewModel { if (self = [super initWithFrame:frame]) { _viewModel = viewModel; [self selfInit]; [self createSubView]; [self setupdata]; [self bindViewModel]; } return self; } - (void)selfInit{ // self.backgroundColor = k_view_backColor; } - (void)createSubView{ [self turntable]; self.turntable.luckyItemArray = _viewModel.luckyItemArray; [self initStartBtn]; } - (void)setupdata{ } - (void)bindViewModel{ } - (SubTurntableView *)turntable{ if(_turntable == nil){ _turntable = [[SubTurntableView alloc] init]; [self addSubview:_turntable]; __weak __typeof__(self) weakSelf = self; [_turntable mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(weakSelf); }]; [_turntable setRotaryEndTurnBlock:^{ [weakSelf lunckyAnimationDidStop]; }]; } return _turntable; ; }
2.2 处理点击抽奖事件
1、判断用户是否可以抽奖
禁用按钮 self.startButton.enabled = NO;
2、发起网络请求获取当前选中奖品,demo通过随机的方式获取一次index; 另外一种是根据奖品百分比进行控制
3、拿到当前奖品的 找到其对于的位置
4、让转盘转起来
/** //1、判断用户是否可以抽奖 //禁用按钮 // self.startButton.enabled = NO; //2、发起网络请求获取当前选中奖品,demo通过随机的方式获取一次index; 另外一种是根据奖品百分比进行控制 //3、拿到当前奖品的 找到其对于的位置 //4、让转盘转起来 */ - (void)startAction { [self setupStartBtnState4noenable]; // 方式一: 发起网络请求获取当前选中奖品,demo通过随机的方式获取一次index; // self.viewModel.endId = arc4random() % self.viewModel.luckyItemArray.count; // 控制中奖的方式二:另外一种是根据奖品百分比进行控制中奖概率 // NSInteger randomNum = arc4random()%100;//控制概率 // 奖品 title A ,index下标0,中奖 概率probability80%, 就是当randomNum为0-80,返回中奖下标0 // 为了便于理解,我们称奖品A的【随机中奖范围】 probabilityRange为0-80 // // 根据randomNum,确定中奖奖品 KNTurntableViewModel *tmp = [KNTurntableViewModel getMbyprobabilityRangeWithArr:self.viewModel.luckyItemArray]; // return nil;// 谢谢参与 if(tmp){ self.viewModel.endId = tmp.index; }else{ self.viewModel.endId = 0;// 谢谢参与 } [self turntableRotate:self.viewModel.endId]; }
2.3 抽奖结束,弹出奖品
- (void)lunckyAnimationDidStop { self.startButton.enabled = YES; self.textImgView.image = [UIImage imageNamed:@"lottery_state_start"]; self.needleImgView.image = [UIImage imageNamed:@"lottery_start_needle_enable"]; NSLog(@"============🌝🌝🌝🌝🌝🌝🌝🌝============:%ld",self.viewModel.endId); KNTurntableViewModel *model = self.viewModel.luckyItemArray[self.viewModel.endId]; NSLog(@"============🌝🌝🌝🌝🌝🌝🌝🌝============:%@",model.title); //抽奖结束 弹出奖品 KNPrizePopView *popView = [KNPrizePopView new]; KNActivityPrizeModel *m = [KNActivityPrizeModel new]; m.icon = model.icon; m.prizeName =model.title; m.winnerNum = [@1 description]; [popView showWithModel:m]; popView.popShareBlock = ^{ NSLog(@"分享按钮点击了"); }; }
III、绘制转盘
原理:利用CoreGraphics进行自定义转盘的绘制
- 头文件
#import "KNTurntableViewModel.h" #import <UIKit/UIKit.h> #define D2R(degrees) ((M_PI * degrees) / 180) @interface SubTurntableView : UIView /** 奖品数据 */ @property (nonatomic, strong) NSArray<KNTurntableViewModel *> *luckyItemArray; -(void)animationWithSelectonIndex:(NSInteger)index; //结束旋转 @property (nonatomic, copy) void (^rotaryEndTurnBlock)(void);
- 根据奖品绘制转盘
#import <Masonry/Masonry.h> #import "SubTurntableView.h" #import "UIImage+CGImageRef.h" @import CoreGraphics; @interface SubTurntableView ()<CAAnimationDelegate>{ UIFont *_textFont; CGFloat _textFontSize; UIColor *_textFontColor; NSDictionary *_attributes; CGSize _imageSize; //相间颜色 UIColor *_colorA; UIColor *_colorB; UIColor *_circleBgColor;//外环 bgColor UIColor *_dotColor; UIColor *_dotShinningColor; CGFloat _circleWidth; NSInteger _numberOfDot;//default is 18 dots CGFloat _dotSize; // default is 10.0 } @property (strong, nonatomic) NSMutableArray *dotLayers;//count = 18 @property (strong, nonatomic) NSMutableArray *imageLayers; @property (strong, nonatomic) NSOperationQueue *imageRenderQueue; @property (nonatomic, assign) CGFloat startValue;//default = 0 @end static CGPoint pointAroundCircumference(CGPoint center, CGFloat radius, CGFloat theta); @implementation SubTurntableView #pragma mark - Draw Method - (void)drawRect:(CGRect)rect{ [super drawRect:rect]; if (_luckyItemArray && _luckyItemArray.count) { [_imageLayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)]; [_imageLayers removeAllObjects]; NSInteger count = _luckyItemArray.count; CGPoint center = CGPointMake(rect.size.width / 2.0, rect.size.height / 2.0); CGFloat degree = 360.0 / count; //draw cicle UIBezierPath *outerPath = [UIBezierPath bezierPathWithArcCenter:center radius:center.x startAngle:0 endAngle:M_PI * 2 clockwise:YES]; UIBezierPath *innerPath = [UIBezierPath bezierPathWithArcCenter:center radius:center.x - _circleWidth startAngle:0 endAngle:M_PI * 2 clockwise:YES]; [outerPath appendPath:innerPath]; [_circleBgColor setFill]; [outerPath fill]; //draw dots 画点 [self drawDotOnCircle]; for (int i = 0; i < count; i++) { KNTurntableViewModel *obj = [_luckyItemArray objectAtIndex:i]; UIBezierPath *fanPath = [UIBezierPath bezierPath];//reference path [fanPath moveToPoint:center]; [fanPath addArcWithCenter:center radius:center.x - _circleWidth startAngle:D2R(i * degree) endAngle:D2R((i + 1) * degree) clockwise:YES]; [fanPath closePath]; if (i%2) { [_colorA setFill]; [fanPath fill]; }else{ [_colorB setFill]; [fanPath fill]; } //text 文字 [self drawCurvedStringOnLayer:self.layer withAttributedText:[[NSAttributedString alloc] initWithString:obj.title attributes:_attributes] atAngle:D2R((i + 0.5) * degree) withRadius:center.x - _circleWidth - _textFontSize - 2]; //image 图片 CALayer *imageLayer = [CALayer layer]; NSBlockOperation *operaton = [NSBlockOperation blockOperationWithBlock:^{ [[NSOperationQueue mainQueue] addOperationWithBlock:^{ UIImage *image = [UIImage imageNamed:obj.imageName]; CGImageRef imageRef = [image newCGImageRenderedInBitmapContext]; imageLayer.contents = (__bridge id)imageRef; }]; }]; [self.imageRenderQueue addOperation:operaton]; // VKOOY CGPoint imageLayerPos = pointAroundCircumference(center, (center.x - _circleWidth) / 2.0, D2R((i + 0.5) * degree)); imageLayer.frame = CGRectMake(0, 0, _imageSize.width, _imageSize.height); imageLayer.position = imageLayerPos; imageLayer.affineTransform = CGAffineTransformRotate(CGAffineTransformIdentity, D2R((i + 0.5) * degree) + M_PI_2); imageLayer.cornerRadius = 3.0; imageLayer.masksToBounds = YES; [self.layer addSublayer:imageLayer]; [self.imageLayers addObject:imageLayer]; } } [self RotationWithEndValue: @(0 - M_PI/2) duration:0.001 delegate:nil]; } /** 画点*/ - (void)drawDotOnCircle{ [_dotLayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)]; [_dotLayers makeObjectsPerformSelector:@selector(removeAllAnimations)]; [_dotLayers removeAllObjects]; CGPoint center = CGPointMake(self.bounds.size.width / 2.0, self.bounds.size.height / 2.0); CGFloat dotRadians = M_PI*2 / _numberOfDot; CABasicAnimation *shinningAnimation = [CABasicAnimation animationWithKeyPath:@"backgroundColor"]; for (int i = 0; i < _numberOfDot; i++) { CAShapeLayer *dotLayer = [CAShapeLayer layer]; dotLayer.frame = CGRectMake(0, 0, _dotSize, _dotSize); dotLayer.cornerRadius = _dotSize / 2.0; dotLayer.position = pointAroundCircumference(center, center.x - _circleWidth / 2.0, i * dotRadians); dotLayer.backgroundColor = (i % 2) ? _dotColor.CGColor : _dotShinningColor.CGColor; [_dotLayers addObject:dotLayer]; [self.layer addSublayer:dotLayer]; shinningAnimation.fromValue = (id)(dotLayer.backgroundColor); shinningAnimation.toValue = (id)((i % 2) ? _dotShinningColor.CGColor : _dotColor.CGColor); shinningAnimation.duration = 0.25f; shinningAnimation.repeatCount = 1000; shinningAnimation.autoreverses = YES; [dotLayer addAnimation:shinningAnimation forKey:@"backgroundColor"]; } } - (void)addAnimation2DotLayer{ for (int i = 0; i < _numberOfDot; i++) { CAShapeLayer *dotLayer = [_dotLayers objectAtIndex:i]; [dotLayer removeAllAnimations]; CABasicAnimation *shinningAnimation = [CABasicAnimation animationWithKeyPath:@"backgroundColor"]; shinningAnimation.fromValue = (id)(dotLayer.backgroundColor); shinningAnimation.toValue = (id)((i % 2) ? _dotShinningColor.CGColor : _dotColor.CGColor); shinningAnimation.duration = 0.25f; shinningAnimation.repeatCount = 1000; shinningAnimation.autoreverses = YES; [dotLayer addAnimation:shinningAnimation forKey:@"backgroundColor"]; } } // draw fan shaped text(sector text) 画扇形字 - (void)drawCurvedStringOnLayer:(CALayer *)layer withAttributedText:(NSAttributedString *)text atAngle:(float)angle withRadius:(float)radius { CGSize textSize = CGRectIntegral([text boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) context:nil]).size; CGFloat perimeter = 2 * M_PI * radius; CGFloat textAngle = (textSize.width / perimeter * 2 * M_PI); CGFloat textRotation = 0; CGFloat textDirection = 0; // if (angle > D2R(10) && angle < D2R(170)) {// 反向 使文字 可读 // //bottom string // textRotation = 0.5 * M_PI ; // textDirection = - 2 * M_PI; // angle += textAngle / 2; // } else { //top string textRotation = 1.5 * M_PI ; textDirection = 2 * M_PI; angle -= textAngle / 2; // } for (int c = 0; c < text.length; c++) { NSRange range = {c, 1}; NSAttributedString* letter = [text attributedSubstringFromRange:range]; CGSize charSize = CGRectIntegral([letter boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) context:nil]).size; CGFloat letterAngle = ((charSize.width / perimeter) * textDirection ); CGFloat x = radius * cos(angle + (letterAngle/2)); CGFloat y = radius * sin(angle + (letterAngle/2)); CATextLayer *singleChar = [self drawTextOnLayer:layer withText:letter frame:CGRectMake(layer.frame.size.width/2 - charSize.width/2 + x, layer.frame.size.height/2 - charSize.height/2 + y, charSize.width, charSize.height) bgColor:nil opacity:1]; singleChar.transform = CATransform3DMakeAffineTransform( CGAffineTransformMakeRotation(angle - textRotation) ); angle += letterAngle; } } - (CATextLayer *)drawTextOnLayer:(CALayer *)layer withText:(NSAttributedString *)text frame:(CGRect)frame bgColor:(UIColor *)bgColor opacity:(CGFloat)opacity { CATextLayer *textLayer = [[CATextLayer alloc] init]; [textLayer setFrame:frame]; [textLayer setString:text]; [textLayer setAlignmentMode:kCAAlignmentCenter]; [textLayer setBackgroundColor:bgColor.CGColor]; [textLayer setContentsScale:[UIScreen mainScreen].scale]; [textLayer setOpacity:opacity]; [layer addSublayer:textLayer]; return textLayer; } @end // center point on circle 在圆上的点 static CGPoint pointAroundCircumference(CGPoint center, CGFloat radius, CGFloat theta){ CGPoint point = CGPointZero; point.x = center.x + radius * cos(theta); point.y = center.y + radius * sin(theta); return point; } - (void)dealloc{ [_imageRenderQueue cancelAllOperations]; _imageRenderQueue = nil; [_imageLayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)]; [_imageLayers removeAllObjects]; [_dotLayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)]; [_dotLayers removeAllObjects]; } #pragma mark - Init Methods - (instancetype)initWithCoder:(NSCoder *)aDecoder{ self = [super initWithCoder:aDecoder]; if (self) { [self defaultSetups]; } return self; } - (instancetype)initWithFrame:(CGRect)frame{ self = [super initWithFrame:frame]; if (self) { [self defaultSetups]; } return self; } #pragma mark - Preparations - (void)defaultSetups{ self.backgroundColor = [UIColor clearColor]; _dotLayers = [NSMutableArray arrayWithCapacity:18]; _textFontSize = 12.0; _textFont = [UIFont systemFontOfSize:_textFontSize]; _textFontColor = [UIColor blackColor]; _attributes = @{ NSForegroundColorAttributeName:_textFontColor, NSFontAttributeName:_textFont }; _imageSize = CGSizeMake(25, 25); _circleWidth = 20.0; _numberOfDot = 18; _dotSize = 8.0; _colorA = [UIColor colorWithRed:249 / 255.0 green:105 / 255.0 blue:108 / 255.0 alpha:1.0]; _colorB = [UIColor colorWithRed:247 / 255.0 green:131 / 255.0 blue:131 / 255.0 alpha:1.0]; _circleBgColor = [UIColor colorWithRed:251 / 255.0 green:94 / 255.0 blue:97 / 255.0 alpha:1.0]; _dotShinningColor = [UIColor colorWithRed:42 / 255.0 green:253 / 255.0 blue:47 / 255.0 alpha:1.0]; _dotColor = [UIColor whiteColor]; } #pragma mark - Getter & Setter - (NSOperationQueue *)imageRenderQueue{ if (!_imageRenderQueue) { _imageRenderQueue = [[NSOperationQueue alloc] init]; _imageRenderQueue.name = @"https://kunnan.blog.csdn.net/"; } return _imageRenderQueue; } - (void)setLuckyItemArray:(NSArray<KNTurntableViewModel *> *)luckyItemArray{ _luckyItemArray = luckyItemArray; _numberOfDot = _luckyItemArray.count * 2; [self setNeedsDisplay]; } #pragma mark - Public Methods /** 转盘算法 */ - (void)animationWithSelectonIndex:(NSInteger)index{ [self backToStartPosition]; double perSection = M_PI*2/_luckyItemArray.count; // //先转4圈 再选区 顺时针(所有这里需要用360-对应的角度) 逆时针不需要 double toValue= ((M_PI*2 - (perSection*index +perSection*0.5)) + M_PI*2*4); [self RotationWithEndValue: @(toValue - M_PI/2) duration:4 delegate:self];// 因为drawRect从正3点开始画,因此- M_PI/2 } - (void)RotationWithEndValue:(id)toValue duration:(CFTimeInterval)duration delegate:(id)delegate{ CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; animation.toValue = toValue;// animation.duration = duration; //由快变慢 animation.removedOnCompletion = NO; animation.fillMode = kCAFillModeForwards; animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];// animation.delegate = delegate; [self.layer addAnimation:animation forKey:@"rotation"]; } -(void)backToStartPosition{ CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; animation.toValue = @(0); animation.duration = 0.001; animation.removedOnCompletion = NO; animation.fillMode = kCAFillModeForwards; [self.layer addAnimation:animation forKey:@"rotation"]; } #pragma mark - CAAnimationDelegate - (void)animationDidStart:(CAAnimation *)anim{ } - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{ if (self.rotaryEndTurnBlock) { self.rotaryEndTurnBlock(); } }
IV、完整Demo下载
iOS抽奖转盘:概率抽奖算法 & 转盘算法 & 转盘主视图的实现思路 (从CSDN下载完整Demo)https://download.csdn.net/download/u011018979/16651799
文章:https://kunnan.blog.csdn.net/article/details/115653905
原理:利用CoreGraphics进行自定义转盘的绘制