Swiftgram/AsyncDisplayKit/AsyncDisplayKit+Debug.m
Hannah Troisi c857e809f4 Clean up header comments (for consistent Facebook licensing info) (#1741)
[Licensing] Clean up header comments (for consistent Facebook licensing info)
2016-06-11 23:31:39 -07:00

194 lines
7.7 KiB
Objective-C

//
// AsyncDisplayKit+Debug.m
// AsyncDisplayKit
//
// Created by Hannah Troisi on 3/7/16.
//
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import "AsyncDisplayKit+Debug.h"
#import "ASDisplayNode+Subclasses.h"
#import "ASDisplayNodeExtras.h"
static BOOL __shouldShowImageScalingOverlay = NO;
@implementation ASImageNode (Debugging)
+ (void)setShouldShowImageScalingOverlay:(BOOL)show;
{
__shouldShowImageScalingOverlay = show;
}
+ (BOOL)shouldShowImageScalingOverlay
{
return __shouldShowImageScalingOverlay;
}
@end
static BOOL __enableHitTestDebug = NO;
@interface ASControlNode (DebuggingInternal)
- (ASImageNode *)debugHighlightOverlay;
@end
@implementation ASControlNode (Debugging)
+ (void)setEnableHitTestDebug:(BOOL)enable
{
__enableHitTestDebug = enable;
}
+ (BOOL)enableHitTestDebug
{
return __enableHitTestDebug;
}
// layout method required ONLY when hitTestDebug is enabled
- (void)layout
{
[super layout];
if ([ASControlNode enableHitTestDebug]) {
// Construct hitTestDebug highlight overlay frame indicating tappable area of a node, which can be restricted by two things:
// (1) Any parent's tapable area (its own bounds + hitTestSlop) may restrict the desired tappable area expansion using
// hitTestSlop of a child as UIKit event delivery (hitTest:) will not search sub-hierarchies if one of our parents does
// not return YES for pointInside:. To circumvent this restriction, a developer will need to set / adjust the hitTestSlop
// on the limiting parent. This is indicated in the overlay by a dark GREEN edge. This is an ACTUAL restriction.
// (2) Any parent's .clipToBounds. If a parent is clipping, we cannot show the overlay outside that area
// (although it still respond to touch). To indicate that the overlay cannot accurately display the true tappable area,
// the overlay will have an ORANGE edge. This is a VISUALIZATION restriction.
CGRect intersectRect = UIEdgeInsetsInsetRect(self.bounds, [self hitTestSlop]);
UIRectEdge clippedEdges = UIRectEdgeNone;
UIRectEdge clipsToBoundsClippedEdges = UIRectEdgeNone;
CALayer *layer = self.layer;
CALayer *intersectLayer = layer;
CALayer *intersectSuperlayer = layer.superlayer;
// FIXED: Stop climbing hierarchy if UIScrollView is encountered (its offset bounds origin may make it seem like our events
// will be clipped when scrolling will actually reveal them (because this process will not re-run due to scrolling))
while (intersectSuperlayer && ![intersectSuperlayer.delegate respondsToSelector:@selector(contentOffset)]) {
// Get parent's tappable area
CGRect parentHitRect = intersectSuperlayer.bounds;
BOOL parentClipsToBounds = NO;
// If parent is a node, tappable area may be expanded by hitTestSlop
ASDisplayNode *parentNode = ASLayerToDisplayNode(intersectSuperlayer);
if (parentNode) {
UIEdgeInsets parentSlop = [parentNode hitTestSlop];
// If parent has hitTestSlop, expand tappable area (if parent doesn't clipToBounds)
if (!UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, parentSlop)) {
parentClipsToBounds = parentNode.clipsToBounds;
if (!parentClipsToBounds) {
parentHitRect = UIEdgeInsetsInsetRect(parentHitRect, [parentNode hitTestSlop]);
}
}
}
// Convert our current rect to parent coordinates
CGRect intersectRectInParentCoordinates = [intersectSuperlayer convertRect:intersectRect fromLayer:intersectLayer];
// Intersect rect with the parent's tappable area rect
intersectRect = CGRectIntersection(parentHitRect, intersectRectInParentCoordinates);
if (!CGSizeEqualToSize(parentHitRect.size, intersectRectInParentCoordinates.size)) {
clippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates
parentRect:parentHitRect rectEdge:clippedEdges];
if (parentClipsToBounds) {
clipsToBoundsClippedEdges = [self setEdgesOfIntersectionForChildRect:intersectRectInParentCoordinates
parentRect:parentHitRect rectEdge:clipsToBoundsClippedEdges];
}
}
// move up hierarchy
intersectLayer = intersectSuperlayer;
intersectSuperlayer = intersectLayer.superlayer;
}
// produce final overlay image (or fill background if edges aren't restricted)
CGRect finalRect = [intersectLayer convertRect:intersectRect toLayer:layer];
UIColor *fillColor = [[UIColor greenColor] colorWithAlphaComponent:0.4];
ASImageNode *debugOverlay = [self debugHighlightOverlay];
// determine if edges are clipped and if so, highlight the restricted edges
if (clippedEdges == UIRectEdgeNone) {
debugOverlay.backgroundColor = fillColor;
} else {
const CGFloat borderWidth = 2.0;
UIColor *borderColor = [[UIColor orangeColor] colorWithAlphaComponent:0.8];
UIColor *clipsBorderColor = [UIColor colorWithRed:30/255.0 green:90/255.0 blue:50/255.0 alpha:0.7];
CGRect imgRect = CGRectMake(0, 0, 2.0 * borderWidth + 1.0, 2.0 * borderWidth + 1.0);
UIGraphicsBeginImageContext(imgRect.size);
[fillColor setFill];
UIRectFill(imgRect);
[self drawEdgeIfClippedWithEdges:clippedEdges color:clipsBorderColor borderWidth:borderWidth imgRect:imgRect];
[self drawEdgeIfClippedWithEdges:clipsToBoundsClippedEdges color:borderColor borderWidth:borderWidth imgRect:imgRect];
UIImage *debugHighlightImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIEdgeInsets edgeInsets = UIEdgeInsetsMake(borderWidth, borderWidth, borderWidth, borderWidth);
debugOverlay.image = [debugHighlightImage resizableImageWithCapInsets:edgeInsets resizingMode:UIImageResizingModeStretch];
debugOverlay.backgroundColor = nil;
}
debugOverlay.frame = finalRect;
}
}
- (UIRectEdge)setEdgesOfIntersectionForChildRect:(CGRect)childRect parentRect:(CGRect)parentRect rectEdge:(UIRectEdge)rectEdge
{
// determine which edges of childRect are outside parentRect (and thus will be clipped)
if (childRect.origin.y < parentRect.origin.y) {
rectEdge |= UIRectEdgeTop;
}
if (childRect.origin.x < parentRect.origin.x) {
rectEdge |= UIRectEdgeLeft;
}
if (CGRectGetMaxY(childRect) > CGRectGetMaxY(parentRect)) {
rectEdge |= UIRectEdgeBottom;
}
if (CGRectGetMaxX(childRect) > CGRectGetMaxX(parentRect)) {
rectEdge |= UIRectEdgeRight;
}
return rectEdge;
}
- (void)drawEdgeIfClippedWithEdges:(UIRectEdge)rectEdge color:(UIColor *)color borderWidth:(CGFloat)borderWidth imgRect:(CGRect)imgRect
{
[color setFill];
// highlight individual edges of overlay if edge is restricted by parentRect
// so that the developer is aware that increasing hitTestSlop will not result in an expanded tappable area
if (rectEdge & UIRectEdgeTop) {
UIRectFill(CGRectMake(0.0, 0.0, imgRect.size.width, borderWidth));
}
if (rectEdge & UIRectEdgeLeft) {
UIRectFill(CGRectMake(0.0, 0.0, borderWidth, imgRect.size.height));
}
if (rectEdge & UIRectEdgeBottom) {
UIRectFill(CGRectMake(0.0, imgRect.size.height - borderWidth, imgRect.size.width, borderWidth));
}
if (rectEdge & UIRectEdgeRight) {
UIRectFill(CGRectMake(imgRect.size.width - borderWidth, 0.0, borderWidth, imgRect.size.height));
}
}
@end