锚点
之前提到过,视图的center
属性和图层的position
属性都指定了anchorPoint
相对于父图层的位置。图层的anchorPoint
通过position
来控制它的frame
的位置,你可以认为anchorPoint
是用来移动图层的把柄。
默认来说,anchorPoint
位于图层的中点,所以图层的将会以这个点为中心放置。anchorPoint
属性并没有被UIView
接口暴露出来,这也是视图的position属性被叫做“center”的原因。但是图层的anchorPoint
可以被移动,比如你可以把它置于图层frame
的左上角,于是图层的内容将会向右下角的position
方向移动(图3.3),而不是居中了。
图3.3 改变anchorPoint
的效果
和第二章提到的contentsRect
和contentsCenter
属性类似,anchorPoint
用单位坐标来描述,也就是图层的相对坐标,图层左上角是{0, 0},右下角是{1, 1},因此默认坐标是{0.5, 0.5}。anchorPoint
可以通过指定x和y值小于0或者大于1,使它放置在图层范围之外。
注意在图3.3中,当改变了anchorPoint
,position
属性保持固定的值并没有发生改变,但是frame
却移动了。
那在什么场合需要改变anchorPoint
呢?既然我们可以随意改变图层位置,那改变anchorPoint
不会造成困惑么?为了举例说明,我们来举一个实用的例子,创建一个模拟闹钟的项目。
钟面和钟表由四张图片组成(图3.4),为了简单说明,我们还是用传统的方式来装载和加载图片,使用四个UIImageView
实例(当然你也可以用正常的视图,设置他们图层的contents
图片)。
图3.4 组成钟面和钟表的四张图片
闹钟的组件通过IB来排列(图3.5),这些图片视图嵌套在一个容器视图之内,并且自动调整和自动布局都被禁用了。这是因为自动调整会影响到视图的frame
,而根据图3.2的演示,当视图旋转的时候,frame
是会发生改变的,这将会导致一些布局上的失灵。
我们用NSTimer
来更新闹钟,使用视图的transform
属性来旋转钟表(如果你对这个属性不太熟悉,不要着急,我们将会在第5章“变换”当中详细说明),具体代码见清单3.1
图3.5 在Interface Builder中布局闹钟视图
清单3.1 Clock
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIImageView *hourHand;
@property (nonatomic, weak) IBOutlet UIImageView *minuteHand;
@property (nonatomic, weak) IBOutlet UIImageView *secondHand;
@property (nonatomic, weak) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//start timer
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES];

//set initial hand positions
[self tick];
}
- (void)tick
{
//convert time to hours, minutes and seconds
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSUInteger units = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
NSDateComponents *components = [calendar components:units fromDate:[NSDate date]];
CGFloat hoursAngle = (components.hour / 12.0) * M_PI * 2.0;
//calculate hour hand angle //calculate minute hand angle
CGFloat minsAngle = (components.minute / 60.0) * M_PI * 2.0;
//calculate second hand angle
CGFloat secsAngle = (components.second / 60.0) * M_PI * 2.0;
//rotate hands
self.hourHand.transform = CGAffineTransformMakeRotation(hoursAngle);
self.minuteHand.transform = CGAffineTransformMakeRotation(minsAngle);
self.secondHand.transform = CGAffineTransformMakeRotation(secsAngle);
}
@end
运行项目,看起来有点奇怪(图3.6),因为钟表的图片在围绕着中心旋转,这并不是我们期待的一个支点。
图3.6 钟面,和不对齐的钟指针
你也许会认为可以在Interface Builder当中调整指针图片的位置来解决,但其实并不能达到目的,因为如果不放在钟面中间的话,同样不能正确的旋转。
也许在图片末尾添加一个透明空间也是个解决方案,但这样会让图片变大,也会消耗更多的内存,这样并不优雅。
更好的方案是使用anchorPoint
属性,我们来在-viewDidLoad
方法中添加几行代码来给每个钟指针的anchorPoint
做一些平移(清单3.2),图3.7显示了正确的结果。
清单3.2
- (void)viewDidLoad
{
[super viewDidLoad];
// adjust anchor points
self.secondHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);
self.minuteHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);
self.hourHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);
// start timer
}
图3.7 钟面,和正确对齐的钟指针