布局
UIView
中比較重要的布局屬性為frame
, bounds
, center
缚陷。CALayer
中對應(yīng)的成為frame
, bounds
, position
怪瓶。
其中frame
表示的是圖層的外部坐標(biāo),即圖層在父視圖上占據(jù)的空間疲迂。center
(position
)代表的是相對于父視圖anchorPoint
所在的位置放典,anchorPoint
會在后面講到晒衩,默認(rèn)的話就是視圖的中心點涌攻。
UIView
的frame
,bounds
,center
僅僅是存取方法,實際訪問和改變的是位于試圖下方的layer
诀蓉。并且frame
是通過bounds
,position
和transform
所計算出來的栗竖。
住當(dāng)對圖層做變換的時候,比如旋轉(zhuǎn)或者縮放,實際上代表了覆蓋在圖層旋轉(zhuǎn)之后的整個軸對齊的矩形區(qū)域,也就是說的寬高可能和 bounds 的寬高不再一致了。
錨點(anchorPoint)
前面說過 center
(position
)實際是anchorPoint
相對于父視圖的位置渠啤。也就是說可以通過修改anchorPoint
來控制frame
的位置狐肢。
anchorPoint
使用的是單位坐標(biāo),圖層左上角為{0, 0}沥曹,右下角為{1, 1}份名,默認(rèn)值為{0.5, 0.5}。當(dāng)然妓美,anchorPoint也可以位于圖層之外僵腺,也就是取值可以小于0或者大于1;
下面我們來做一個時鐘:
我們可以啟用一個
NSTimer
壶栋,然后每隔一秒獲取一次本地時間辰如,并且對應(yīng)的使用transform
來旋轉(zhuǎn)指針(transform
屬性會在后面講到)。
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *hourImageView;
@property (weak, nonatomic) IBOutlet UIImageView *minuteImageView;
@property (weak, nonatomic) IBOutlet UIImageView *secondImageView;
@property (weak, nonatomic) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(timeTick) userInfo:nil repeats:YES];
//初始化指針的位置
[self timeTick];
}
- (void)timeTick {
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierChinese];
NSUInteger units = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
NSDateComponents *components = [calendar components:units fromDate:[NSDate date]];
CGFloat hourAngle = (components.hour / 12.0) * M_PI * 2.0;
CGFloat minuteAngle = (components.minute / 60.0) * M_PI * 2.0;
CGFloat secondAngle = (components.second / 60.0) * M_PI * 2.0;
self.hourImageView.transform = CGAffineTransformMakeRotation(hourAngle);
self.minuteImageView.transform = CGAffineTransformMakeRotation(minuteAngle);
self.secondImageView.transform = CGAffineTransformMakeRotation(secondAngle);
}
@end
可以看到旋轉(zhuǎn)的支點以及指針的位置都十分奇怪贵试,因為我們圖層默認(rèn)的anchorPoint
是{0.5, 0.5}琉兜,但是現(xiàn)實中時鐘的指針卻很少有以中心為支點旋轉(zhuǎn)的凯正,所以我們可以修改一下anchorPoint
來調(diào)整我們指針的位置和旋轉(zhuǎn)的支點。在viewDidLoad
中添加如下代碼
self.hourImageView.layer.anchorPoint = CGPointMake(0.5, 0.7);
self.minuteImageView.layer.anchorPoint = CGPointMake(0.5, 0.7);
self.secondImageView.layer.anchorPoint = CGPointMake(0.5, 0.7);
坐標(biāo)系
和視圖一樣豌蟋,圖層在父視圖中也是按照層級關(guān)系放置的廊散,如果父圖層移動了,那么所有的子視圖也是跟著一起移動梧疲。這樣就可以將若干圖層作為一個整體一起移動允睹,但是有時候你需要知道一個圖層的絕對位置,或者是相對于另一個圖層的位置,而不是它當(dāng)前父圖層的位置,CALayer
提供了不同坐標(biāo)系之間相互轉(zhuǎn)換的方法:
- (CGPoint)convertPoint:(CGPoint)point fromLayer:(CALayer *)layer;
- (CGPoint)convertPoint:(CGPoint)point toLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect fromLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect toLayer:(CALayer *)layer;
翻轉(zhuǎn)的幾何結(jié)構(gòu)
我們都知道圖層的原點是左上角幌氮,但是在macOS中視圖原點卻是左下角缭受,Core Animation
可以通過 geometryFlipped
屬性來適配這兩種情況,他決定了一個圖層以及其子圖層是否垂直翻轉(zhuǎn)浩销。
Z坐標(biāo)軸
CALayer
與UIView
的一大主要區(qū)別就是CALayer
是存在于三維空間中的贯涎,除了position
和anchorPoint
之外,CALayer
還有zPosition
和zAnchorPoint
這兩個屬性慢洋,二者都是在Z軸上描述圖層位置的浮點類型。這里并沒有引申出z軸方向上的bounds等屬性陆盘,因為圖層并沒有厚度普筹。寫一個簡單的例子,下面有紅色和藍色兩個圖層隘马,紅色圖層在藍色圖層上方太防,我們稍微改變一下藍色圖層的zPosition,可以看到藍色圖層就會跑到紅色圖層上方
@interface ZAxleController ()
@property (weak, nonatomic) CALayer *blueLayer;
@property (weak, nonatomic) CALayer *redLayer;
@end
@implementation ZAxleController
- (void)viewDidLoad {
[super viewDidLoad];
CALayer *blueLayer = [CALayer layer];
blueLayer.frame = CGRectMake(100, 50, 100, 100);
blueLayer.backgroundColor = [UIColor colorWithRed:0.00 green:0.55 blue:1.00 alpha:1.00].CGColor;
[self.view.layer addSublayer:blueLayer];
self.blueLayer = blueLayer;
CALayer *redLayer = [CALayer layer];
redLayer.frame = CGRectMake(150, 100, 100, 100);
redLayer.backgroundColor = [UIColor colorWithRed:1.00 green:0.45 blue:0.43 alpha:1.00].CGColor;
[self.view.layer addSublayer:redLayer];
self.redLayer = redLayer;
}
更改一下藍色圖層的zPosition(這里改變了0.1酸员,理論上哪怕只是改變0.0000001效果也是一樣的蜒车,但是由于編譯器的原因,精度過小可能會出現(xiàn)問題幔嗦,所以最好還是不要寫的太心鹄ⅰ)。
- (IBAction)zChangeClick:(id)sender {
self.blueLayer.zPosition += 0.1;
}
Hit Testing
CALayer
并不關(guān)心任何響應(yīng)鏈?zhǔn)录?所以不能直接處理觸摸事件或者手勢邀泉。但是它有一系列的方法幫你處理事件:-containsPoint:
和 -hitTest:
嬉挡。
-containsPoint:
傳入一個點,如果這個點在當(dāng)前圖層的frame范圍內(nèi)汇恤,就返回YES庞钢,比如下面的例子,我們在-touchesBegan: withEvent:
中判斷點擊是否位于藍色圖層內(nèi)部因谎。
@interface HitTestingController ()
@property (weak, nonatomic) IBOutlet UIView *layerView;
@property (weak, nonatomic) CALayer *blueLayer;
@end
@implementation HitTestingController
- (void)viewDidLoad {
[super viewDidLoad];
CALayer *blueLayer = [CALayer layer];
blueLayer.frame = CGRectMake(50, 50, 100, 100);
blueLayer.backgroundColor = [UIColor colorWithRed:0.00 green:0.55 blue:1.00 alpha:1.00].CGColor;
[self.layerView.layer addSublayer:blueLayer];
self.blueLayer = blueLayer;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
CGPoint point = [touches.anyObject locationInView:self.view];
// 轉(zhuǎn)換為layerView中的坐標(biāo)
point = [self.blueLayer convertPoint:point fromLayer:self.view.layer];
NSString *message = @"";
if ([self.blueLayer containsPoint:point]) {
message = @"點擊位于藍色視圖中";
} else {
message = @"點擊位于藍色視圖外";
}
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}
@end
-hitTest:
同樣接收一個CGPoint
類型的參數(shù)基括,返回圖層本身或者包含這個節(jié)點的子圖層,如果這個點在圖層之外則返回nil财岔。所以上面的代碼也可以寫成這個樣子风皿。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//-hitTest:
CGPoint point = [touches.anyObject locationInView:self.view];
CALayer *layer = [self.layerView.layer hitTest:point];
NSString *message = @"";
if (layer == self.blueLayer) {
message = @"點擊位于藍色視圖中";
} else {
message = @"點擊位于藍色視圖外";
}
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}
- 需要注意的是雖然我們可以通過zPosition改變我們看到的圖層的位置饭冬,但實際上響應(yīng)鏈的順序并不會改變。
自動布局
在iOS6
中蘋果引入了自動布局揪阶,在使用UIView的時候你可以使用NSLayoutConstraint
的API使用自動布局昌抠,但如果想要隨意控制CALayer
的布局就需要手動操作。最簡單的方法就是使用CALayerDelegate
的方法:
- (void)layoutSublayersOfLayer:(CALayer *)layer;
當(dāng)圖層的bounds
發(fā)生變化或者調(diào)用-setNeedsLayout
的時候鲁僚,這個方法就會被執(zhí)行炊苫,你可以在這里手動的調(diào)整子圖層的大小與位置。
總結(jié)
本章涉及了CALayer
的幾何結(jié)構(gòu),包括它的 frame
,position
和 bounds
,介紹了三維空間內(nèi)圖層的概念,以及如何在獨立的圖層內(nèi)響應(yīng)事件,最后簡單說明了在iOS平臺,Core Animation
對自動調(diào)整和自動布局支持的缺乏冰沙。在第四章“視覺效果”當(dāng)中,我們接著介紹一些圖層外表的特性侨艾。