Hit Testing

第一章“图层树”证实了最好使用图层相关视图,而不是创建独立的图层关系。其中一个原因就是要处理额外复杂的触摸事件。

CALayer并不关心任何响应链事件,所以不能直接处理触摸事件或者手势。但是它有一系列的方法帮你处理事件:-containsPoint:-hitTest:

-containsPoint:接受一个在本图层坐标系下的CGPoint,如果这个点在图层frame范围内就返回YES。如清单3.4所示第一章的项目的另一个合适的版本,也就是使用-containsPoint:方法来判断到底是白色还是蓝色的图层被触摸了
(图3.10)。这需要把触摸坐标转换成每个图层坐标系下的坐标,结果很不方便。

清单3.4 使用containsPoint判断被点击的图层

  1. @interface ViewController ()
  2. @property (nonatomic, weak) IBOutlet UIView *layerView;
  3. @property (nonatomic, weak) CALayer *blueLayer;
  4. @end
  5. @implementation ViewController
  6. - (void)viewDidLoad
  7. {
  8. [super viewDidLoad];
  9. //create sublayer
  10. self.blueLayer = [CALayer layer];
  11. self.blueLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
  12. self.blueLayer.backgroundColor = [UIColor blueColor].CGColor;
  13. //add it to our view
  14. [self.layerView.layer addSublayer:self.blueLayer];
  15. }
  16. - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
  17. {
  18. //get touch position relative to main view
  19. CGPoint point = [[touches anyObject] locationInView:self.view];
  20. //convert point to the white layer's coordinates
  21. point = [self.layerView.layer convertPoint:point fromLayer:self.view.layer];
  22. //get layer using containsPoint:
  23. if ([self.layerView.layer containsPoint:point]) {
  24. //convert point to blueLayer’s coordinates
  25. point = [self.blueLayer convertPoint:point fromLayer:self.layerView.layer];
  26. if ([self.blueLayer containsPoint:point]) {
  27. [[[UIAlertView alloc] initWithTitle:@"Inside Blue Layer"
  28. message:nil
  29. delegate:nil
  30. cancelButtonTitle:@"OK"
  31. otherButtonTitles:nil] show];
  32. } else {
  33. [[[UIAlertView alloc] initWithTitle:@"Inside White Layer"
  34. message:nil
  35. delegate:nil
  36. cancelButtonTitle:@"OK"
  37. otherButtonTitles:nil] show];
  38. }
  39. }
  40. }
  41. @end

图3.10

图3.10 点击图层被正确标识

-hitTest:方法同样接受一个CGPoint类型参数,而不是BOOL类型,它返回图层本身,或者包含这个坐标点的叶子节点图层。这意味着不再需要像使用-containsPoint:那样,人工地在每个子图层变换或者测试点击的坐标。如果这个点在最外面图层的范围之外,则返回nil。具体使用-hitTest:方法被点击图层的代码如清单3.5所示。

清单3.5 使用hitTest判断被点击的图层

  1. - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
  2. {
  3. //get touch position
  4. CGPoint point = [[touches anyObject] locationInView:self.view];
  5. //get touched layer
  6. CALayer *layer = [self.layerView.layer hitTest:point];
  7. //get layer using hitTest
  8. if (layer == self.blueLayer) {
  9. [[[UIAlertView alloc] initWithTitle:@"Inside Blue Layer"
  10. message:nil
  11. delegate:nil
  12. cancelButtonTitle:@"OK"
  13. otherButtonTitles:nil] show];
  14. } else if (layer == self.layerView.layer) {
  15. [[[UIAlertView alloc] initWithTitle:@"Inside White Layer"
  16. message:nil
  17. delegate:nil
  18. cancelButtonTitle:@"OK"
  19. otherButtonTitles:nil] show];
  20. }
  21. }

注意当调用图层的-hitTest:方法时,测算的顺序严格依赖于图层树当中的图层顺序(和UIView处理事件类似)。之前提到的zPosition属性可以明显改变屏幕上图层的顺序,但不能改变事件传递的顺序。

这意味着如果改变了图层的z轴顺序,你会发现将不能够检测到最前方的视图点击事件,这是因为被另一个图层遮盖住了,虽然它的zPosition值较小,但是在图层树中的顺序靠前。我们将在第五章详细讨论这个问题。