Skip to content

Commit

Permalink
Add layer-action support to nodes (#1396)
Browse files Browse the repository at this point in the history
* Add layer-action support to nodes, unify hierarchy notifications on it

* Better pending state

* Fix bool

* Skip extra copy

* Never run default actions

* Continue the search
  • Loading branch information
Adlai-Holler committed Mar 13, 2019
1 parent 9d77ef9 commit 34f1621
Show file tree
Hide file tree
Showing 10 changed files with 83 additions and 90 deletions.
7 changes: 7 additions & 0 deletions Source/ASDisplayNode+Subclasses.h
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,13 @@ AS_CATEGORY_IMPLEMENTABLE
*/
@property (readonly) CGFloat contentsScaleForDisplay;

/**
* Called as part of actionForLayer:forKey:. Gives the node a chance to provide a custom action for its layer.
*
* The default implementation returns NSNull, indicating that no action should be taken.
*/
AS_CATEGORY_IMPLEMENTABLE
- (nullable id<CAAction>)layerActionForKey:(NSString *)event;

#pragma mark - Touch handling
/** @name Touch handling */
Expand Down
2 changes: 2 additions & 0 deletions Source/ASDisplayNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,8 @@ AS_EXTERN NSInteger const ASDefaultDrawingPriority;
@property (getter=isExclusiveTouch) BOOL exclusiveTouch; // default=NO
#endif

@property (nullable, copy) NSDictionary<NSString *, id<CAAction>> *actions; // default = nil

/**
* @abstract The node view's background color.
*
Expand Down
19 changes: 8 additions & 11 deletions Source/ASDisplayNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,7 @@

static ASDisplayNodeNonFatalErrorBlock _nonFatalErrorBlock = nil;

// Forward declare CALayerDelegate protocol as the iOS 10 SDK moves CALayerDelegate from an informal delegate to a protocol.
// We have to forward declare the protocol as this place otherwise it will not compile compiling with an Base SDK < iOS 10
@protocol CALayerDelegate;

@interface ASDisplayNode () <UIGestureRecognizerDelegate, CALayerDelegate, _ASDisplayLayerDelegate, ASCATransactionQueueObserving>
@interface ASDisplayNode () <UIGestureRecognizerDelegate, _ASDisplayLayerDelegate, ASCATransactionQueueObserving>
/**
* See ASDisplayNodeInternal.h for ivars
*/
Expand Down Expand Up @@ -107,9 +103,10 @@ BOOL ASDisplayNodeNeedsSpecialPropertiesHandling(BOOL isSynchronous, BOOL isLaye
return result;
}

void StubImplementationWithNoArgs(id receiver) {}
void StubImplementationWithSizeRange(id receiver, ASSizeRange sr) {}
void StubImplementationWithTwoInterfaceStates(id receiver, ASInterfaceState s0, ASInterfaceState s1) {}
void StubImplementationWithNoArgs(id receiver, SEL _cmd) {}
void StubImplementationWithSizeRange(id receiver, SEL _cmd, ASSizeRange sr) {}
void StubImplementationWithTwoInterfaceStates(id receiver, SEL _cmd, ASInterfaceState s0, ASInterfaceState s1) {}
id StubLayerActionImplementation(id receiver, SEL _cmd, NSString *key) { return (id)kCFNull; }

/**
* Returns ASDisplayNodeFlags for the given class/instance. instance MAY BE NIL.
Expand Down Expand Up @@ -281,6 +278,8 @@ + (void)initialize
auto interfaceStateType = std::string(@encode(ASInterfaceState));
auto type1 = "v@:" + interfaceStateType + interfaceStateType;
class_addMethod(self, @selector(interfaceStateDidChange:fromState:), (IMP)StubImplementationWithTwoInterfaceStates, type1.c_str());

class_addMethod(self, @selector(layerActionForKey:), (IMP)StubLayerActionImplementation, "@@:@");
}
}

Expand Down Expand Up @@ -1804,7 +1803,6 @@ - (void)subnodeDisplayDidFinish:(ASDisplayNode *)subnode

#pragma mark <CALayerDelegate>

// We are only the delegate for the layer when we are layer-backed, as UIView performs this function normally
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
if (event == kCAOnOrderIn) {
Expand All @@ -1813,8 +1811,7 @@ - (void)subnodeDisplayDidFinish:(ASDisplayNode *)subnode
[self __exitHierarchy];
}

ASDisplayNodeAssert(_flags.layerBacked, @"We shouldn't get called back here unless we are layer-backed.");
return (id)kCFNull;
return [self layerActionForKey:event];
}

#pragma mark - Error Handling
Expand Down
1 change: 1 addition & 0 deletions Source/Details/UIView+ASConvenience.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic) BOOL allowsGroupOpacity;
@property (nonatomic) BOOL allowsEdgeAntialiasing;
@property (nonatomic) unsigned int edgeAntialiasingMask;
@property (nonatomic, nullable, copy) NSDictionary<NSString *, id<CAAction>> *actions;

- (void)setNeedsDisplay;
- (void)setNeedsLayout;
Expand Down
7 changes: 7 additions & 0 deletions Source/Details/_ASDisplayLayer.mm
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ - (void)setNeedsDisplay

#pragma mark -

+ (id<CAAction>)defaultActionForKey:(NSString *)event
{
// We never want to run one of CA's root default actions. So if we return nil from actionForLayer:forKey:, and let CA
// dig into the actions dictionary, and it doesn't find it there, it will check here and we need to stop the search.
return (id)kCFNull;
}

+ (dispatch_queue_t)displayQueue
{
static dispatch_queue_t displayQueue = NULL;
Expand Down
23 changes: 10 additions & 13 deletions Source/Details/_ASDisplayView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -153,22 +153,19 @@ - (NSString *)description

#pragma mark - UIView Overrides

- (void)willMoveToWindow:(UIWindow *)newWindow
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
BOOL visible = (newWindow != nil);
if (visible && !node.inHierarchy) {
[node __enterHierarchy];
}
}
id<CAAction> uikitAction = [super actionForLayer:layer forKey:event];

- (void)didMoveToWindow
{
ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar.
BOOL visible = (self.window != nil);
if (!visible && node.inHierarchy) {
[node __exitHierarchy];
// Even though the UIKit action will take precedence, we still unconditionally forward to the node so that it can
// track events like kCAOnOrderIn.
id<CAAction> nodeAction = [_asyncdisplaykit_node actionForLayer:layer forKey:event];

// If UIKit specifies an action, that takes precedence. That's an animation block so it's explicit.
if (uikitAction && uikitAction != (id)kCFNull) {
return uikitAction;
}
return nodeAction;
}

- (void)willMoveToSuperview:(UIView *)newSuperview
Expand Down
12 changes: 12 additions & 0 deletions Source/Private/ASDisplayNode+UIViewBridge.mm
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,18 @@ - (void)setInsetsLayoutMarginsFromSafeArea:(BOOL)insetsLayoutMarginsFromSafeArea
}
}

- (NSDictionary<NSString *,id<CAAction>> *)actions
{
_bridge_prologue_read;
return _getFromLayer(actions);
}

- (void)setActions:(NSDictionary<NSString *,id<CAAction>> *)actions
{
_bridge_prologue_write;
_setToLayer(actions, actions);
}

- (void)safeAreaInsetsDidChange
{
ASDisplayNodeAssertMainThread();
Expand Down
2 changes: 1 addition & 1 deletion Source/Private/ASDisplayNodeInternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ AS_EXTERN NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimest

#define NUM_CLIP_CORNER_LAYERS 4

@interface ASDisplayNode () <_ASTransitionContextCompletionDelegate>
@interface ASDisplayNode () <_ASTransitionContextCompletionDelegate, CALayerDelegate>
{
@package
AS::RecursiveMutex __instanceLock__;
Expand Down
86 changes: 21 additions & 65 deletions Source/Private/_ASPendingState.mm
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,12 @@
int setLayoutMargins:1;
int setPreservesSuperviewLayoutMargins:1;
int setInsetsLayoutMarginsFromSafeArea:1;
int setActions:1;
} ASPendingStateFlags;


static constexpr ASPendingStateFlags kZeroFlags = {0};

@implementation _ASPendingState
{
@package //Expose all ivars for ASDisplayNode to bypass getters for efficiency
Expand Down Expand Up @@ -140,6 +144,7 @@ @implementation _ASPendingState
CGPoint accessibilityActivationPoint;
UIBezierPath *accessibilityPath;
UISemanticContentAttribute semanticContentAttribute API_AVAILABLE(ios(9.0), tvos(9.0));
NSDictionary<NSString *, id<CAAction>> *actions;

ASPendingStateFlags _flags;
}
Expand Down Expand Up @@ -209,6 +214,7 @@ ASDISPLAYNODE_INLINE void ASPendingStateApplyMetricsToLayer(_ASPendingState *sta
@synthesize layoutMargins=layoutMargins;
@synthesize preservesSuperviewLayoutMargins=preservesSuperviewLayoutMargins;
@synthesize insetsLayoutMarginsFromSafeArea=insetsLayoutMarginsFromSafeArea;
@synthesize actions=actions;

static CGColorRef blackColorRef = NULL;
static UIColor *defaultTintColor = nil;
Expand Down Expand Up @@ -586,6 +592,12 @@ - (void)setSemanticContentAttribute:(UISemanticContentAttribute)attribute API_AV
_flags.setSemanticContentAttribute = YES;
}

- (void)setActions:(NSDictionary<NSString *,id<CAAction>> *)actionsArg
{
actions = [actionsArg copy];
_flags.setActions = YES;
}

- (BOOL)isAccessibilityElement
{
return isAccessibilityElement;
Expand Down Expand Up @@ -917,6 +929,9 @@ - (void)applyToLayer:(CALayer *)layer
if (flags.setOpaque)
ASDisplayNodeAssert(layer.opaque == opaque, @"Didn't set opaque as desired");

if (flags.setActions)
layer.actions = actions;

ASPendingStateApplyMetricsToLayer(self, layer);

if (flags.needsLayout)
Expand All @@ -936,7 +951,7 @@ - (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)specialPr
because a different setter would be called.
*/

CALayer *layer = view.layer;
unowned CALayer *layer = view.layer;

ASPendingStateFlags flags = _flags;
if (__shouldSetNeedsDisplay(layer)) {
Expand Down Expand Up @@ -979,6 +994,9 @@ - (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)specialPr
if (flags.setRasterizationScale)
layer.rasterizationScale = rasterizationScale;

if (flags.setActions)
layer.actions = actions;

if (flags.setClipsToBounds)
view.clipsToBounds = clipsToBounds;

Expand Down Expand Up @@ -1272,7 +1290,7 @@ + (_ASPendingState *)pendingViewStateFromView:(UIView *)view

- (void)clearChanges
{
_flags = (ASPendingStateFlags){ 0 };
_flags = kZeroFlags;
}

- (BOOL)hasSetNeedsLayout
Expand All @@ -1287,69 +1305,7 @@ - (BOOL)hasSetNeedsDisplay

- (BOOL)hasChanges
{
ASPendingStateFlags flags = _flags;

return (flags.setAnchorPoint
|| flags.setPosition
|| flags.setZPosition
|| flags.setFrame
|| flags.setBounds
|| flags.setPosition
|| flags.setTransform
|| flags.setSublayerTransform
|| flags.setContents
|| flags.setContentsGravity
|| flags.setContentsRect
|| flags.setContentsCenter
|| flags.setContentsScale
|| flags.setRasterizationScale
|| flags.setClipsToBounds
|| flags.setBackgroundColor
|| flags.setTintColor
|| flags.setHidden
|| flags.setAlpha
|| flags.setCornerRadius
|| flags.setContentMode
|| flags.setUserInteractionEnabled
|| flags.setExclusiveTouch
|| flags.setShadowOpacity
|| flags.setShadowOffset
|| flags.setShadowRadius
|| flags.setShadowColor
|| flags.setBorderWidth
|| flags.setBorderColor
|| flags.setAutoresizingMask
|| flags.setAutoresizesSubviews
|| flags.setNeedsDisplayOnBoundsChange
|| flags.setAllowsGroupOpacity
|| flags.setAllowsEdgeAntialiasing
|| flags.setEdgeAntialiasingMask
|| flags.needsDisplay
|| flags.needsLayout
|| flags.setAsyncTransactionContainer
|| flags.setOpaque
|| flags.setSemanticContentAttribute
|| flags.setLayoutMargins
|| flags.setPreservesSuperviewLayoutMargins
|| flags.setInsetsLayoutMarginsFromSafeArea
|| flags.setIsAccessibilityElement
|| flags.setAccessibilityLabel
|| flags.setAccessibilityAttributedLabel
|| flags.setAccessibilityHint
|| flags.setAccessibilityAttributedHint
|| flags.setAccessibilityValue
|| flags.setAccessibilityAttributedValue
|| flags.setAccessibilityTraits
|| flags.setAccessibilityFrame
|| flags.setAccessibilityLanguage
|| flags.setAccessibilityElementsHidden
|| flags.setAccessibilityViewIsModal
|| flags.setShouldGroupAccessibilityChildren
|| flags.setAccessibilityIdentifier
|| flags.setAccessibilityNavigationStyle
|| flags.setAccessibilityHeaderElements
|| flags.setAccessibilityActivationPoint
|| flags.setAccessibilityPath);
return memcmp(&_flags, &kZeroFlags, sizeof(ASPendingStateFlags));
}

- (void)dealloc
Expand Down
14 changes: 14 additions & 0 deletions Tests/ASDisplayNodeTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#import <QuartzCore/QuartzCore.h>
#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>

#import <AsyncDisplayKit/_ASDisplayLayer.h>
#import <AsyncDisplayKit/_ASDisplayView.h>
Expand Down Expand Up @@ -2697,4 +2698,17 @@ - (void)testCornerRoundingTypeClippingRoundedCornersIsUsingASDisplayNodeCornerLa
}
}

- (void)testLayerActionForKeyIsCalled
{
UIWindow *window = [[UIWindow alloc] init];
ASDisplayNode *node = [[ASDisplayNode alloc] init];

id mockNode = OCMPartialMock(node);
OCMExpect([mockNode layerActionForKey:kCAOnOrderIn]);
[window.layer addSublayer:node.layer];
OCMExpect([mockNode layerActionForKey:@"position"]);
node.layer.position = CGPointMake(10, 10);
OCMVerifyAll(mockNode);
}

@end

0 comments on commit 34f1621

Please sign in to comment.