Skip to content
This repository was archived by the owner on Feb 2, 2023. It is now read-only.

Commit b787020

Browse files
committed
Merge pull request #961 from facebook/AutomagicalNodeHierarchyHookups
[ASDisplayNode] Automatically find and hook up node hierarchies, even when -addSubview: is used directly.
2 parents 8ce0f2a + 92126f0 commit b787020

3 files changed

Lines changed: 123 additions & 40 deletions

File tree

AsyncDisplayKit/ASDisplayNode.mm

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -996,13 +996,14 @@ - (void)_insertSubnode:(ASDisplayNode *)subnode atSubnodeIndex:(NSInteger)subnod
996996
if (isMovingEquivalentParents) {
997997
[subnode __incrementVisibilityNotificationsDisabled];
998998
}
999+
9991000
[subnode removeFromSupernode];
1000-
1001+
[oldSubnode removeFromSupernode];
1002+
10011003
if (!_subnodes)
10021004
_subnodes = [[NSMutableArray alloc] init];
1003-
1004-
[oldSubnode removeFromSupernode];
10051005
[_subnodes insertObject:subnode atIndex:subnodeIndex];
1006+
[subnode __setSupernode:self];
10061007

10071008
// Don't bother inserting the view/layer if in a rasterized subtree, because there are no layers in the hierarchy and none of this could possibly work.
10081009
if (!_flags.shouldRasterizeDescendants && [self __shouldLoadViewOrLayer]) {
@@ -1030,8 +1031,6 @@ - (void)_insertSubnode:(ASDisplayNode *)subnode atSubnodeIndex:(NSInteger)subnod
10301031
if (isMovingEquivalentParents) {
10311032
[subnode __decrementVisibilityNotificationsDisabled];
10321033
}
1033-
1034-
[subnode __setSupernode:self];
10351034
}
10361035

10371036
- (void)replaceSubnode:(ASDisplayNode *)oldSubnode withSubnode:(ASDisplayNode *)replacementSubnode
@@ -1225,17 +1224,24 @@ - (void)removeFromSupernode
12251224
if (!_supernode)
12261225
return;
12271226

1227+
// Check to ensure that our view or layer is actually inside of our supernode; otherwise, don't remove it.
1228+
// Though _ASDisplayView decouples the supernode if it is inserted inside another view hierarchy, this is
1229+
// more difficult to guarantee with _ASDisplayLayer because CoreAnimation doesn't have a -didMoveToSuperlayer.
1230+
BOOL shouldRemoveFromSuperviewOrSuperlayer = NO;
1231+
1232+
if (self.nodeLoaded && _supernode.nodeLoaded) {
1233+
if (_flags.layerBacked || _supernode.layerBacked) {
1234+
shouldRemoveFromSuperviewOrSuperlayer = (_layer.superlayer == _supernode.layer);
1235+
} else {
1236+
shouldRemoveFromSuperviewOrSuperlayer = (_view.superview == _supernode.view);
1237+
}
1238+
}
1239+
12281240
// Do this before removing the view from the hierarchy, as the node will clear its supernode pointer when its view is removed from the hierarchy.
12291241
[_supernode _removeSubnode:self];
12301242

1231-
if (ASDisplayNodeThreadIsMain()) {
1232-
if (_flags.layerBacked) {
1233-
[_layer removeFromSuperlayer];
1234-
} else {
1235-
[_view removeFromSuperview];
1236-
}
1237-
} else {
1238-
dispatch_async(dispatch_get_main_queue(), ^{
1243+
if (shouldRemoveFromSuperviewOrSuperlayer) {
1244+
ASPerformBlockOnMainThread(^{
12391245
if (_flags.layerBacked) {
12401246
[_layer removeFromSuperlayer];
12411247
} else {
@@ -1301,7 +1307,7 @@ - (void)__enterHierarchy
13011307
_flags.isEnteringHierarchy = NO;
13021308

13031309
CALayer *layer = self.layer;
1304-
if (!self.layer.contents) {
1310+
if (!layer.contents) {
13051311
[layer setNeedsDisplay];
13061312
}
13071313
}
@@ -2317,22 +2323,33 @@ @implementation CALayer (ASDisplayNodeInternal)
23172323

23182324
@implementation UIView (AsyncDisplayKit)
23192325

2320-
- (void)addSubnode:(ASDisplayNode *)node
2326+
- (void)addSubnode:(ASDisplayNode *)subnode
23212327
{
2322-
if (node.layerBacked) {
2323-
[self.layer addSublayer:node.layer];
2328+
if (subnode.layerBacked) {
2329+
// Call -addSubnode: so that we use the asyncdisplaykit_node path if possible.
2330+
[self.layer addSubnode:subnode];
23242331
} else {
2325-
[self addSubview:node.view];
2332+
ASDisplayNode *selfNode = self.asyncdisplaykit_node;
2333+
if (selfNode) {
2334+
[selfNode addSubnode:subnode];
2335+
} else {
2336+
[self addSubview:subnode.view];
2337+
}
23262338
}
23272339
}
23282340

23292341
@end
23302342

23312343
@implementation CALayer (AsyncDisplayKit)
23322344

2333-
- (void)addSubnode:(ASDisplayNode *)node
2345+
- (void)addSubnode:(ASDisplayNode *)subnode
23342346
{
2335-
[self addSublayer:node.layer];
2347+
ASDisplayNode *selfNode = self.asyncdisplaykit_node;
2348+
if (selfNode) {
2349+
[selfNode addSubnode:subnode];
2350+
} else {
2351+
[self addSublayer:subnode.layer];
2352+
}
23362353
}
23372354

23382355
@end

AsyncDisplayKit/Details/_ASDisplayView.mm

Lines changed: 79 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,22 @@ - (id)initWithFrame:(CGRect)frame
6363
return self;
6464
}
6565

66+
- (void)willMoveToWindow:(UIWindow *)newWindow
67+
{
68+
BOOL visible = (newWindow != nil);
69+
if (visible && !_node.inHierarchy) {
70+
[_node __enterHierarchy];
71+
}
72+
}
73+
74+
- (void)didMoveToWindow
75+
{
76+
BOOL visible = (self.window != nil);
77+
if (!visible && _node.inHierarchy) {
78+
[_node __exitHierarchy];
79+
}
80+
}
81+
6682
- (void)willMoveToSuperview:(UIView *)newSuperview
6783
{
6884
// Keep the node alive while the view is in a view hierarchy. This helps ensure that async-drawing views can always
@@ -76,28 +92,74 @@ - (void)willMoveToSuperview:(UIView *)newSuperview
7692
else if (currentSuperview && !newSuperview) {
7793
self.keepalive_node = nil;
7894
}
79-
}
80-
81-
- (void)willMoveToWindow:(UIWindow *)newWindow
82-
{
83-
BOOL visible = newWindow != nil;
84-
if (visible && !_node.inHierarchy) {
85-
[_node __enterHierarchy];
86-
} else if (!visible && _node.inHierarchy) {
87-
[_node __exitHierarchy];
95+
96+
if (newSuperview) {
97+
ASDisplayNode *supernode = _node.supernode;
98+
BOOL supernodeLoaded = supernode.nodeLoaded;
99+
ASDisplayNodeAssert(!supernode.isLayerBacked, @"Shouldn't be possible for _ASDisplayView's supernode to be layer-backed.");
100+
101+
BOOL needsSupernodeUpdate = NO;
102+
103+
if (supernode) {
104+
if (supernodeLoaded) {
105+
if (supernode.layerBacked) {
106+
// See comment in -didMoveToSuperview. This case should be avoided, but is possible with app-level coding errors.
107+
needsSupernodeUpdate = (supernode.layer != newSuperview.layer);
108+
} else {
109+
// If we have a supernode, compensate for users directly messing with views by hitching up to any new supernode.
110+
needsSupernodeUpdate = (supernode.view != newSuperview);
111+
}
112+
} else {
113+
needsSupernodeUpdate = YES;
114+
}
115+
} else {
116+
// If we have no supernode and we are now in a view hierarchy, check to see if we can hook up to a supernode.
117+
needsSupernodeUpdate = (newSuperview != nil);
118+
}
119+
120+
if (needsSupernodeUpdate) {
121+
// -removeFromSupernode is called by -addSubnode:, if it is needed.
122+
[newSuperview.asyncdisplaykit_node addSubnode:_node];
123+
}
88124
}
125+
89126
}
90127

91128
- (void)didMoveToSuperview
92129
{
93-
// FIXME maybe move this logic into ASDisplayNode addSubnode/removeFromSupernode
94-
UIView *superview = self.superview;
95-
96-
// If superview's node is different from supernode's view, fix it by setting supernode to the new superview's node. Got that?
97-
if (!superview)
98-
[_node __setSupernode:nil];
99-
else if (superview != _node.supernode.view)
100-
[_node __setSupernode:superview.asyncdisplaykit_node];
130+
ASDisplayNode *supernode = _node.supernode;
131+
ASDisplayNodeAssert(!supernode.isLayerBacked, @"Shouldn't be possible for superview's node to be layer-backed.");
132+
133+
if (supernode) {
134+
ASDisplayNodeAssertTrue(_node.nodeLoaded);
135+
UIView *superview = self.superview;
136+
BOOL supernodeLoaded = supernode.nodeLoaded;
137+
BOOL needsSupernodeRemoval = NO;
138+
139+
if (superview) {
140+
// If our new superview is not the same as the supernode's view, or the supernode has no view, disconnect.
141+
if (supernodeLoaded) {
142+
if (supernode.layerBacked) {
143+
// As asserted at the top, this shouldn't be possible, but in production with assertions disabled it can happen.
144+
// We try to make such code behave as well as feasible because it's not that hard of an error to make if some deep
145+
// child node of a layer-backed node happens to be view-backed, but it is not supported and should be avoided.
146+
needsSupernodeRemoval = (supernode.layer != superview.layer);
147+
} else {
148+
needsSupernodeRemoval = (supernode.view != superview);
149+
}
150+
} else {
151+
needsSupernodeRemoval = YES;
152+
}
153+
} else {
154+
// If supernode is loaded but our superview is nil, the user manually removed us, so disconnect supernode.
155+
needsSupernodeRemoval = supernodeLoaded;
156+
}
157+
158+
if (needsSupernodeRemoval) {
159+
// The node will only disconnect from its supernode, not removeFromSuperview, in this condition.
160+
[_node removeFromSupernode];
161+
}
162+
}
101163
}
102164

103165
- (void)setNeedsDisplay

AsyncDisplayKitTests/ASDisplayNodeTests.m

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,16 +1342,20 @@ - (void)testInsertSubviewAtIndexWithMeddlingViewsAndLayersViewBacked
13421342
[parent insertSubnode:c belowSubnode:b];
13431343
XCTAssertEqualObjects(orderStringFromSublayers(parent.layer), @"a,e,d,c,b", @"Didn't match");
13441344

1345-
XCTAssertEqual(3u, parent.subnodes.count, @"Should have the right subnode count");
1345+
XCTAssertEqual(4u, parent.subnodes.count, @"Should have the right subnode count");
13461346
XCTAssertEqual(4u, parent.view.subviews.count, @"Should have the right subview count");
13471347
XCTAssertEqual(5u, parent.layer.sublayers.count, @"Should have the right sublayer count");
1348+
1349+
[e removeFromSuperlayer];
1350+
XCTAssertEqual(4u, parent.layer.sublayers.count, @"Should have the right sublayer count");
13481351

13491352
//TODO: assert that things deallocate immediately and don't have latent autoreleases in here
13501353
[parent release];
13511354
[a release];
13521355
[b release];
13531356
[c release];
13541357
[d release];
1358+
[e release];
13551359
}
13561360

13571361
- (void)testAppleBugInsertSubview
@@ -1415,11 +1419,11 @@ - (void)testInsertSubviewAtIndexWithMeddlingView
14151419
[parent.view insertSubview:d aboveSubview:a.view];
14161420
XCTAssertEqualObjects(orderStringFromSublayers(parent.layer), @"a,d,b", @"Didn't match");
14171421

1418-
// (a,e,d,b) => (a,d,>c<,b)
1422+
// (a,d,b) => (a,d,>c<,b)
14191423
[parent insertSubnode:c belowSubnode:b];
14201424
XCTAssertEqualObjects(orderStringFromSublayers(parent.layer), @"a,d,c,b", @"Didn't match");
14211425

1422-
XCTAssertEqual(3u, parent.subnodes.count, @"Should have the right subnode count");
1426+
XCTAssertEqual(4u, parent.subnodes.count, @"Should have the right subnode count");
14231427
XCTAssertEqual(4u, parent.view.subviews.count, @"Should have the right subview count");
14241428
XCTAssertEqual(4u, parent.layer.sublayers.count, @"Should have the right sublayer count");
14251429

0 commit comments

Comments
 (0)