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

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

引言

image.png

原理:利用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

image.png

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();
    }
}

image.png

IV、完整Demo下载

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

文章:https://kunnan.blog.csdn.net/article/details/115653905

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

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

see also

目录
相关文章
|
4月前
|
语音技术 开发工具 图形学
Unity与IOS⭐一、百度语音IOS版Demo调试方法
Unity与IOS⭐一、百度语音IOS版Demo调试方法
|
安全 数据安全/隐私保护 iOS开发
iOS小技能:【发红包】使用tweak和lua脚本结合进行实现
我们开发的大部分越狱程序,都是编译成动态链接库(`例如:介绍的越狱程序(Tweak)开发,就是动态链接库。`),然后通过越狱平台的MobileSubstrate(iOS7上叫CydiaSubstrate)来加载进入目标程序(Target),通过对目标程序的挂钩(Hook),来实现相应的功能。
346 0
|
iOS开发
iOS 多个滚动控件嵌套Demo
iOS 多个滚动控件嵌套Demo
72 0
|
iOS开发
iOS UIKit Dynamics Demo 学习地址列表
iOS UIKit Dynamics Demo 学习地址列表
52 0
倒计时15分钟-兼容ios手机效果demo(整理)
倒计时15分钟-兼容ios手机效果demo(整理)
|
JSON 测试技术 Android开发
基于AirTest+Python的ios自动化测试demo(微信朋友圈无限点赞)
AirTest相比Appuim有个好处就是可以对GUI图片进行捕捉和最新版本支持WebView(目前Appuim不支持iOS12的WebView进行Xpath抓取)
617 0
|
测试技术 iOS开发 Python
基于Python+appium的ios自动化测试demo(更新中)
appium环境搭建可参考以下两个链接: www.jianshu.com/p/a2b79cd8b… www.jianshu.com/p/3c04e029c…
456 0
|
Android开发 iOS开发
iOS开发 - 商品详情页两种分页模式,只提供思路和实现方式。
iOS开发 - 商品详情页两种分页模式,只提供思路和实现方式。
418 0
iOS开发 - 商品详情页两种分页模式,只提供思路和实现方式。
|
存储 安全 iOS开发
iOS开发 - 继udid,Mac地址等一系列唯一标识无效后,如何用KeyChain来实现设备唯一性
iOS开发 - 继udid,Mac地址等一系列唯一标识无效后,如何用KeyChain来实现设备唯一性
479 0
iOS开发 - 继udid,Mac地址等一系列唯一标识无效后,如何用KeyChain来实现设备唯一性
|
Swift 数据安全/隐私保护 iOS开发
iOS开发 - swift通过Alamofire实现https通信
iOS开发 - swift通过Alamofire实现https通信
439 0
iOS开发 - swift通过Alamofire实现https通信