mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-24 07:05:35 +00:00
Merge pull request #1156 from levi/implicitNodeMembership
[ASDisplayNode] Implicit node hierarchy handling with ASLayouts
This commit is contained in:
@@ -475,6 +475,10 @@
|
||||
D785F6621A74327E00291744 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; };
|
||||
DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; };
|
||||
DBC452DB1C5BF64600B16017 /* NSArray+Diffing.h in Headers */ = {isa = PBXBuildFile; fileRef = DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */; };
|
||||
DBC452DC1C5BF64600B16017 /* NSArray+Diffing.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */; };
|
||||
DBC452DE1C5C6A6A00B16017 /* ArrayDiffingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */; };
|
||||
DBC453221C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */; };
|
||||
DE040EF91C2B40AC004692FF /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
DE0702FC1C3671E900D7DE62 /* libAsyncDisplayKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */; };
|
||||
DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; };
|
||||
@@ -802,6 +806,10 @@
|
||||
D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = "<group>"; };
|
||||
D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = "<group>"; };
|
||||
DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Diffing.h"; sourceTree = "<group>"; };
|
||||
DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Diffing.m"; sourceTree = "<group>"; };
|
||||
DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArrayDiffingTests.m; sourceTree = "<group>"; };
|
||||
DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayNodeImplicitHierarchyTests.m; sourceTree = "<group>"; };
|
||||
DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+FrameworkPrivate.h"; sourceTree = "<group>"; };
|
||||
DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDelegateProxy.h; sourceTree = "<group>"; };
|
||||
DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDelegateProxy.m; sourceTree = "<group>"; };
|
||||
@@ -1000,6 +1008,8 @@
|
||||
058D09C5195D04C000B7D73C /* AsyncDisplayKitTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */,
|
||||
DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */,
|
||||
057D02C01AC0A66700C7AC3C /* AsyncDisplayKitTestHost */,
|
||||
056D21501ABCEDA1001107EF /* ASSnapshotTestCase.h */,
|
||||
05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */,
|
||||
@@ -1164,6 +1174,8 @@
|
||||
0442850C1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm */,
|
||||
AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */,
|
||||
AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.m */,
|
||||
DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */,
|
||||
DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */,
|
||||
);
|
||||
path = Private;
|
||||
sourceTree = "<group>";
|
||||
@@ -1354,6 +1366,7 @@
|
||||
ACF6ED201B17843500DA7C62 /* ASDimension.h in Headers */,
|
||||
058D0A78195D05F900B7D73C /* ASDisplayNode+DebugTiming.h in Headers */,
|
||||
DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */,
|
||||
DBC452DB1C5BF64600B16017 /* NSArray+Diffing.h in Headers */,
|
||||
058D0A4C195D05CB00B7D73C /* ASDisplayNode+Subclasses.h in Headers */,
|
||||
258FF4271C0D152600A83844 /* ASRangeHandlerVisible.h in Headers */,
|
||||
058D0A4A195D05CB00B7D73C /* ASDisplayNode.h in Headers */,
|
||||
@@ -1798,6 +1811,7 @@
|
||||
ACF6ED1D1B17843500DA7C62 /* ASCenterLayoutSpec.mm in Sources */,
|
||||
18C2ED801B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */,
|
||||
92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */,
|
||||
DBC452DC1C5BF64600B16017 /* NSArray+Diffing.m in Sources */,
|
||||
AC3C4A521A1139C100143C57 /* ASCollectionView.mm in Sources */,
|
||||
205F0E1E1B373A2C007741D0 /* ASCollectionViewLayoutController.mm in Sources */,
|
||||
058D0A13195D050800B7D73C /* ASControlNode.m in Sources */,
|
||||
@@ -1906,7 +1920,9 @@
|
||||
254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */,
|
||||
058D0A3D195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m in Sources */,
|
||||
058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */,
|
||||
DBC453221C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m in Sources */,
|
||||
058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */,
|
||||
DBC452DE1C5C6A6A00B16017 /* ArrayDiffingTests.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
+ (BOOL)shouldUseNewRenderingRange;
|
||||
+ (void)setShouldUseNewRenderingRange:(BOOL)shouldUseNewRenderingRange;
|
||||
|
||||
+ (BOOL)usesImplicitHierarchyManagement;
|
||||
+ (void)setUsesImplicitHierarchyManagement:(BOOL)enabled;
|
||||
|
||||
/** @name Layout */
|
||||
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#import "_ASCoreAnimationExtras.h"
|
||||
#import "ASDisplayNodeExtras.h"
|
||||
#import "ASEqualityHelpers.h"
|
||||
#import "NSArray+Diffing.h"
|
||||
|
||||
#import "ASInternalHelpers.h"
|
||||
#import "ASLayout.h"
|
||||
@@ -29,6 +30,36 @@
|
||||
|
||||
NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority;
|
||||
|
||||
@interface _ASDisplayNodePosition : NSObject
|
||||
|
||||
@property (nonatomic, assign) NSUInteger index;
|
||||
@property (nonatomic, strong) ASDisplayNode *node;
|
||||
|
||||
+ (instancetype)positionWithNode:(ASDisplayNode *)node atIndex:(NSUInteger)index;
|
||||
|
||||
- (instancetype)initWithNode:(ASDisplayNode *)node atIndex:(NSUInteger)index;
|
||||
|
||||
@end
|
||||
|
||||
@implementation _ASDisplayNodePosition
|
||||
|
||||
+ (instancetype)positionWithNode:(ASDisplayNode *)node atIndex:(NSUInteger)index
|
||||
{
|
||||
return [[self alloc] initWithNode:node atIndex:index];
|
||||
}
|
||||
|
||||
- (instancetype)initWithNode:(ASDisplayNode *)node atIndex:(NSUInteger)index
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_node = node;
|
||||
_index = index;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASDisplayNode () <UIGestureRecognizerDelegate>
|
||||
|
||||
/**
|
||||
@@ -52,6 +83,9 @@ NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority;
|
||||
#endif
|
||||
|
||||
@interface ASDisplayNode () <_ASDisplayLayerDelegate>
|
||||
|
||||
@property (assign, nonatomic) BOOL implicitNodeHierarchyManagement;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASDisplayNode
|
||||
@@ -62,9 +96,21 @@ NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority;
|
||||
@synthesize preferredFrameSize = _preferredFrameSize;
|
||||
@synthesize isFinalLayoutable = _isFinalLayoutable;
|
||||
|
||||
static BOOL usesImplicitHierarchyManagement = FALSE;
|
||||
|
||||
+ (BOOL)usesImplicitHierarchyManagement
|
||||
{
|
||||
return usesImplicitHierarchyManagement;
|
||||
}
|
||||
|
||||
+ (void)setUsesImplicitHierarchyManagement:(BOOL)enabled
|
||||
{
|
||||
usesImplicitHierarchyManagement = enabled;
|
||||
}
|
||||
|
||||
BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector)
|
||||
{
|
||||
return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector);
|
||||
return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector);
|
||||
}
|
||||
|
||||
void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)())
|
||||
@@ -582,14 +628,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
}
|
||||
|
||||
- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize
|
||||
{
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
return [self __measureWithSizeRange:constrainedSize];
|
||||
}
|
||||
|
||||
- (ASLayout *)__measureWithSizeRange:(ASSizeRange)constrainedSize
|
||||
{
|
||||
ASDisplayNodeAssertThreadAffinity(self);
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
|
||||
if (![self __shouldSize])
|
||||
return nil;
|
||||
@@ -598,7 +639,22 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
// - we haven't already
|
||||
// - the constrained size range is different
|
||||
if (!_flags.isMeasured || !ASSizeRangeEqualToSizeRange(constrainedSize, _constrainedSize)) {
|
||||
_layout = [self calculateLayoutThatFits:constrainedSize];
|
||||
ASLayout *newLayout = [self calculateLayoutThatFits:constrainedSize];
|
||||
|
||||
if (_layout) {
|
||||
NSIndexSet *insertions, *deletions;
|
||||
[_layout.sublayouts asdk_diffWithArray:newLayout.sublayouts insertions:&insertions deletions:&deletions compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) {
|
||||
return ASObjectIsEqual(lhs.layoutableObject, rhs.layoutableObject);
|
||||
}];
|
||||
_insertedSubnodes = [self _filterNodesInLayouts:newLayout.sublayouts withIndexes:insertions];
|
||||
_deletedSubnodes = [self _filterNodesInLayouts:_layout.sublayouts withIndexes:deletions];
|
||||
} else {
|
||||
NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [newLayout.sublayouts count])];
|
||||
_insertedSubnodes = [self _filterNodesInLayouts:newLayout.sublayouts withIndexes:indexes];
|
||||
_deletedSubnodes = @[];
|
||||
}
|
||||
|
||||
_layout = newLayout;
|
||||
_constrainedSize = constrainedSize;
|
||||
_flags.isMeasured = YES;
|
||||
[self calculatedLayoutDidChange];
|
||||
@@ -608,23 +664,36 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
ASDisplayNodeAssertTrue(_layout.size.width >= 0.0);
|
||||
ASDisplayNodeAssertTrue(_layout.size.height >= 0.0);
|
||||
|
||||
// we generate placeholders at measureWithSizeRange: time so that a node is guaranteed to have a placeholder ready to go
|
||||
// also if a node has no size, it should not have a placeholder
|
||||
if (self.placeholderEnabled && [self _displaysAsynchronously] && _layout.size.width > 0.0 && _layout.size.height > 0.0) {
|
||||
// we generate placeholders at measureWithSizeRange: time so that a node is guaranteed
|
||||
// to have a placeholder ready to go. Also, if a node has no size it should not have a placeholder
|
||||
if (self.placeholderEnabled && [self _displaysAsynchronously] &&
|
||||
_layout.size.width > 0.0 && _layout.size.height > 0.0) {
|
||||
if (!_placeholderImage) {
|
||||
_placeholderImage = [self placeholderImage];
|
||||
}
|
||||
|
||||
|
||||
if (_placeholderLayer) {
|
||||
[self setupPlaceholderLayerContents];
|
||||
[self _setupPlaceholderLayerContents];
|
||||
}
|
||||
}
|
||||
|
||||
return _layout;
|
||||
}
|
||||
|
||||
- (NSArray<_ASDisplayNodePosition *> *)_filterNodesInLayouts:(NSArray<ASLayout *> *)layouts withIndexes:(NSIndexSet *)indexes
|
||||
{
|
||||
NSMutableArray<_ASDisplayNodePosition *> *result = [NSMutableArray array];
|
||||
[indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
ASDisplayNode *node = (ASDisplayNode *)layouts[idx].layoutableObject;
|
||||
ASDisplayNodeAssertNotNil(node, @"A flattened layout must consist exclusively of node sublayouts");
|
||||
[result addObject:[_ASDisplayNodePosition positionWithNode:node atIndex:idx]];
|
||||
}];
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)calculatedLayoutDidChange
|
||||
{
|
||||
// subclass override
|
||||
}
|
||||
|
||||
- (BOOL)displaysAsynchronously
|
||||
@@ -805,7 +874,10 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDN::MutexLocker l(_propertyLock);
|
||||
if (CGRectEqualToRect(self.bounds, CGRectZero)) {
|
||||
return; // Performing layout on a zero-bounds view often results in frame calculations with negative sizes after applying margins, which will cause measureWithSizeRange: on subnodes to assert.
|
||||
// Performing layout on a zero-bounds view often results in frame calculations
|
||||
// with negative sizes after applying margins, which will cause
|
||||
// measureWithSizeRange: on subnodes to assert.
|
||||
return;
|
||||
}
|
||||
_placeholderLayer.frame = self.bounds;
|
||||
[self layout];
|
||||
@@ -1606,7 +1678,11 @@ static BOOL ShouldUseNewRenderingRange = YES;
|
||||
layout = [ASLayout layoutWithLayoutableObject:self size:layout.size sublayouts:@[layout]];
|
||||
}
|
||||
return [layout flattenedLayoutUsingPredicateBlock:^BOOL(ASLayout *evaluatedLayout) {
|
||||
return [_subnodes containsObject:evaluatedLayout.layoutableObject];
|
||||
if ([[self class] usesImplicitHierarchyManagement]) {
|
||||
return ASObjectIsEqual(layout, evaluatedLayout) == NO && [evaluatedLayout.layoutableObject isKindOfClass:[ASDisplayNode class]];
|
||||
} else {
|
||||
return [_subnodes containsObject:evaluatedLayout.layoutableObject];
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
// If neither -layoutSpecThatFits: nor -calculateSizeThatFits: is overridden by subclassses, preferredFrameSize should be used,
|
||||
@@ -1974,7 +2050,9 @@ static BOOL ShouldUseNewRenderingRange = YES;
|
||||
ASDisplayNode *subnode = nil;
|
||||
CGRect subnodeFrame = CGRectZero;
|
||||
for (ASLayout *subnodeLayout in _layout.sublayouts) {
|
||||
ASDisplayNodeAssert([_subnodes containsObject:subnodeLayout.layoutableObject], @"Cached sublayouts must only contain subnodes' layout. self = %@, subnodes = %@", self, _subnodes);
|
||||
if (![[self class] usesImplicitHierarchyManagement]) {
|
||||
ASDisplayNodeAssert([_subnodes containsObject:subnodeLayout.layoutableObject], @"Sublayouts must only contain subnodes' layout. self = %@, subnodes = %@", self, _subnodes);
|
||||
}
|
||||
CGPoint adjustedOrigin = subnodeLayout.position;
|
||||
if (isfinite(adjustedOrigin.x) == NO) {
|
||||
ASDisplayNodeAssert(0, @"subnodeLayout has an invalid position");
|
||||
@@ -2000,6 +2078,45 @@ static BOOL ShouldUseNewRenderingRange = YES;
|
||||
subnode = ((ASDisplayNode *)subnodeLayout.layoutableObject);
|
||||
[subnode setFrame:subnodeFrame];
|
||||
}
|
||||
|
||||
if ([[self class] usesImplicitHierarchyManagement]) {
|
||||
for (_ASDisplayNodePosition *position in _deletedSubnodes) {
|
||||
[self _implicitlyRemoveSubnode:position.node atIndex:position.index];
|
||||
}
|
||||
|
||||
for (_ASDisplayNodePosition *position in _insertedSubnodes) {
|
||||
[self _implicitlyInsertSubnode:position.node atIndex:position.index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_implicitlyInsertSubnode:(ASDisplayNode *)node atIndex:(NSUInteger)idx
|
||||
{
|
||||
ASDisplayNodeAssertThreadAffinity(self);
|
||||
|
||||
if (!_managedSubnodes) {
|
||||
_managedSubnodes = [NSMutableArray array];
|
||||
}
|
||||
|
||||
ASDisplayNodeAssert(idx <= [_managedSubnodes count], @"index needs to be in range of the current managed subnodes");
|
||||
if (idx == [_managedSubnodes count]) {
|
||||
[_managedSubnodes addObject:node];
|
||||
} else {
|
||||
[_managedSubnodes insertObject:node atIndex:idx];
|
||||
}
|
||||
[self addSubnode:node];
|
||||
}
|
||||
|
||||
- (void)_implicitlyRemoveSubnode:(ASDisplayNode *)node atIndex:(NSUInteger)idx
|
||||
{
|
||||
ASDisplayNodeAssertThreadAffinity(self);
|
||||
|
||||
if (!_managedSubnodes) {
|
||||
_managedSubnodes = [NSMutableArray array];
|
||||
}
|
||||
|
||||
[_managedSubnodes removeObjectAtIndex:idx];
|
||||
[node removeFromSupernode];
|
||||
}
|
||||
|
||||
- (void)displayWillStart
|
||||
@@ -2012,14 +2129,14 @@ static BOOL ShouldUseNewRenderingRange = YES;
|
||||
if (_placeholderImage && _placeholderLayer && self.layer.contents == nil) {
|
||||
[CATransaction begin];
|
||||
[CATransaction setDisableActions:YES];
|
||||
[self setupPlaceholderLayerContents];
|
||||
[self _setupPlaceholderLayerContents];
|
||||
_placeholderLayer.opacity = 1.0;
|
||||
[CATransaction commit];
|
||||
[self.layer addSublayer:_placeholderLayer];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupPlaceholderLayerContents
|
||||
- (void)_setupPlaceholderLayerContents
|
||||
{
|
||||
BOOL stretchable = !UIEdgeInsetsEqualToEdgeInsets(_placeholderImage.capInsets, UIEdgeInsetsZero);
|
||||
if (stretchable) {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#import "ASAssert.h"
|
||||
#import "ASLayoutSpecUtilities.h"
|
||||
#import "ASInternalHelpers.h"
|
||||
#import <stack>
|
||||
#import <queue>
|
||||
|
||||
CGPoint const CGPointNull = {NAN, NAN};
|
||||
|
||||
@@ -71,14 +71,14 @@ extern BOOL CGPointIsNull(CGPoint point)
|
||||
BOOL visited;
|
||||
};
|
||||
|
||||
// Stack of Contexts, used to keep track of sublayouts while traversing this layout in a DFS fashion.
|
||||
std::stack<Context> stack;
|
||||
stack.push({self, CGPointMake(0, 0), NO});
|
||||
// Stack of Contexts, used to keep track of sublayouts while traversing this layout in a BFS fashion.
|
||||
std::queue<Context> queue;
|
||||
queue.push({self, CGPointMake(0, 0), NO});
|
||||
|
||||
while (!stack.empty()) {
|
||||
Context &context = stack.top();
|
||||
while (!queue.empty()) {
|
||||
Context &context = queue.front();
|
||||
if (context.visited) {
|
||||
stack.pop();
|
||||
queue.pop();
|
||||
} else {
|
||||
context.visited = YES;
|
||||
|
||||
@@ -90,11 +90,11 @@ extern BOOL CGPointIsNull(CGPoint point)
|
||||
}
|
||||
|
||||
for (ASLayout *sublayout in context.layout.sublayouts) {
|
||||
stack.push({sublayout, context.absolutePosition + sublayout.position, NO});
|
||||
queue.push({sublayout, context.absolutePosition + sublayout.position, NO});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return [ASLayout layoutWithLayoutableObject:_layoutableObject size:_size sublayouts:flattenedSublayouts];
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
|
||||
};
|
||||
|
||||
@class _ASPendingState;
|
||||
@class _ASDisplayNodePosition;
|
||||
|
||||
// Allow 2^n increments of begin disabling hierarchy notifications
|
||||
#define VISIBILITY_NOTIFICATIONS_DISABLED_BITS 4
|
||||
@@ -60,6 +61,12 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
|
||||
UIEdgeInsets _hitTestSlop;
|
||||
NSMutableArray *_subnodes;
|
||||
|
||||
// Subnodes implicitly managed by layout changes
|
||||
NSMutableArray<ASDisplayNode *> *_managedSubnodes;
|
||||
|
||||
NSArray<_ASDisplayNodePosition *> *_insertedSubnodes;
|
||||
NSArray<_ASDisplayNodePosition *> *_deletedSubnodes;
|
||||
|
||||
ASDisplayNodeViewBlock _viewBlock;
|
||||
ASDisplayNodeLayerBlock _layerBlock;
|
||||
ASDisplayNodeDidLoadBlock _nodeLoadedBlock;
|
||||
@@ -131,10 +138,11 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides)
|
||||
- (BOOL)__shouldLoadViewOrLayer;
|
||||
- (BOOL)__shouldSize;
|
||||
|
||||
// Core implementation of -measureWithSizeRange:. Must be called with _propertyLock held.
|
||||
- (ASLayout *)__measureWithSizeRange:(ASSizeRange)constrainedSize;
|
||||
|
||||
/**
|
||||
Invoked by a call to setNeedsLayout to the underlying view
|
||||
*/
|
||||
- (void)__setNeedsLayout;
|
||||
|
||||
- (void)__layout;
|
||||
- (void)__setSupernode:(ASDisplayNode *)supernode;
|
||||
|
||||
|
||||
29
AsyncDisplayKit/Private/NSArray+Diffing.h
Normal file
29
AsyncDisplayKit/Private/NSArray+Diffing.h
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// NSArray+Diffing.h
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Levi McCallum on 1/29/16.
|
||||
// Copyright © 2016 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface NSArray (Diffing)
|
||||
|
||||
/**
|
||||
* @abstract Compares two arrays, providing the insertion and deletion indexes needed to transform into the target array.
|
||||
* @discussion This compares the equality of each object with `isEqual:`.
|
||||
* This diffing algorithm uses a bottom-up memoized longest common subsequence solution to identify differences.
|
||||
* It runs in O(mn) complexity.
|
||||
*/
|
||||
- (void)asdk_diffWithArray:(NSArray *)array insertions:(NSIndexSet **)insertions deletions:(NSIndexSet **)deletions;
|
||||
|
||||
/**
|
||||
* @abstract Compares two arrays, providing the insertion and deletion indexes needed to transform into the target array.
|
||||
* @discussion The `compareBlock` is used to identify the equality of the objects within the arrays.
|
||||
* This diffing algorithm uses a bottom-up memoized longest common subsequence solution to identify differences.
|
||||
* It runs in O(mn) complexity.
|
||||
*/
|
||||
- (void)asdk_diffWithArray:(NSArray *)array insertions:(NSIndexSet **)insertions deletions:(NSIndexSet **)deletions compareBlock:(BOOL (^)(id lhs, id rhs))comparison;
|
||||
|
||||
@end
|
||||
78
AsyncDisplayKit/Private/NSArray+Diffing.m
Normal file
78
AsyncDisplayKit/Private/NSArray+Diffing.m
Normal file
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// NSArray+Diffing.m
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Levi McCallum on 1/29/16.
|
||||
// Copyright © 2016 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSArray+Diffing.h"
|
||||
|
||||
@implementation NSArray (Diffing)
|
||||
|
||||
- (void)asdk_diffWithArray:(NSArray *)array insertions:(NSIndexSet **)insertions deletions:(NSIndexSet **)deletions
|
||||
{
|
||||
[self asdk_diffWithArray:array insertions:insertions deletions:deletions compareBlock:^BOOL(id lhs, id rhs) {
|
||||
return [lhs isEqual:rhs];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)asdk_diffWithArray:(NSArray *)array insertions:(NSIndexSet **)insertions deletions:(NSIndexSet **)deletions compareBlock:(BOOL (^)(id lhs, id rhs))comparison
|
||||
{
|
||||
NSIndexSet *commonIndexes = [self _asdk_commonIndexesWithArray:array compareBlock:comparison];
|
||||
|
||||
if (insertions) {
|
||||
NSArray *commonObjects = [self objectsAtIndexes:commonIndexes];
|
||||
NSMutableIndexSet *insertionIndexes = [NSMutableIndexSet indexSet];
|
||||
for (NSInteger i = 0, j = 0; i < commonObjects.count || j < array.count;) {
|
||||
if (i < commonObjects.count && j < array.count && comparison(commonObjects[i], array[j])) {
|
||||
i++; j++;
|
||||
} else {
|
||||
[insertionIndexes addIndex:j];
|
||||
j++;
|
||||
}
|
||||
}
|
||||
*insertions = insertionIndexes;
|
||||
}
|
||||
|
||||
if (deletions) {
|
||||
NSMutableIndexSet *deletionIndexes = [NSMutableIndexSet indexSet];
|
||||
for (NSInteger i = 0; i < self.count; i++) {
|
||||
if (![commonIndexes containsIndex:i]) {
|
||||
[deletionIndexes addIndex:i];
|
||||
}
|
||||
}
|
||||
*deletions = deletionIndexes;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSIndexSet *)_asdk_commonIndexesWithArray:(NSArray *)array compareBlock:(BOOL (^)(id lhs, id rhs))comparison
|
||||
{
|
||||
NSInteger lengths[self.count+1][array.count+1];
|
||||
for (NSInteger i = self.count; i >= 0; i--) {
|
||||
for (NSInteger j = array.count; j >= 0; j--) {
|
||||
if (i == self.count || j == array.count) {
|
||||
lengths[i][j] = 0;
|
||||
} else if ([self[i] isEqual:array[j]]) {
|
||||
lengths[i][j] = 1 + lengths[i+1][j+1];
|
||||
} else {
|
||||
lengths[i][j] = MAX(lengths[i+1][j], lengths[i][j+1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSMutableIndexSet *common = [NSMutableIndexSet indexSet];
|
||||
for (NSInteger i = 0, j = 0; i < self.count && j < array.count;) {
|
||||
if (comparison(self[i], array[j])) {
|
||||
[common addIndex:i];
|
||||
i++; j++;
|
||||
} else if (lengths[i+1][j] >= lengths[i][j+1]) {
|
||||
i++;
|
||||
} else {
|
||||
j++;
|
||||
}
|
||||
}
|
||||
return common;
|
||||
}
|
||||
|
||||
@end
|
||||
129
AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m
Normal file
129
AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m
Normal file
@@ -0,0 +1,129 @@
|
||||
//
|
||||
// ASDisplayNodeImplicitHierarchyTests.m
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Levi McCallum on 2/1/16.
|
||||
// Copyright © 2016 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "ASDisplayNode.h"
|
||||
#import "ASDisplayNode+Beta.h"
|
||||
#import "ASDisplayNode+Subclasses.h"
|
||||
|
||||
#import "ASStaticLayoutSpec.h"
|
||||
#import "ASStackLayoutSpec.h"
|
||||
|
||||
@interface ASSpecTestDisplayNode : ASDisplayNode
|
||||
|
||||
@property (copy, nonatomic) ASLayoutSpec * (^layoutSpecBlock)(ASSizeRange constrainedSize, NSNumber *layoutState);
|
||||
|
||||
/**
|
||||
Simple state identifier to allow control of current spec inside of the layoutSpecBlock
|
||||
*/
|
||||
@property (strong, nonatomic) NSNumber *layoutState;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASSpecTestDisplayNode
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_layoutState = @1;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
|
||||
{
|
||||
return self.layoutSpecBlock(constrainedSize, _layoutState);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ASDisplayNodeImplicitHierarchyTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASDisplayNodeImplicitHierarchyTests
|
||||
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
[ASDisplayNode setUsesImplicitHierarchyManagement:YES];
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
[ASDisplayNode setUsesImplicitHierarchyManagement:NO];
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
- (void)testFeatureFlag
|
||||
{
|
||||
XCTAssert([ASDisplayNode usesImplicitHierarchyManagement]);
|
||||
}
|
||||
|
||||
- (void)testInitialNodeInsertionWithOrdering
|
||||
{
|
||||
ASDisplayNode *node1 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node2 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node3 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node4 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node5 = [[ASDisplayNode alloc] init];
|
||||
|
||||
ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
|
||||
node.layoutSpecBlock = ^(ASSizeRange constrainedSize, NSNumber *layoutState) {
|
||||
ASStaticLayoutSpec *staticLayout = [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[node4]];
|
||||
|
||||
ASStackLayoutSpec *stack1 = [[ASStackLayoutSpec alloc] init];
|
||||
[stack1 setChildren:@[node1, node2]];
|
||||
|
||||
ASStackLayoutSpec *stack2 = [[ASStackLayoutSpec alloc] init];
|
||||
[stack2 setChildren:@[node3, staticLayout]];
|
||||
|
||||
return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[stack1, stack2, node5]];
|
||||
};
|
||||
[node measureWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeZero)];
|
||||
[node layout]; // Layout immediately
|
||||
XCTAssertEqual(node.subnodes[0], node5);
|
||||
XCTAssertEqual(node.subnodes[1], node1);
|
||||
XCTAssertEqual(node.subnodes[2], node2);
|
||||
XCTAssertEqual(node.subnodes[3], node3);
|
||||
XCTAssertEqual(node.subnodes[4], node4);
|
||||
}
|
||||
|
||||
- (void)testCalculatedLayoutHierarchyTransitions
|
||||
{
|
||||
ASDisplayNode *node1 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node2 = [[ASDisplayNode alloc] init];
|
||||
ASDisplayNode *node3 = [[ASDisplayNode alloc] init];
|
||||
|
||||
ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init];
|
||||
node.layoutSpecBlock = ^(ASSizeRange constrainedSize, NSNumber *layoutState){
|
||||
if ([layoutState isEqualToNumber:@1]) {
|
||||
return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[node1, node2]];
|
||||
} else {
|
||||
ASStackLayoutSpec *stackLayout = [[ASStackLayoutSpec alloc] init];
|
||||
[stackLayout setChildren:@[node3, node2]];
|
||||
return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[node1, stackLayout]];
|
||||
}
|
||||
};
|
||||
|
||||
[node measureWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeZero)];
|
||||
[node layout]; // Layout immediately
|
||||
XCTAssertEqual(node.subnodes[0], node1);
|
||||
XCTAssertEqual(node.subnodes[1], node2);
|
||||
|
||||
node.layoutState = @2;
|
||||
[node invalidateCalculatedLayout]; // TODO(levi): Look into a way where measureWithSizeRange resizes when a new hierarchy is introduced but the size has not changed
|
||||
[node measureWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeZero)];
|
||||
[node layout]; // Layout immediately
|
||||
|
||||
XCTAssertEqual(node.subnodes[0], node1);
|
||||
XCTAssertEqual(node.subnodes[1], node3);
|
||||
XCTAssertEqual(node.subnodes[2], node2);
|
||||
}
|
||||
|
||||
@end
|
||||
71
AsyncDisplayKitTests/ArrayDiffingTests.m
Normal file
71
AsyncDisplayKitTests/ArrayDiffingTests.m
Normal file
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// ArrayDiffingTests.m
|
||||
// AsyncDisplayKit
|
||||
//
|
||||
// Created by Levi McCallum on 1/29/16.
|
||||
// Copyright © 2016 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "NSArray+Diffing.h"
|
||||
|
||||
@interface ArrayDiffingTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation ArrayDiffingTests
|
||||
|
||||
- (void)testDiffing {
|
||||
NSArray<NSArray *> *tests = @[
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave"],
|
||||
@[@"bob", @"alice", @"dave", @"gary"],
|
||||
@[@3],
|
||||
@[],
|
||||
],
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave"],
|
||||
@[@"bob", @"gary", @"alice", @"dave"],
|
||||
@[@1],
|
||||
@[],
|
||||
],
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave"],
|
||||
@[@"bob", @"alice"],
|
||||
@[],
|
||||
@[@2],
|
||||
],
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave"],
|
||||
@[],
|
||||
@[],
|
||||
@[@0, @1, @2],
|
||||
],
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave"],
|
||||
@[@"gary", @"alice", @"dave", @"jack"],
|
||||
@[@0, @3],
|
||||
@[@0],
|
||||
],
|
||||
@[
|
||||
@[@"bob", @"alice", @"dave", @"judy", @"lynda", @"tony"],
|
||||
@[@"gary", @"bob", @"suzy", @"tony"],
|
||||
@[@0, @2],
|
||||
@[@1, @2, @3, @4],
|
||||
],
|
||||
];
|
||||
|
||||
for (NSArray *test in tests) {
|
||||
NSIndexSet *insertions, *deletions;
|
||||
[test[0] asdk_diffWithArray:test[1] insertions:&insertions deletions:&deletions];
|
||||
for (NSNumber *index in (NSArray *)test[2]) {
|
||||
XCTAssert([insertions containsIndex:[index integerValue]]);
|
||||
}
|
||||
for (NSNumber *index in (NSArray *)test[3]) {
|
||||
XCTAssert([deletions containsIndex:[index integerValue]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user