iOS SlotMachine老虎机

#import 

NS_ASSUME_NONNULL_BEGIN

@interface SlotMachineViewController : UIViewController

@end

NS_ASSUME_NONNULL_END
#import "SlotMachineViewController.h"
#import "SlotMatchineView.h"
#import 
#import 

static const NSUInteger slotMachineColum = 3;

@interface SlotMachineViewController () 

/// slotmachine
@property (nonatomic, strong) SlotMatchineView *slotMachine;

/// 数据
@property (nonatomic, copy) NSArray *slotMachineIcons;

@end

@implementation SlotMachineViewController

/// 视图加载完毕
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.navigationItem.title = @"demo";
    self.view.backgroundColor = [UIColor whiteColor];
    
    CGFloat slotMachineWidth = self.view.frame.size.width;
    CGFloat slotMachineHeight = ceil(slotMachineWidth / 80 * 74);
    
    _slotMachine = [[SlotMatchineView alloc] initWithFrame: CGRectMake(0, 0, slotMachineWidth, slotMachineHeight)];
    _slotMachine.backgroundImage = [YYImage imageNamed: @"slot_machine_normal.webp"];
    _slotMachine.delegate = self;
    _slotMachine.dataSource = self;
    [self.view addSubview: _slotMachine];
    
    [_slotMachine mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.mas_equalTo(0);
        make.width.mas_equalTo(slotMachineWidth);
        make.height.mas_equalTo(slotMachineHeight);
    }];
}

/// 点击play
- (void)slotMachineDidClickPlayBtn:(SlotMatchineView *)slotMachine {
    [self playSlotMachine];
}

/// 动画将要开始
- (void)slotMachineWillStartSliding:(SlotMatchineView *)slotMachine {
    _slotMachine.backgroundImage = [YYImage imageNamed: @"slot_machine_sliding.webp"];
}

/// 动画结束
- (void)slotMachineDidEndSliding:(SlotMatchineView *)slotMachine {
    _slotMachine.backgroundImage = [YYImage imageNamed: @"slot_machine_reward.webp"];
    UIAlertController *alert = [UIAlertController alertControllerWithTitle: @"slotMachine result" message: [NSString stringWithFormat: @"%@ %@ %@", _slotMachine.slotResults[0], _slotMachine.slotResults[1], _slotMachine.slotResults[2]] preferredStyle: UIAlertControllerStyleAlert];
    [alert addAction: [UIAlertAction actionWithTitle: @"ok" style: UIAlertActionStyleDefault handler: nil]];
    __weak typeof(self) weakSelf = self;
    [alert addAction: [UIAlertAction actionWithTitle: @"play again" style: UIAlertActionStyleDefault handler:^(UIAlertAction *_Nonnull action) {
        [weakSelf playSlotMachine];
    }]];
    [self presentViewController: alert animated: YES completion: nil];
}

/// 开始
- (void)playSlotMachine {
    _slotMachine.slotResults = [self getRandomSlotResults];
    [_slotMachine startSliding];
}

/// 列数
- (NSUInteger)numberOfSlotsInSlotMachine:(SlotMatchineView *)slotMachine {
    return slotMachineColum;
}

/// 图标
- (NSArray *)iconsForSlotsInSlotMachine:(SlotMatchineView *)slotMachine {
    return self.slotMachineIcons;
}

/// 图标间隙
- (CGFloat)slotSpacingInSlotMachine:(SlotMatchineView *)slotMachine {
    return 0.0;
}

/// 随机结果
- (NSArray *)getRandomSlotResults {
    NSMutableArray *slotResults = [NSMutableArray arrayWithCapacity: slotMachineColum];
    for (NSInteger i = 0; i < slotMachineColum; i++) {
        NSInteger value = arc4random() % self.slotMachineIcons.count;
        [slotResults appendObject: [NSNumber numberWithInteger: value]];
    }
    return slotResults;
}

/// 图标
- (NSArray *)slotMachineIcons {
    if (!_slotMachineIcons) {
        _slotMachineIcons = @[
            [UIImage imageNamed: @"0"],
            [UIImage imageNamed: @"1"],
            [UIImage imageNamed: @"2"],
            [UIImage imageNamed: @"3"],
            [UIImage imageNamed: @"4"],
            [UIImage imageNamed: @"5"],
            [UIImage imageNamed: @"6"],
            [UIImage imageNamed: @"7"],
            [UIImage imageNamed: @"8"],
            [UIImage imageNamed: @"9"]
        ];
    }
    return _slotMachineIcons;
}

/// 释放
- (void)dealloc {
    NSLog(@"----- %@ dealloc -----", [self className]);
}

@end
#import 
#import 

NS_ASSUME_NONNULL_BEGIN

@class SlotMatchineView;

@protocol SlotMachineDelegate 

@optional

/// 点击开始
- (void)slotMachineDidClickPlayBtn:(SlotMatchineView *)slotMachine;

/// 动画将要开始
- (void)slotMachineWillStartSliding:(SlotMatchineView *)slotMachine;

/// 动画结束
- (void)slotMachineDidEndSliding:(SlotMatchineView *)slotMachine;

@end

@protocol SlotMachineDataSource 

@required

/// 列数
- (NSUInteger)numberOfSlotsInSlotMachine:(SlotMatchineView *)slotMachine;

/// 图标
- (NSArray *)iconsForSlotsInSlotMachine:(SlotMatchineView *)slotMachine;

@optional

/// 图标宽度
- (CGFloat)slotWidthInSlotMachine:(SlotMatchineView *)slotMachine;

/// 图标间距
- (CGFloat)slotSpacingInSlotMachine:(SlotMatchineView *)slotMachine;

@end

@interface SlotMatchineView : UIView

/// 背景动图
@property (nonatomic, strong) YYImage *backgroundImage;

/// 抽奖结果
@property (nonatomic, strong) NSArray *slotResults;

/// 旋转速度, 默认 0.14f
@property (nonatomic, assign) CGFloat singleUnitDuration;

/// 方法代理
@property (nonatomic, weak) id delegate;

/// 数据源代理
@property (nonatomic, weak) id dataSource;

/// 开始滚动
- (void)startSliding;

@end

NS_ASSUME_NONNULL_END
#import "SlotMatchineView.h"
#import 

static const NSUInteger kMinTurn = 3;

@interface SlotMatchineView ()

/// 背景动图
@property (nonatomic, strong) YYAnimatedImageView *backgroundImageView;

/// 列表内容区域
@property (nonatomic, strong) UIView *contentView;

/// 滚动layer数组
@property (nonatomic, strong) NSMutableArray *slotScrollLayerArray;

/// 当前抽奖结果
@property (nonatomic, copy) NSArray *currentSlotResults;

/// 是否正在滚动
@property (nonatomic, assign) BOOL isSliding;

@end

@implementation SlotMatchineView

/// 初始化
- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        
        /// 背景动图
        _backgroundImageView = [[YYAnimatedImageView alloc] init];
        [self addSubview: _backgroundImageView];
    
        [_backgroundImageView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.edges.mas_equalTo(0);
        }];
        
        /// play点击区域
        UIButton *playBtn = [[UIButton alloc] init];
        playBtn.adjustsImageWhenHighlighted = NO;
        playBtn.adjustsImageWhenDisabled = NO;
        [playBtn addTarget: self action: @selector(btnPlayClick:) forControlEvents: UIControlEventTouchUpInside];
        [self addSubview: playBtn];
        
        [playBtn mas_makeConstraints:^(MASConstraintMaker *make) {
            make.centerX.mas_equalTo(0);
            make.height.mas_equalTo(50);
            make.width.mas_equalTo(136);
            make.bottom.mas_equalTo(-30);
        }];
        
        /// 滚动内容区域
        _contentView = [[UIView alloc] initWithFrame: CGRectMake(0, 0, 196, 114)];
        _contentView.layer.masksToBounds = YES;
        [self addSubview: _contentView];
        
        [_contentView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.centerX.mas_equalTo(0);
            make.width.mas_equalTo(196);
            make.height.mas_equalTo(114);
            make.bottom.mas_equalTo(playBtn.mas_top).mas_offset(-22);
        }];
        
        _slotScrollLayerArray = [NSMutableArray array];
        _singleUnitDuration = 0.14f;
    }
    return self;
}

/// 设置背景动图
- (void)setBackgroundImage:(YYImage *)backgroundImage {
    _backgroundImageView.image = backgroundImage;
}

/// 点击play
- (void)btnPlayClick:(UIButton *)sender {
    if (_delegate && [_delegate respondsToSelector: @selector(slotMachineDidClickPlayBtn:)]) {
        [_delegate slotMachineDidClickPlayBtn: self];
    }
}

/// 设置数据
- (void)setDataSource:(id)dataSource {
    _dataSource = dataSource;
    [self reloadData];
}

/// 刷新数据
- (void)reloadData {
    if (!_dataSource) {
        return;
    }
    for (CALayer *containerLayer in _contentView.layer.sublayers) {
        [containerLayer removeFromSuperlayer];
    }
    _slotScrollLayerArray = [NSMutableArray array];
    NSUInteger numberOfSlots = [_dataSource numberOfSlotsInSlotMachine: self];
    CGFloat slotSpacing = 0;
    if ([_dataSource respondsToSelector: @selector(slotSpacingInSlotMachine:)]) {
        slotSpacing = [_dataSource slotSpacingInSlotMachine: self];
    }
    CGFloat slotWidth = _contentView.frame.size.width / numberOfSlots;
    if ([_dataSource respondsToSelector: @selector(slotWidthInSlotMachine:)]) {
        slotWidth = [_dataSource slotWidthInSlotMachine: self];
    }
    for (int i = 0; i < numberOfSlots; i++) {
        CALayer *slotContainerLayer = [[CALayer alloc] init];
        slotContainerLayer.frame = CGRectMake(i * (slotWidth + slotSpacing), 0, slotWidth, _contentView.frame.size.height);
        slotContainerLayer.masksToBounds = YES;
        CALayer *slotScrollLayer = [[CALayer alloc] init];
        slotScrollLayer.frame = CGRectMake(0, 0, slotWidth, _contentView.frame.size.height);
        [slotContainerLayer addSublayer: slotScrollLayer];
        [_contentView.layer addSublayer: slotContainerLayer];
        [_slotScrollLayerArray addObject: slotScrollLayer];
    }
    CGFloat singleUnitHeight = _contentView.frame.size.height / 2;
    NSArray *slotIcons = [_dataSource iconsForSlotsInSlotMachine: self];
    NSUInteger iconCount = [slotIcons count];
    for (int i = 0; i < numberOfSlots; i++) {
        CALayer *slotScrollLayer = [_slotScrollLayerArray objectAtIndex: i];
        NSInteger scrollLayerTopIndex = - (i + kMinTurn + 3) * iconCount;
        for (int j = 0; j > scrollLayerTopIndex; j--) {
            UIImage *iconImage = [slotIcons objectAtIndex: abs(j) % iconCount];
            CALayer *iconImageLayer = [[CALayer alloc] init];
            NSInteger offsetYUnit = j + 1 + iconCount;
            iconImageLayer.frame = CGRectMake(0, offsetYUnit * singleUnitHeight - singleUnitHeight / 2 + 5.5, slotScrollLayer.frame.size.width, singleUnitHeight - 11);
            iconImageLayer.contents = (id)iconImage.CGImage;
            iconImageLayer.contentsScale = 2.0;
            iconImageLayer.contentsGravity = kCAGravityResizeAspect;
            [slotScrollLayer addSublayer: iconImageLayer];
        }
    }
}

/// 开始滚动
- (void)startSliding {
    
    if (_isSliding) {
        return;
    }
    _isSliding = YES;
    
    if (_delegate && [_delegate respondsToSelector: @selector(slotMachineWillStartSliding:)]) {
        [_delegate slotMachineWillStartSliding: self];
    }
    
    NSArray *slotIcons = [_dataSource iconsForSlotsInSlotMachine: self];
    NSUInteger slotIconsCount = [slotIcons count];
    
    __block NSMutableArray *completePositionArray = [NSMutableArray array];
    __weak typeof(self) weakSelf = self;
    
    [CATransaction begin];
    [CATransaction setAnimationTimingFunction: [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut]];
    [CATransaction setDisableActions: YES];
    [CATransaction setCompletionBlock:^{
        weakSelf.isSliding = NO;
        if ([weakSelf.delegate respondsToSelector: @selector(slotMachineDidEndSliding:)]) {
            [weakSelf.delegate slotMachineDidEndSliding: weakSelf];
        }
        for (int i = 0; i < weakSelf.slotScrollLayerArray.count; i++) {
            CALayer *slotScrollLayer = [weakSelf.slotScrollLayerArray objectAtIndex: i];
            slotScrollLayer.position = CGPointMake(slotScrollLayer.position.x, ((NSNumber *)[completePositionArray objectAtIndex: i]).floatValue);
            NSMutableArray *toBeDeletedLayerArray = [NSMutableArray array];
            NSUInteger resultIndex = [[weakSelf.slotResults objectAtIndex: i] unsignedIntegerValue];
            NSUInteger currentIndex = [[weakSelf.currentSlotResults objectAtIndex: i] unsignedIntegerValue];
            for (int j = 0; j < slotIconsCount * (kMinTurn + i) + resultIndex - currentIndex; j++) {
                CALayer *iconLayer = [slotScrollLayer.sublayers objectAtIndex: j];
                [toBeDeletedLayerArray addObject: iconLayer];
            }
            for (CALayer *toBeDeletedLayer in toBeDeletedLayerArray) {
                CALayer *toBeAddedLayer = [CALayer layer];
                toBeAddedLayer.frame = toBeDeletedLayer.frame;
                toBeAddedLayer.contents = toBeDeletedLayer.contents;
                toBeAddedLayer.contentsGravity = toBeDeletedLayer.contentsGravity;
                CGFloat shiftY = slotIconsCount * (toBeAddedLayer.frame.size.height + 11) * (kMinTurn + i + 3);
                toBeAddedLayer.position = CGPointMake(toBeAddedLayer.position.x, toBeAddedLayer.position.y - shiftY);
                [toBeDeletedLayer removeFromSuperlayer];
                [slotScrollLayer addSublayer: toBeAddedLayer];
            }
            toBeDeletedLayerArray = [NSMutableArray array];
        }
        weakSelf.currentSlotResults = weakSelf.slotResults;
        completePositionArray = [NSMutableArray array];
    }];
    
    for (int i = 0; i < _slotScrollLayerArray.count; i++) {
        CALayer *slotScrollLayer = [_slotScrollLayerArray objectAtIndex: i];
        NSUInteger resultIndex = [[_slotResults objectAtIndex: i] unsignedIntegerValue];
        NSUInteger currentIndex = [[_currentSlotResults objectAtIndex: i] unsignedIntegerValue];
        NSUInteger howManyUnit = (i + kMinTurn) * slotIconsCount + resultIndex - currentIndex;
        CGFloat slideY = howManyUnit * (_contentView.frame.size.height / 2);
        CABasicAnimation *slideAnimation = [CABasicAnimation animationWithKeyPath: @"position.y"];
        slideAnimation.fillMode = kCAFillModeForwards;
        slideAnimation.duration = howManyUnit * _singleUnitDuration;
        slideAnimation.toValue = [NSNumber numberWithFloat: slotScrollLayer.position.y + slideY];
        slideAnimation.removedOnCompletion = NO;
        [slotScrollLayer addAnimation: slideAnimation forKey: @"slideAnimation"];
        [completePositionArray addObject: slideAnimation.toValue];
    }
    [CATransaction commit];
}

/// 释放
- (void)dealloc {
    if ([_backgroundImageView currentIsPlayingAnimation]) {
        [_backgroundImageView stopAnimating];
        _backgroundImageView = nil;
    }
    NSLog(@"----- %@ dealloc -----", [self className]);
}

@end

你可能感兴趣的:(iOS SlotMachine老虎机)