锚点

    之前提到过,视图的center属性和图层的position属性都指定了anchorPoint相对于父图层的位置。图层的anchorPoint通过position来控制它的frame的位置,你可以认为anchorPoint是用来移动图层的把柄

    默认来说,anchorPoint位于图层的中点,所以图层的将会以这个点为中心放置。anchorPoint属性并没有被UIView接口暴露出来,这也是视图的position属性被叫做“center”的原因。但是图层的anchorPoint可以被移动,比如你可以把它置于图层frame的左上角,于是图层的内容将会向右下角的position方向移动(图3.3),而不是居中了。

图3.3

图3.3 改变anchorPoint的效果

    和第二章提到的contentsRectcontentsCenter属性类似,anchorPoint单位坐标来描述,也就是图层的相对坐标,图层左上角是{0, 0},右下角是{1, 1},因此默认坐标是{0.5, 0.5}。anchorPoint可以通过指定x和y值小于0或者大于1,使它放置在图层范围之外。

    注意在图3.3中,当改变了anchorPointposition属性保持固定的值并没有发生改变,但是frame却移动了。

    那在什么场合需要改变anchorPoint呢?既然我们可以随意改变图层位置,那改变anchorPoint不会造成困惑么?为了举例说明,我们来举一个实用的例子,创建一个模拟闹钟的项目。

    钟面和钟表由四张图片组成(图3.4),为了简单说明,我们还是用传统的方式来装载和加载图片,使用四个UIImageView实例(当然你也可以用正常的视图,设置他们图层的contents图片)。

图3.4

图3.4 组成钟面和钟表的四张图片

    闹钟的组件通过IB来排列(图3.5),这些图片视图嵌套在一个容器视图之内,并且自动调整和自动布局都被禁用了。这是因为自动调整会影响到视图的frame,而根据图3.2的演示,当视图旋转的时候,frame是会发生改变的,这将会导致一些布局上的失灵。

    我们用NSTimer来更新闹钟,使用视图的transform属性来旋转钟表(如果你对这个属性不太熟悉,不要着急,我们将会在第5章“变换”当中详细说明),具体代码见清单3.1

图3.5

图3.5 在Interface Builder中布局闹钟视图

清单3.1 Clock

  1. @interface ViewController ()
  2. @property (nonatomic, weak) IBOutlet UIImageView *hourHand;
  3. @property (nonatomic, weak) IBOutlet UIImageView *minuteHand;
  4. @property (nonatomic, weak) IBOutlet UIImageView *secondHand;
  5. @property (nonatomic, weak) NSTimer *timer;
  6. @end
  7. @implementation ViewController
  8. - (void)viewDidLoad
  9. {
  10. [super viewDidLoad];
  11. //start timer
  12. self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES];
  13. //set initial hand positions
  14. [self tick];
  15. }
  16. - (void)tick
  17. {
  18. //convert time to hours, minutes and seconds
  19. NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
  20. NSUInteger units = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
  21. NSDateComponents *components = [calendar components:units fromDate:[NSDate date]];
  22. CGFloat hoursAngle = (components.hour / 12.0) * M_PI * 2.0;
  23. //calculate hour hand angle //calculate minute hand angle
  24. CGFloat minsAngle = (components.minute / 60.0) * M_PI * 2.0;
  25. //calculate second hand angle
  26. CGFloat secsAngle = (components.second / 60.0) * M_PI * 2.0;
  27. //rotate hands
  28. self.hourHand.transform = CGAffineTransformMakeRotation(hoursAngle);
  29. self.minuteHand.transform = CGAffineTransformMakeRotation(minsAngle);
  30. self.secondHand.transform = CGAffineTransformMakeRotation(secsAngle);
  31. }
  32. @end

    运行项目,看起来有点奇怪(图3.6),因为钟表的图片在围绕着中心旋转,这并不是我们期待的一个支点。

图3.6

图3.6 钟面,和不对齐的钟指针

    你也许会认为可以在Interface Builder当中调整指针图片的位置来解决,但其实并不能达到目的,因为如果不放在钟面中间的话,同样不能正确的旋转。

    也许在图片末尾添加一个透明空间也是个解决方案,但这样会让图片变大,也会消耗更多的内存,这样并不优雅。

    更好的方案是使用anchorPoint属性,我们来在-viewDidLoad方法中添加几行代码来给每个钟指针的anchorPoint做一些平移(清单3.2),图3.7显示了正确的结果。

清单3.2

  1. - (void)viewDidLoad
  2. {
  3. [super viewDidLoad];
  4. // adjust anchor points
  5. self.secondHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);
  6. self.minuteHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);
  7. self.hourHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);
  8. // start timer
  9. }

图3.7

图3.7 钟面,和正确对齐的钟指针