iOS抽奖转盘下篇:转盘主视图的实现(内含完整Demo)

2,089 阅读4分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

引言

原理:利用CoreGraphics进行自定义转盘的绘制

视频:live.csdn.net/v/158749

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下载

iOS抽奖转盘:概率抽奖算法 & 转盘算法 & 转盘主视图的实现思路 (从CSDN下载完整Demo)https://download.csdn.net/download/u011018979/16651799

文章:kunnan.blog.csdn.net/article/det…

原理:利用CoreGraphics进行自定义转盘的绘制

视频:live.csdn.net/v/158749

see also

更多内容请关注#小程序:iOS逆向,只为你呈现有价值的信息,专注于移动端技术研究领域。


作者:公众号iOS逆向
链接:juejin.cn/post/701803… 来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

《Quartz 2D编程指南》【自定义控件(横屏电子签名)、图片处理(水印、裁剪以及屏幕截图)、常见图形的绘制(饼图、柱状图、雪花、手势密码、画板)】Using Quartz in Your App

————————————————

版权声明

本文为CSDN博主「#公众号:iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:blog.csdn.net/z929118967/…