小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
引言
原理:利用CoreGraphics进行自定义转盘的绘制
demo: https://download.csdn.net/download/u011018979/16651799
I 、概率抽奖算法 & 转盘算法
iOS概率抽奖算法 & 转盘算法 &轮盘边框动画丨蓄力计划 kunnan.blog.csdn.net/article/det…
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下载
文章:kunnan.blog.csdn.net/article/det…
原理:利用CoreGraphics进行自定义转盘的绘制
see also
更多内容请关注#小程序:iOS逆向
,只为你呈现有价值的信息,专注于移动端技术研究领域。
作者:公众号iOS逆向
链接:juejin.cn/post/701803…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
《Quartz 2D编程指南》【自定义控件(横屏电子签名)、图片处理(水印、裁剪以及屏幕截图)、常见图形的绘制(饼图、柱状图、雪花、手势密码、画板)】Using Quartz in Your App
————————————————
版权声明
本文为CSDN博主「#公众号:iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。