Properly consider node for responder methods (#1008)

* Properly consider node for responder methods

* Add changelog
This commit is contained in:
Michael Schneider 2018-07-06 08:32:03 -07:00 committed by GitHub
parent d28b17c87f
commit 6c487dd26c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 103 additions and 30 deletions

View File

@ -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)

View File

@ -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;
}
@ -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:))) {

View File

@ -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)
@ -452,25 +466,24 @@ static _ASDisplayViewMethodOverrides GetASDisplayViewMethodOverrides(Class c)
#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 */ \
return [self __##__sel]; \
} \
/* Check if we can call through to ASDisplayNode subclass directly */ \
if (node.methodOverrides & __nodeMethodOverride) { \
return [node __sel]; \
} else { \
/* Call through to internal node __canBecomeFirstResponder that will consider the view in responding */ \
return [node __##__sel]; \
/* 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]; \
} \
} \
}\
/* All __ prefixed methods are called from ASDisplayNode to let the view decide in what UIResponder state they \
@ -480,11 +493,21 @@ are not overridden by a ASDisplayNode subclass */ \
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
{

View File

@ -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)

View File

@ -263,6 +263,14 @@ for (ASDisplayNode *n in @[ nodes ]) {\
@end
@interface ASTestResponderNodeWithOverride : ASDisplayNode
@end
@implementation ASTestResponderNodeWithOverride
- (BOOL)canBecomeFirstResponder {
return YES;
}
@end
@interface ASTestViewController: ASViewController<ASDisplayNode *>
@end
@implementation ASTestViewController
@ -353,6 +361,21 @@ for (ASDisplayNode *n in @[ nodes ]) {\
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];