#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