《幸運大轉盤》有一句代碼是這樣的:
self.rotateView.transform = CGAffineTransformMakeRotation(-angle);
它出現在延遲派遣消息 dispatch_after 裏面,然而你真的看懂它了嗎?
本文將揭祕這句代碼的真相!紅字黃底標出!
#import "ViewController.h"
#import "ZHYView.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 讓控制器的view以拉伸的方式設置成圖片
self.view.layer.contents = (__bridge id)([UIImage imageNamed:@"LuckyBackground"].CGImage);
// 創建轉盤的對象
ZHYView *rotateView= [ZHYView rotateImage];
// 設置轉盤在屏幕上居中顯示
rotateView.center = self.view.center;
// 把裝盤添加到控制器當中
[self.view addSubview:rotateView];
// 程序一運行就讓鋸齒圖片旋轉
[rotateView startRotate];
}
// 設置狀態欄樣式爲白色字體,更好看一些
-(UIStatusBarStyle)preferredStatusBarStyle{
return UIStatusBarStyleLightContent;
}
@end
#import <UIKit/UIKit.h>
@interface CZView : UIView
+ (instancetype)rotateView;
- (void)startRotate;
@end
#import "ZHYView.h"
#define kButtonCount 12
@interface ZHYView () <UIAlertViewDelegate>
@property (weak, nonatomic) IBOutlet UIImageView *rotateView;
@property (nonatomic,weak) UIButton *lastButton;
@property (nonatomic,strong) CADisplayLink *link;
@end
@implementation ZHYView
// 開始旋轉
-(void)startRotate{
// CADisplayLink刷幀,默認每秒刷新60次,該定時器創建之後,默認是不會執行的,需要把它加載到主消息循環中才會被執行
CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(rotate)];
// 將CADisplayLink定時器加載到主循環中
[link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
// 給屬性link賦值,便於後面對CADisplayLink定時器對象link進行設置
self.link = link;
}
// 旋轉
-(void)rotate{
CGFloat round = 5;
CGFloat angle = 2 * M_PI / 60 / round;
// 設置CADisplayLink定時器對象link,讓rotateView每5秒轉一圈
self.rotateView.transform = CGAffineTransformRotate(self.rotateView.transform, angle);
}
// 開始選號的點擊事件
- (IBAction)pickNumber:(UIButton *)sender {
// 如果沒有選中任何一個button的時候不旋轉
if (!self.lastButton) {
return;
}
// 讓CADisplayLink定時器對象link暫停下來,避免UIAlertView的代理方法(點擊彈出提示框的確定按鈕後執行的方法)結束後選中的button跳屏
self.link.paused = YES;
// 關掉大轉盤的用戶交互
self.userInteractionEnabled = NO;
// 創建一個基本動畫,讓轉盤快速旋轉以備選號
CABasicAnimation *ani = [[CABasicAnimation alloc] init];
// 設置關鍵路徑
ani.keyPath = @"transform.rotation";
// 記錄每個button對應的初始角度
CGFloat angle = self.lastButton.tag * 2 * M_PI / 12;
// 設置屬性toValue爲了讓選中的button快速旋轉後指向轉盤的正上方
ani.toValue = @(2 * M_PI * 5 - angle);
// 設置快速旋轉動畫的持續時間
ani.duration = 2;
// 修改動畫的默認模式,不讓動畫在結束後復位
ani.fillMode = kCAFillModeForwards;
// 該BOOL屬性不設置的話,動畫默認修改不成功;
ani.removedOnCompletion = NO;
// 將動畫添加到layer上
[self.rotateView.layer addAnimation:ani forKey:@"key"];
// 延遲 ani.duration 時間後派遣{}中的消息
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(ani.duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
/*
這裏需要詳細解釋一下:
假設:
原始角度:獅子座在最上面: 0
當你點擊大轉盤的星座按鈕時獅子座的角度:A
當你接着點擊選號按鈕時獅子座的角度:B
動畫結束後獅子座的位置被設定了在最上面:0
如果下面這句代碼不設置,就會發生跳屏現象,即選中的按鈕會跳到角度 B
*/
self.rotateView.transform = CGAffineTransformMakeRotation(-angle);
// 創建提示視圖,顯示信息
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"幸運大轉盤,賺的就是你" message:@"13579" delegate:self cancelButtonTitle:@"確定" otherButtonTitles:nil, nil];
// 顯示提示視圖
[alert show];
// 開啓大轉盤的用戶交互
self.userInteractionEnabled = YES;
});
}
// UIAlertView的代理方法,在點擊cancelButtonTitle:@"確定"的時候會執行
-(void)alertView:(UIAlertView *)alertViewclickedButtonAtIndex:(NSInteger)buttonIndex{
// 移除指定AnimationForKey:@"key"的動畫
[self.rotateView.layer removeAnimationForKey:@"key"];
/*
其實在大轉盤選號結束後這麼設置可以讓大轉盤恢復初始狀態[隨便玩玩]
self.lastButton.selected = NO;
self.lastButton = nil;
*/
// 讓CADisplayLink定時器對象link重新計時
self.link.paused = NO;
}
// button的點擊事件
-(void)clickButton:(UIButton *)button{
// 取消上一個button的選中狀態
self.lastButton.selected = NO;
// 設置點擊到的button的選中狀態
button.selected = YES;
// 點擊事件的最後將當前button賦值給lastbutton
self.lastButton = button;
}
// 佈局子控件
-(void)layoutSubviews{
// 遍歷鋸齒圖片裏面的子控件(button)設置frame
for (int i = 0; i < self.rotateView.subviews.count; i++) {
UIButton *button = self.rotateView.subviews[i];
CGFloat buttonCenterX = self.bounds.size.width * 0.5;
CGFloat buttonCenterY = self.bounds.size.height * 0.5;
button.frame = CGRectMake(0, 0, 68, 143);
button.center = CGPointMake(buttonCenterX,buttonCenterY);
// 設置每個button的初始角度
CGFloat angle = i * 2 * M_PI / 12;
// 將每個button散開
button.transform = CGAffineTransformMakeRotation(angle);
// 設置button的內邊距
[button setContentEdgeInsets:UIEdgeInsetsMake(-44, 0, 0, 0)];
}
}
//裁剪圖片
-(UIImage *)clipImage:(UIImage *)image withIndex:(int)index{
//設置將要從image獲取的裁剪到的圖片的frame,注意寬高需要乘以設備的縮放因子
CGFloat w = image.size.width / 12 * [UIScreen mainScreen].scale;
CGFloat h = image.size.height * [UIScreen mainScreen].scale;
CGFloat x = index * w;
CGFloat y = 0;
// 獲取裁剪image中rect部分裁剪的圖片
CGImageRef imageRef = CGImageCreateWithImageInRect(image.CGImage, CGRectMake(x, y, w, h));
// 通過CGImageRef的image轉成UIimage,scale是縮放因子,需要手動調試出一個合適的值,orientation是一個方向枚舉,0表示默認方向
return [UIImage imageWithCGImage:imageRef scale:2 orientation:0];
}
//從nib加載
-(void)awakeFromNib{
// 創建12個button
for (int i = 0; i < kButtonCount; i++) {
// 創建button
UIButton * button = [[UIButton alloc] init];
// 修改button的錨點爲中間最下
button.layer.anchorPoint = CGPointMake(0.5, 1);
// 綁定button的tag屬性,便於確定每個button的初始位置角度
button.tag = i;
// 加載button默認和選中兩個狀態的原圖
UIImage *image = [UIImage imageNamed:@"LuckyAstrology"];
UIImage *imagePress = [UIImage imageNamed:@"LuckyAstrologyPressed"];
// 獲取通過自定義的方法將兩個原圖按順序裁剪的圖片
image = [self clipImage:image withIndex:i];
imagePress = [self clipImage:imagePress withIndex:i];
// 給button的默認和選中狀態設置裁剪好的圖片
[button setImage:image forState:UIControlStateNormal];
[button setImage:imagePress forState:UIControlStateSelected];
// 設置button的背景圖片
[button setBackgroundImage:[UIImage imageNamed:@"LuckyRototeSelected"] forState:UIControlStateSelected];
// 設置button的點擊事件clickButton:
[button addTarget:self action:@selector(clickButton:) forControlEvents:UIControlEventTouchUpInside];
// 將button添加到rotateView中
[self.rotateView addSubview:button];
}
}
// 一個返回值爲大轉盤對象的類方法,便於外部訪問
+(instancetype)rotateImage{
return [[NSBundle mainBundle] loadNibNamed:@"ZHYView" owner:nil options:nil][0];
}
@end
運行結果賞析: