Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Properly consider node for responder methods #1008

Merged
merged 2 commits into from
Jul 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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