Skip to content

Commit

Permalink
Properly consider node for responder methods (#1008)
Browse files Browse the repository at this point in the history
* Properly consider node for responder methods

* Add changelog
  • Loading branch information
maicki committed Jul 6, 2018
1 parent d28b17c commit 6c487dd
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 29 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- Optimize layout flattening, particularly reducing retain/release operations. [Adlai Holler](https://github.com/Adlai-Holler)
- Create a method to transfer strong C-arrays into immutable NSArrays, reducing retain/release traffic. [Adlai Holler](https://github.com/Adlai-Holler)
- Remove yoga layout spec, which has been superseded by tighter Yoga integration (`ASDisplayNode+Yoga.h`)
- Properly consider node for responder methods [Michael Schneider](https://github.com/maicki)

## 2.7
- Fix pager node for interface coalescing. [Max Wang](https://github.com/wsdwsd0829) [#877](https://github.com/TextureGroup/Texture/pull/877)
Expand Down
25 changes: 23 additions & 2 deletions Source/ASDisplayNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
ASDisplayNodeCAssertNotNil(c, @"class is required");

ASDisplayNodeMethodOverrides overrides = ASDisplayNodeMethodOverrideNone;

// Handling touches
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(touchesBegan:withEvent:))) {
overrides |= ASDisplayNodeMethodOverrideTouchesBegan;
}
Expand All @@ -166,13 +168,32 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(touchesEnded:withEvent:))) {
overrides |= ASDisplayNodeMethodOverrideTouchesEnded;
}

// Responder chain
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(canBecomeFirstResponder))) {
overrides |= ASDisplayNodeMethodOverrideCanBecomeFirstResponder;
}
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(becomeFirstResponder))) {
overrides |= ASDisplayNodeMethodOverrideBecomeFirstResponder;
}
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(canResignFirstResponder))) {
overrides |= ASDisplayNodeMethodOverrideCanResignFirstResponder;
}
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(resignFirstResponder))) {
overrides |= ASDisplayNodeMethodOverrideResignFirstResponder;
}
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(isFirstResponder))) {
overrides |= ASDisplayNodeMethodOverrideIsFirstResponder;
}

// Layout related methods
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(layoutSpecThatFits:))) {
overrides |= ASDisplayNodeMethodOverrideLayoutSpecThatFits;
}
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(calculateLayoutThatFits:)) ||
ASDisplayNodeSubclassOverridesSelector(c, @selector(calculateLayoutThatFits:
restrictedToSize:
relativeToParentSize:))) {
restrictedToSize:
relativeToParentSize:))) {
overrides |= ASDisplayNodeMethodOverrideCalcLayoutThatFits;
}
if (ASDisplayNodeSubclassOverridesSelector(c, @selector(calculateSizeThatFits:))) {
Expand Down
61 changes: 42 additions & 19 deletions Source/Details/_ASDisplayView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
#import <AsyncDisplayKit/ASViewController.h>

#pragma mark - ASDisplayNode

/**
* Open access to the method overrides struct for ASDisplayView
*/
@implementation ASDisplayNode (ASDisplayNodeMethodOverrides_ASDisplayView)

- (ASDisplayNodeMethodOverrides)methodOverrides
{
return _methodOverrides;
}

@end

#pragma mark - _ASDisplayViewMethodOverrides

typedef NS_OPTIONS(NSUInteger, _ASDisplayViewMethodOverrides)
Expand Down Expand Up @@ -452,25 +466,24 @@ - (void)tintColorDidChange

#pragma mark UIResponder Handling

#define IMPLEMENT_RESPONDER_METHOD(__sel, __methodOverride) \
#define IMPLEMENT_RESPONDER_METHOD(__sel, __nodeMethodOverride, __viewMethodOverride) \
- (BOOL)__sel\
{\
ASDisplayNode *node = _asyncdisplaykit_node; /* Create strong reference to weak ivar. */ \
SEL sel = @selector(__sel); \
/* Prevent an infinite loop in here if [super canBecomeFirstResponder] was called on a
/ _ASDisplayView subclass */ \
if (self->_methodOverrides & __methodOverride) { \
/* Check if we can call through to ASDisplayNode subclass directly */ \
if (ASDisplayNodeSubclassOverridesSelector([node class], sel)) { \
return [node __sel]; \
} else { \
/* Call through to views superclass as we expect super was called from the
_ASDisplayView subclass and a node subclass does not overwrite canBecomeFirstResponder */ \
/* Check if we can call through to ASDisplayNode subclass directly */ \
if (node.methodOverrides & __nodeMethodOverride) { \
return [node __sel]; \
} else { \
/* Prevent an infinite loop in here if [super __sel] was called on a \
/ _ASDisplayView subclass */ \
if (self->_methodOverrides & __viewMethodOverride) { \
/* Call through to views superclass as we expect super was called from the
_ASDisplayView subclass and a node subclass does not overwrite __sel */ \
return [self __##__sel]; \
} else { \
/* Call through to internal node __sel method that will consider the view in responding */ \
return [node __##__sel]; \
} \
} else { \
/* Call through to internal node __canBecomeFirstResponder that will consider the view in responding */ \
return [node __##__sel]; \
} \
}\
/* All __ prefixed methods are called from ASDisplayNode to let the view decide in what UIResponder state they \
Expand All @@ -480,11 +493,21 @@ - (BOOL)__##__sel \
return [super __sel]; \
} \

IMPLEMENT_RESPONDER_METHOD(canBecomeFirstResponder, _ASDisplayViewMethodOverrideCanBecomeFirstResponder);
IMPLEMENT_RESPONDER_METHOD(becomeFirstResponder, _ASDisplayViewMethodOverrideBecomeFirstResponder);
IMPLEMENT_RESPONDER_METHOD(canResignFirstResponder, _ASDisplayViewMethodOverrideCanResignFirstResponder);
IMPLEMENT_RESPONDER_METHOD(resignFirstResponder, _ASDisplayViewMethodOverrideResignFirstResponder);
IMPLEMENT_RESPONDER_METHOD(isFirstResponder, _ASDisplayViewMethodOverrideIsFirstResponder);
IMPLEMENT_RESPONDER_METHOD(canBecomeFirstResponder,
ASDisplayNodeMethodOverrideCanBecomeFirstResponder,
_ASDisplayViewMethodOverrideCanBecomeFirstResponder);
IMPLEMENT_RESPONDER_METHOD(becomeFirstResponder,
ASDisplayNodeMethodOverrideBecomeFirstResponder,
_ASDisplayViewMethodOverrideBecomeFirstResponder);
IMPLEMENT_RESPONDER_METHOD(canResignFirstResponder,
ASDisplayNodeMethodOverrideCanResignFirstResponder,
_ASDisplayViewMethodOverrideCanResignFirstResponder);
IMPLEMENT_RESPONDER_METHOD(resignFirstResponder,
ASDisplayNodeMethodOverrideResignFirstResponder,
_ASDisplayViewMethodOverrideResignFirstResponder);
IMPLEMENT_RESPONDER_METHOD(isFirstResponder,
ASDisplayNodeMethodOverrideIsFirstResponder,
_ASDisplayViewMethodOverrideIsFirstResponder);

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
Expand Down
21 changes: 13 additions & 8 deletions Source/Private/ASDisplayNodeInternal.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,19 @@ _ASPendingState * ASDisplayNodeGetPendingState(ASDisplayNode * node);

typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
{
ASDisplayNodeMethodOverrideNone = 0,
ASDisplayNodeMethodOverrideTouchesBegan = 1 << 0,
ASDisplayNodeMethodOverrideTouchesCancelled = 1 << 1,
ASDisplayNodeMethodOverrideTouchesEnded = 1 << 2,
ASDisplayNodeMethodOverrideTouchesMoved = 1 << 3,
ASDisplayNodeMethodOverrideLayoutSpecThatFits = 1 << 4,
ASDisplayNodeMethodOverrideCalcLayoutThatFits = 1 << 5,
ASDisplayNodeMethodOverrideCalcSizeThatFits = 1 << 6,
ASDisplayNodeMethodOverrideNone = 0,
ASDisplayNodeMethodOverrideTouchesBegan = 1 << 0,
ASDisplayNodeMethodOverrideTouchesCancelled = 1 << 1,
ASDisplayNodeMethodOverrideTouchesEnded = 1 << 2,
ASDisplayNodeMethodOverrideTouchesMoved = 1 << 3,
ASDisplayNodeMethodOverrideLayoutSpecThatFits = 1 << 4,
ASDisplayNodeMethodOverrideCalcLayoutThatFits = 1 << 5,
ASDisplayNodeMethodOverrideCalcSizeThatFits = 1 << 6,
ASDisplayNodeMethodOverrideCanBecomeFirstResponder= 1 << 7,
ASDisplayNodeMethodOverrideBecomeFirstResponder = 1 << 8,
ASDisplayNodeMethodOverrideCanResignFirstResponder= 1 << 9,
ASDisplayNodeMethodOverrideResignFirstResponder = 1 << 10,
ASDisplayNodeMethodOverrideIsFirstResponder = 1 << 11,
};

typedef NS_OPTIONS(uint_least32_t, ASDisplayNodeAtomicFlags)
Expand Down
23 changes: 23 additions & 0 deletions Tests/ASDisplayNodeTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,14 @@ - (BOOL)resignFirstResponder {

@end

@interface ASTestResponderNodeWithOverride : ASDisplayNode
@end
@implementation ASTestResponderNodeWithOverride
- (BOOL)canBecomeFirstResponder {
return YES;
}
@end

@interface ASTestViewController: ASViewController<ASDisplayNode *>
@end
@implementation ASTestViewController
Expand Down Expand Up @@ -353,6 +361,21 @@ - (void)testResponderMethodsBehavior
XCTAssertFalse([textNode.view isFirstResponder]);
}

- (void)testResponderOverrrideCanBecomeFirstResponder
{
UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
ASTestResponderNodeWithOverride *node = [[ASTestResponderNodeWithOverride alloc] init];

// We have to add the text node to a window otherwise the responder methods responses are undefined
// This will also create the backing view of the node
[window addSubnode:node];
[window makeKeyAndVisible];

XCTAssertTrue([node canBecomeFirstResponder]);
XCTAssertTrue([node becomeFirstResponder]);
XCTAssertTrue([window firstResponder] == node.view);
}

- (void)testUnsupportedResponderSetupWillThrow
{
ASTestResponderNode *node = [[ASTestResponderNode alloc] init];
Expand Down

0 comments on commit 6c487dd

Please sign in to comment.