|
11 | 11 | #import "ASThread.h" |
12 | 12 | #import "ASDisplayNodeExtras.h" |
13 | 13 | #import "ASImageNode.h" |
| 14 | +#import "AsyncDisplayKit+Debug.h" |
| 15 | +#import "ASInternalHelpers.h" |
14 | 16 |
|
15 | 17 | // UIControl allows dragging some distance outside of the control itself during |
16 | 18 | // tracking. This value depends on the device idiom (25 or 70 points), so |
@@ -71,8 +73,6 @@ @interface ASControlNode () |
71 | 73 |
|
72 | 74 | @end |
73 | 75 |
|
74 | | -static BOOL _enableHitTestDebug = NO; |
75 | | - |
76 | 76 | @implementation ASControlNode |
77 | 77 | { |
78 | 78 | ASImageNode *_debugHighlightOverlay; |
@@ -262,14 +262,15 @@ - (void)addTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeE |
262 | 262 | _controlEventDispatchTable = [[NSMutableDictionary alloc] initWithCapacity:kASControlNodeEventDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries. |
263 | 263 |
|
264 | 264 | // only show tap-able areas for views with 1 or more addTarget:action: pairs |
265 | | - if (_enableHitTestDebug) { |
266 | | - |
267 | | - // add a highlight overlay node with area of ASControlNode + UIEdgeInsets |
268 | | - self.clipsToBounds = NO; |
269 | | - _debugHighlightOverlay = [[ASImageNode alloc] init]; |
270 | | - _debugHighlightOverlay.zPosition = 1000; // CALayer doesn't have -moveSublayerToFront, but this will ensure we're over the top of any siblings. |
271 | | - _debugHighlightOverlay.layerBacked = YES; |
272 | | - [self addSubnode:_debugHighlightOverlay]; |
| 265 | + if ([ASControlNode enableHitTestDebug] && _debugHighlightOverlay == nil) { |
| 266 | + ASPerformBlockOnMainThread(^{ |
| 267 | + // add a highlight overlay node with area of ASControlNode + UIEdgeInsets |
| 268 | + self.clipsToBounds = NO; |
| 269 | + _debugHighlightOverlay = [[ASImageNode alloc] init]; |
| 270 | + _debugHighlightOverlay.zPosition = 1000; // ensure we're over the top of any siblings |
| 271 | + _debugHighlightOverlay.layerBacked = YES; |
| 272 | + [self addSubnode:_debugHighlightOverlay]; |
| 273 | + }); |
273 | 274 | } |
274 | 275 | } |
275 | 276 |
|
@@ -470,134 +471,9 @@ - (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent |
470 | 471 | } |
471 | 472 |
|
472 | 473 | #pragma mark - Debug |
473 | | -// Layout method required when _enableHitTestDebug is enabled. |
474 | | -- (void)layout |
475 | | -{ |
476 | | - [super layout]; |
477 | | - |
478 | | - if (_debugHighlightOverlay) { |
479 | | - |
480 | | - // Even if our parents don't have clipsToBounds set and would allow us to display the debug overlay, UIKit event delivery (hitTest:) |
481 | | - // will not search sub-hierarchies if one of our parents does not return YES for pointInside:. In such a scenario, hitTestSlop |
482 | | - // may not be able to expand the tap target as much as desired without also setting some hitTestSlop on the limiting parents. |
483 | | - CGRect intersectRect = UIEdgeInsetsInsetRect(self.bounds, [self hitTestSlop]); |
484 | | - UIRectEdge clippedEdges = UIRectEdgeNone; |
485 | | - UIRectEdge clipsToBoundsClippedEdges = UIRectEdgeNone; |
486 | | - CALayer *layer = self.layer; |
487 | | - CALayer *intersectLayer = layer; |
488 | | - CALayer *intersectSuperlayer = layer.superlayer; |
489 | | - |
490 | | - // Stop climbing if we encounter a UIScrollView, as its offset bounds origin may make it seem like our events will be clipped when |
491 | | - // scrolling will actually reveal them (because this process will not re-run due to scrolling) |
492 | | - while (intersectSuperlayer && ![intersectSuperlayer.delegate respondsToSelector:@selector(contentOffset)]) { |
493 | | - // Get our parent's tappable bounds. If the parent has an associated node, consider hitTestSlop, as it will extend its pointInside:. |
494 | | - CGRect parentHitRect = intersectSuperlayer.bounds; |
495 | | - BOOL parentClipsToBounds = NO; |
496 | | - |
497 | | - ASDisplayNode *parentNode = ASLayerToDisplayNode(intersectSuperlayer); |
498 | | - if (parentNode) { |
499 | | - UIEdgeInsets parentSlop = [parentNode hitTestSlop]; |
500 | | - |
501 | | - // if parent has a hitTestSlop as well, we need to account for the fact that events will be routed towards us in that area too. |
502 | | - if (!UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, parentSlop)) { |
503 | | - parentClipsToBounds = parentNode.clipsToBounds; |
504 | | - // if the parent is clipping, this will prevent us from showing the overlay outside that area. |
505 | | - // in this case, we will make the overlay smaller so that the special highlight to indicate the overlay |
506 | | - // cannot accurately display the true tappable area is shown. |
507 | | - if (!parentClipsToBounds) { |
508 | | - parentHitRect = UIEdgeInsetsInsetRect(parentHitRect, [parentNode hitTestSlop]); |
509 | | - } |
510 | | - } |
511 | | - } |
512 | | - |
513 | | - // Convert our current rectangle to parent coordinates, and intersect with the parent's hit rect. |
514 | | - CGRect intersectRectInParentCoordinates = [intersectSuperlayer convertRect:intersectRect fromLayer:intersectLayer]; |
515 | | - intersectRect = CGRectIntersection(parentHitRect, intersectRectInParentCoordinates); |
516 | | - if (!CGSizeEqualToSize(parentHitRect.size, intersectRectInParentCoordinates.size)) { |
517 | | - clippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates |
518 | | - parentRect:parentHitRect rectEdge:clippedEdges]; |
519 | | - if (parentClipsToBounds) { |
520 | | - clipsToBoundsClippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates |
521 | | - parentRect:parentHitRect rectEdge:clipsToBoundsClippedEdges]; |
522 | | - } |
523 | | - } |
524 | | - |
525 | | - // Advance up the tree. |
526 | | - intersectLayer = intersectSuperlayer; |
527 | | - intersectSuperlayer = intersectLayer.superlayer; |
528 | | - } |
529 | | - |
530 | | - CGRect finalRect = [intersectLayer convertRect:intersectRect toLayer:layer]; |
531 | | - UIColor *fillColor = [[UIColor greenColor] colorWithAlphaComponent:0.4]; |
532 | | - |
533 | | - // determine if edges are clipped |
534 | | - if (clippedEdges == UIRectEdgeNone) { |
535 | | - _debugHighlightOverlay.backgroundColor = fillColor; |
536 | | - } else { |
537 | | - const CGFloat borderWidth = 2.0; |
538 | | - UIColor *borderColor = [[UIColor orangeColor] colorWithAlphaComponent:0.8]; |
539 | | - UIColor *clipsBorderColor = [UIColor colorWithRed:30/255.0 green:90/255.0 blue:50/255.0 alpha:0.7]; |
540 | | - CGRect imgRect = CGRectMake(0, 0, 2.0 * borderWidth + 1.0, 2.0 * borderWidth + 1.0); |
541 | | - UIGraphicsBeginImageContext(imgRect.size); |
542 | | - |
543 | | - [fillColor setFill]; |
544 | | - UIRectFill(imgRect); |
545 | | - |
546 | | - [self drawEdgeIfClippedWithEdges:clippedEdges color:clipsBorderColor borderWidth:borderWidth imgRect:imgRect]; |
547 | | - [self drawEdgeIfClippedWithEdges:clipsToBoundsClippedEdges color:borderColor borderWidth:borderWidth imgRect:imgRect]; |
548 | | - |
549 | | - UIImage *debugHighlightImage = UIGraphicsGetImageFromCurrentImageContext(); |
550 | | - UIGraphicsEndImageContext(); |
551 | | - |
552 | | - UIEdgeInsets edgeInsets = UIEdgeInsetsMake(borderWidth, borderWidth, borderWidth, borderWidth); |
553 | | - _debugHighlightOverlay.image = [debugHighlightImage resizableImageWithCapInsets:edgeInsets |
554 | | - resizingMode:UIImageResizingModeStretch]; |
555 | | - _debugHighlightOverlay.backgroundColor = nil; |
556 | | - } |
557 | | - |
558 | | - _debugHighlightOverlay.frame = finalRect; |
559 | | - } |
560 | | -} |
561 | | - |
562 | | -- (UIRectEdge)setEdgesOfIntersectionForChildRect:(CGRect)childRect parentRect:(CGRect)parentRect rectEdge:(UIRectEdge)rectEdge |
563 | | -{ |
564 | | - if (childRect.origin.y < parentRect.origin.y) { |
565 | | - rectEdge |= UIRectEdgeTop; |
566 | | - } |
567 | | - if (childRect.origin.x < parentRect.origin.x) { |
568 | | - rectEdge |= UIRectEdgeLeft; |
569 | | - } |
570 | | - if (CGRectGetMaxY(childRect) > CGRectGetMaxY(parentRect)) { |
571 | | - rectEdge |= UIRectEdgeBottom; |
572 | | - } |
573 | | - if (CGRectGetMaxX(childRect) > CGRectGetMaxX(parentRect)) { |
574 | | - rectEdge |= UIRectEdgeRight; |
575 | | - } |
576 | | - |
577 | | - return rectEdge; |
578 | | -} |
579 | | - |
580 | | -- (void)drawEdgeIfClippedWithEdges:(UIRectEdge)rectEdge color:(UIColor *)color borderWidth:(CGFloat)borderWidth imgRect:(CGRect)imgRect |
581 | | -{ |
582 | | - [color setFill]; |
583 | | - |
584 | | - if (rectEdge & UIRectEdgeTop) { |
585 | | - UIRectFill(CGRectMake(0.0, 0.0, imgRect.size.width, borderWidth)); |
586 | | - } |
587 | | - if (rectEdge & UIRectEdgeLeft) { |
588 | | - UIRectFill(CGRectMake(0.0, 0.0, borderWidth, imgRect.size.height)); |
589 | | - } |
590 | | - if (rectEdge & UIRectEdgeBottom) { |
591 | | - UIRectFill(CGRectMake(0.0, imgRect.size.height - borderWidth, imgRect.size.width, borderWidth)); |
592 | | - } |
593 | | - if (rectEdge & UIRectEdgeRight) { |
594 | | - UIRectFill(CGRectMake(imgRect.size.width - borderWidth, 0.0, borderWidth, imgRect.size.height)); |
595 | | - } |
596 | | -} |
597 | | - |
598 | | -+ (void)setEnableHitTestDebug:(BOOL)enable |
| 474 | +- (ASImageNode *)debugHighlightOverlay |
599 | 475 | { |
600 | | - _enableHitTestDebug = enable; |
| 476 | + return _debugHighlightOverlay; |
601 | 477 | } |
602 | 478 |
|
603 | 479 | @end |
0 commit comments