From c43e93d1b434357564a733b8ee30d3b5dd27ebe4 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Fri, 17 Jul 2015 03:53:15 -0700 Subject: [PATCH] Reverted ca9d1b3bf5a6f46ec29babe8685634690ff9a2bc to unbreak groups --- .../RCTUIManagerScenarioTests.m | 477 ++++----------- .../testViewExample_1@2x.png | Bin 99475 -> 99477 bytes .../UIExplorerUnitTests/RCTUIManagerTests.m | 24 +- Libraries/Components/ScrollView/ScrollView.js | 1 - Libraries/Components/View/View.js | 6 - .../ReactNative/ReactNativeViewAttributes.js | 16 - Libraries/Text/RCTShadowRawText.m | 5 - React/Modules/RCTUIManager.m | 544 ++++-------------- React/Views/RCTShadowView.h | 6 - React/Views/RCTShadowView.m | 94 +-- React/Views/RCTViewNodeProtocol.h | 3 +- 11 files changed, 234 insertions(+), 942 deletions(-) diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/RCTUIManagerScenarioTests.m b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTUIManagerScenarioTests.m index d6981fba2..c7d30539f 100644 --- a/Examples/UIExplorer/UIExplorerIntegrationTests/RCTUIManagerScenarioTests.m +++ b/Examples/UIExplorer/UIExplorerIntegrationTests/RCTUIManagerScenarioTests.m @@ -15,46 +15,21 @@ #import #import - -#import "RCTShadowView.h" -#import "RCTUIManager.h" -#import "RCTRootView.h" #import "RCTSparseArray.h" +#import "RCTUIManager.h" #import "UIView+React.h" @interface RCTUIManager (Testing) - (void)_manageChildren:(NSNumber *)containerReactTag + moveFromIndices:(NSArray *)moveFromIndices + moveToIndices:(NSArray *)moveToIndices addChildReactTags:(NSArray *)addChildReactTags addAtIndices:(NSArray *)addAtIndices removeAtIndices:(NSArray *)removeAtIndices registry:(RCTSparseArray *)registry; -- (void)modifyManageChildren:(NSNumber *)containerReactTag - addChildReactTags:(NSMutableArray *)mutableAddChildReactTags - addAtIndices:(NSMutableArray *)mutableAddAtIndices - removeAtIndices:(NSMutableArray *)mutableRemoveAtIndices; - -- (void)createView:(NSNumber *)reactTag - viewName:(NSString *)viewName - rootTag:(NSNumber *)rootTag - props:(NSDictionary *)props; - -- (void)updateView:(NSNumber *)reactTag - viewName:(NSString *)viewName - props:(NSDictionary *)props; - -- (void)manageChildren:(NSNumber *)containerReactTag - moveFromIndices:(NSArray *)moveFromIndices - moveToIndices:(NSArray *)moveToIndices - addChildReactTags:(NSArray *)addChildReactTags - addAtIndices:(NSArray *)addAtIndices - removeAtIndices:(NSArray *)removeAtIndices; - -- (void)flushUIBlocks; - @property (nonatomic, readonly) RCTSparseArray *viewRegistry; -@property (nonatomic, readonly) RCTSparseArray *shadowViewRegistry; // RCT thread only @end @@ -77,360 +52,140 @@ UIView *registeredView = [[UIView alloc] init]; [registeredView setReactTag:@(i)]; _uiManager.viewRegistry[i] = registeredView; - - RCTShadowView *registeredShadowView = [[RCTShadowView alloc] init]; - registeredShadowView.viewName = @"RCTView"; - [registeredShadowView setReactTag:@(i)]; - _uiManager.shadowViewRegistry[i] = registeredShadowView; } } -/* +-----------------------------------------------------------+ +----------------------+ - * | Shadow Hierarchy | | Legend | - * +-----------------------------------------------------------+ +----------------------+ - * | | | | - * | +---+ ****** | | ******************** | - * | | 1 | * 11 * | | * Layout-only View * | - * | +---+ ****** | | ******************** | - * | | | | | | - * | +-------+---+---+----------+ +---+---+ | | +----+ | - * | | | | | | | | | |View| Subview | - * | v v v v v v | | +----+ -----------> | - * | ***** +---+ ***** +---+ +----+ +----+ | | | - * | * 2 * | 3 | * 4 * | 5 | | 12 | | 13 | | +----------------------+ - * | ***** +---+ ***** +---+ +----+ +----+ | - * | | | | | - * | +---+--+ | +---+---+ | - * | | | | | | | - * | v v v v v | - * | +---+ +---+ +---+ +---+ ****** | - * | | 6 | | 7 | | 8 | | 9 | * 10 * | - * | +---+ +---+ +---+ +---+ ****** | - * | | - * +-----------------------------------------------------------+ - * - * +-----------------------------------------------------------+ - * | View Hierarchy | - * +-----------------------------------------------------------+ - * | | - * | +---+ ****** | - * | | 1 | * 11 * | - * | +---+ ****** | - * | | | | - * | +------+------+------+------+ +---+---+ | - * | | | | | | | | | - * | v v v v v v v | - * | +---+ +---+ +---+ +---+ +---+ +----+ +----+ | - * | | 6 | | 7 | | 3 | | 8 | | 5 | | 12 | | 13 | | - * | +---+ +---+ +---+ +---+ +---+ +----+ +----+ | - * | | | - * | v | - * | +---+ | - * | | 9 | | - * | +---+ | - * | | - * +-----------------------------------------------------------+ - */ - -- (void)updateShadowViewWithReactTag:(NSNumber *)reactTag layoutOnly:(BOOL)isLayoutOnly childTags:(NSArray *)childTags +- (void)testManagingChildrenToAddViews { - RCTShadowView *shadowView = _uiManager.shadowViewRegistry[reactTag]; - shadowView.allProps = isLayoutOnly ? @{} : @{@"collapsible": @NO}; - [childTags enumerateObjectsUsingBlock:^(NSNumber *childTag, NSUInteger idx, __unused BOOL *stop) { - [shadowView insertReactSubview:_uiManager.shadowViewRegistry[childTag] atIndex:idx]; - }]; -} + UIView *containerView = _uiManager.viewRegistry[20]; + NSMutableArray *addedViews = [NSMutableArray array]; -- (void)setUpShadowViewHierarchy -{ - [self updateShadowViewWithReactTag:@1 layoutOnly:NO childTags:@[@2, @3, @4, @5]]; - [self updateShadowViewWithReactTag:@2 layoutOnly:YES childTags:@[@6, @7]]; - [self updateShadowViewWithReactTag:@3 layoutOnly:NO childTags:nil]; - [self updateShadowViewWithReactTag:@4 layoutOnly:YES childTags:@[@8]]; - [self updateShadowViewWithReactTag:@5 layoutOnly:NO childTags:@[@9, @10]]; - [self updateShadowViewWithReactTag:@6 layoutOnly:NO childTags:nil]; - [self updateShadowViewWithReactTag:@7 layoutOnly:NO childTags:nil]; - [self updateShadowViewWithReactTag:@8 layoutOnly:NO childTags:nil]; - [self updateShadowViewWithReactTag:@9 layoutOnly:NO childTags:nil]; - [self updateShadowViewWithReactTag:@10 layoutOnly:YES childTags:nil]; - [self updateShadowViewWithReactTag:@11 layoutOnly:YES childTags:@[@12, @13]]; - [self updateShadowViewWithReactTag:@12 layoutOnly:NO childTags:nil]; - [self updateShadowViewWithReactTag:@13 layoutOnly:NO childTags:nil]; -} - -- (void)testModifyIndices1 -{ - [self setUpShadowViewHierarchy]; - - NSMutableArray *addTags = [@[@2] mutableCopy]; - NSMutableArray *addIndices = [@[@3] mutableCopy]; - NSMutableArray *removeIndices = [@[@0] mutableCopy]; - [self.uiManager modifyManageChildren:@1 - addChildReactTags:addTags - addAtIndices:addIndices - removeAtIndices:removeIndices]; - XCTAssertEqualObjects(addTags, (@[@6, @7])); - XCTAssertEqualObjects(addIndices, (@[@3, @4])); - XCTAssertEqualObjects(removeIndices, (@[@0, @1])); -} - -- (void)testModifyIndices2 -{ - [self setUpShadowViewHierarchy]; - - NSMutableArray *addTags = [@[@11] mutableCopy]; - NSMutableArray *addIndices = [@[@4] mutableCopy]; - NSMutableArray *removeIndices = [@[] mutableCopy]; - [self.uiManager modifyManageChildren:@1 - addChildReactTags:addTags - addAtIndices:addIndices - removeAtIndices:removeIndices]; - XCTAssertEqualObjects(addTags, (@[@12, @13])); - XCTAssertEqualObjects(addIndices, (@[@5, @6])); - XCTAssertEqualObjects(removeIndices, (@[])); -} - -- (void)testModifyIndices3 -{ - [self setUpShadowViewHierarchy]; - - NSMutableArray *addTags = [@[] mutableCopy]; - NSMutableArray *addIndices = [@[] mutableCopy]; - NSMutableArray *removeIndices = [@[@2] mutableCopy]; - [self.uiManager modifyManageChildren:@1 - addChildReactTags:addTags - addAtIndices:addIndices - removeAtIndices:removeIndices]; - XCTAssertEqualObjects(addTags, (@[])); - XCTAssertEqualObjects(addIndices, (@[])); - XCTAssertEqualObjects(removeIndices, (@[@3])); -} - -- (void)testModifyIndices4 -{ - [self setUpShadowViewHierarchy]; - - NSMutableArray *addTags = [@[@11] mutableCopy]; - NSMutableArray *addIndices = [@[@3] mutableCopy]; - NSMutableArray *removeIndices = [@[@2] mutableCopy]; - [self.uiManager modifyManageChildren:@1 - addChildReactTags:addTags - addAtIndices:addIndices - removeAtIndices:removeIndices]; - XCTAssertEqualObjects(addTags, (@[@12, @13])); - XCTAssertEqualObjects(addIndices, (@[@4, @5])); - XCTAssertEqualObjects(removeIndices, (@[@3])); -} - -- (void)testModifyIndices5 -{ - [self setUpShadowViewHierarchy]; - - NSMutableArray *addTags = [@[] mutableCopy]; - NSMutableArray *addIndices = [@[] mutableCopy]; - NSMutableArray *removeIndices = [@[@0, @1, @2, @3] mutableCopy]; - [self.uiManager modifyManageChildren:@1 - addChildReactTags:addTags - addAtIndices:addIndices - removeAtIndices:removeIndices]; - XCTAssertEqualObjects(addTags, (@[])); - XCTAssertEqualObjects(addIndices, (@[])); - XCTAssertEqualObjects(removeIndices, (@[@0, @1, @2, @3, @4])); -} - -- (void)testModifyIndices6 -{ - [self setUpShadowViewHierarchy]; - - NSMutableArray *addTags = [@[@11] mutableCopy]; - NSMutableArray *addIndices = [@[@0] mutableCopy]; - NSMutableArray *removeIndices = [@[@0, @1, @2, @3] mutableCopy]; - [self.uiManager modifyManageChildren:@1 - addChildReactTags:addTags - addAtIndices:addIndices - removeAtIndices:removeIndices]; - XCTAssertEqualObjects(addTags, (@[@12, @13])); - XCTAssertEqualObjects(addIndices, (@[@0, @1])); - XCTAssertEqualObjects(removeIndices, (@[@0, @1, @2, @3, @4])); -} - -- (void)testModifyIndices7 -{ - [self setUpShadowViewHierarchy]; - - NSMutableArray *addTags = [@[@11] mutableCopy]; - NSMutableArray *addIndices = [@[@1] mutableCopy]; - NSMutableArray *removeIndices = [@[@0, @2, @3] mutableCopy]; - [self.uiManager modifyManageChildren:@1 - addChildReactTags:addTags - addAtIndices:addIndices - removeAtIndices:removeIndices]; - XCTAssertEqualObjects(addTags, (@[@12, @13])); - XCTAssertEqualObjects(addIndices, (@[@1, @2])); - XCTAssertEqualObjects(removeIndices, (@[@0, @1, @3, @4])); -} - -- (void)DISABLED_testScenario1 -{ - RCTUIManager *uiManager = [[RCTUIManager alloc] init]; - RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[uiManager]; } launchOptions:nil]; - NS_VALID_UNTIL_END_OF_SCOPE RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"Test"]; - - __block BOOL done = NO; - - dispatch_queue_t shadowQueue = [uiManager valueForKey:@"shadowQueue"]; - dispatch_async(shadowQueue, ^{ - // Make sure root view finishes loading. - dispatch_sync(dispatch_get_main_queue(), ^{}); - - /* */[uiManager createView:@2 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}]; - /* */[uiManager createView:@3 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}]; - /* V */[uiManager createView:@4 viewName:@"RCTView" rootTag:@1 props:@{@"alignItems":@"center",@"backgroundColor":@"#F5FCFF",@"flex":@1,@"justifyContent":@"center"}]; - /* V */[uiManager createView:@5 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"blue",@"height":@50,@"width":@50}]; - /* */[uiManager createView:@6 viewName:@"RCTView" rootTag:@1 props:@{@"width":@250}]; - /* V */[uiManager createView:@7 viewName:@"RCTView" rootTag:@1 props:@{@"borderWidth":@10,@"margin":@50}]; - /* V */[uiManager createView:@8 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"yellow",@"height":@50}]; - /* V */[uiManager createView:@9 viewName:@"RCTText" rootTag:@1 props:@{@"accessible":@1,@"fontSize":@20,@"isHighlighted":@0,@"margin":@10,@"textAlign":@"center"}]; - /* */[uiManager createView:@10 viewName:@"RCTRawText" rootTag:@1 props:@{@"text":@"This tests removal of layout-only views."}]; - /* */[uiManager manageChildren:@9 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@10] addAtIndices:@[@0] removeAtIndices:nil]; - /* V */[uiManager createView:@12 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"green",@"height":@50}]; - /* */[uiManager manageChildren:@7 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@8,@9,@12] addAtIndices:@[@0,@1,@2] removeAtIndices:nil]; - /* */[uiManager manageChildren:@6 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@7] addAtIndices:@[@0] removeAtIndices:nil]; - /* V */[uiManager createView:@13 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"red",@"height":@50,@"width":@50}]; - /* */[uiManager manageChildren:@4 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@5,@6,@13] addAtIndices:@[@0,@1,@2] removeAtIndices:nil]; - /* */[uiManager manageChildren:@3 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@4] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@2 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@3] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@1 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@2] addAtIndices:@[@0] removeAtIndices:nil]; - - [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { - XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)12); - XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)8); - done = YES; - }]; - - [uiManager flushUIBlocks]; - }); - - NSDate *date = [NSDate dateWithTimeIntervalSinceNow:1.0]; - while ([date timeIntervalSinceNow] > 0 && !done) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + NSArray *tagsToAdd = @[@1, @2, @3, @4, @5]; + NSArray *addAtIndices = @[@0, @1, @2, @3, @4]; + for (NSNumber *tag in tagsToAdd) { + [addedViews addObject:_uiManager.viewRegistry[tag]]; } - done = NO; - dispatch_async(shadowQueue, ^{ - [uiManager updateView:@7 viewName:@"RCTView" props:@{@"borderWidth":[NSNull null]}]; - [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { - XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)12); - XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)7); - done = YES; - }]; + // Add views 1-5 to view 20 + [_uiManager _manageChildren:@20 + moveFromIndices:nil + moveToIndices:nil + addChildReactTags:tagsToAdd + addAtIndices:addAtIndices + removeAtIndices:nil + registry:_uiManager.viewRegistry]; - [uiManager flushUIBlocks]; - }); - - date = [NSDate dateWithTimeIntervalSinceNow:1.0]; - while ([date timeIntervalSinceNow] > 0 && !done) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - } - - done = NO; - dispatch_async(shadowQueue, ^{ - [uiManager updateView:@7 viewName:@"RCTView" props:@{@"borderWidth":@10}]; - [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { - XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)12); - XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)8); - done = YES; - }]; - - [uiManager flushUIBlocks]; - }); - - date = [NSDate dateWithTimeIntervalSinceNow:1.0]; - while ([date timeIntervalSinceNow] > 0 && !done) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + XCTAssertTrue([[containerView reactSubviews] count] == 5, + @"Expect to have 5 react subviews after calling manage children \ + with 5 tags to add, instead have %lu", (unsigned long)[[containerView reactSubviews] count]); + for (UIView *view in addedViews) { + XCTAssertTrue([view superview] == containerView, + @"Expected to have manage children successfully add children"); + [view removeFromSuperview]; } } -- (void)DISABLED_testScenario2 +- (void)testManagingChildrenToRemoveViews { - RCTUIManager *uiManager = [[RCTUIManager alloc] init]; - RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[uiManager]; } launchOptions:nil]; - NS_VALID_UNTIL_END_OF_SCOPE RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"Test"]; + UIView *containerView = _uiManager.viewRegistry[20]; + NSMutableArray *removedViews = [NSMutableArray array]; - __block BOOL done = NO; - - dispatch_queue_t shadowQueue = [uiManager valueForKey:@"shadowQueue"]; - dispatch_async(shadowQueue, ^{ - // Make sure root view finishes loading. - dispatch_sync(dispatch_get_main_queue(), ^{}); - - /* */[uiManager createView:@2 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}]; - /* */[uiManager createView:@3 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}]; - /* V */[uiManager createView:@4 viewName:@"RCTView" rootTag:@1 props:@{@"alignItems":@"center",@"backgroundColor":@"#F5FCFF",@"flex":@1,@"justifyContent":@"center"}]; - /* */[uiManager createView:@5 viewName:@"RCTView" rootTag:@1 props:@{@"width":@250}]; - /* V */[uiManager createView:@6 viewName:@"RCTView" rootTag:@1 props:@{@"borderWidth":@1}]; - /* V */[uiManager createView:@7 viewName:@"RCTText" rootTag:@1 props:@{@"accessible":@1,@"fontSize":@20,@"isHighlighted":@0,@"margin":@10,@"textAlign":@"center"}]; - /* */[uiManager createView:@8 viewName:@"RCTRawText" rootTag:@1 props:@{@"text":@"This tests removal of layout-only views."}]; - /* */[uiManager manageChildren:@7 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@8] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@6 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@7] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@5 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@6] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@4 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@5] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@3 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@4] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@2 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@3] addAtIndices:@[@0] removeAtIndices:nil]; - /* */[uiManager manageChildren:@1 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@2] addAtIndices:@[@0] removeAtIndices:nil]; - - [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { - XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)8); - XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)4); - done = YES; - }]; - - [uiManager flushUIBlocks]; - }); - - NSDate *date = [NSDate dateWithTimeIntervalSinceNow:1.0]; - while ([date timeIntervalSinceNow] > 0 && !done) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + NSArray *removeAtIndices = @[@0, @4, @8, @12, @16]; + for (NSNumber *index in removeAtIndices) { + NSNumber *reactTag = @([index integerValue] + 2); + [removedViews addObject:_uiManager.viewRegistry[reactTag]]; + } + for (NSInteger i = 2; i < 20; i++) { + UIView *view = _uiManager.viewRegistry[i]; + [containerView addSubview:view]; } - done = NO; - dispatch_async(shadowQueue, ^{ - [uiManager updateView:@6 viewName:@"RCTView" props:@{@"borderWidth":[NSNull null]}]; - [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { - XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)8); - XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)3); - done = YES; - }]; + // Remove views 1-5 from view 20 + [_uiManager _manageChildren:@20 + moveFromIndices:nil + moveToIndices:nil + addChildReactTags:nil + addAtIndices:nil + removeAtIndices:removeAtIndices + registry:_uiManager.viewRegistry]; - [uiManager flushUIBlocks]; - }); + XCTAssertEqual(containerView.reactSubviews.count, (NSUInteger)13, + @"Expect to have 13 react subviews after calling manage children\ + with 5 tags to remove and 18 prior children, instead have %zd", + containerView.reactSubviews.count); + for (UIView *view in removedViews) { + XCTAssertTrue([view superview] == nil, + @"Expected to have manage children successfully remove children"); + // After removing views are unregistered - we need to reregister + _uiManager.viewRegistry[view.reactTag] = view; + } + for (NSInteger i = 2; i < 20; i++) { + UIView *view = _uiManager.viewRegistry[i]; + if (![removedViews containsObject:view]) { + XCTAssertTrue([view superview] == containerView, + @"Should not have removed view with react tag %ld during delete but did", (long)i); + [view removeFromSuperview]; + } + } +} - date = [NSDate dateWithTimeIntervalSinceNow:1.0]; - while ([date timeIntervalSinceNow] > 0 && !done) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; +// We want to start with views 1-10 added at indices 0-9 +// Then we'll remove indices 2, 3, 5 and 8 +// Add views 11 and 12 to indices 0 and 6 +// And move indices 4 and 9 to 1 and 7 +// So in total it goes from: +// [1,2,3,4,5,6,7,8,9,10] +// to +// [11,5,1,2,7,8,12,10] +- (void)testManagingChildrenToAddRemoveAndMove +{ + UIView *containerView = _uiManager.viewRegistry[20]; + + NSArray *removeAtIndices = @[@2, @3, @5, @8]; + NSArray *addAtIndices = @[@0, @6]; + NSArray *tagsToAdd = @[@11, @12]; + NSArray *moveFromIndices = @[@4, @9]; + NSArray *moveToIndices = @[@1, @7]; + + // We need to keep these in array to keep them around + NSMutableArray *viewsToRemove = [NSMutableArray array]; + for (NSUInteger i = 0; i < removeAtIndices.count; i++) { + NSNumber *reactTagToRemove = @([removeAtIndices[i] integerValue] + 1); + UIView *viewToRemove = _uiManager.viewRegistry[reactTagToRemove]; + [viewsToRemove addObject:viewToRemove]; } - done = NO; - dispatch_async(shadowQueue, ^{ - [uiManager updateView:@6 viewName:@"RCTView" props:@{@"borderWidth":@1}]; - [uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) { - XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)8); - XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)4); - done = YES; - }]; + for (NSInteger i = 1; i < 11; i++) { + UIView *view = _uiManager.viewRegistry[i]; + [containerView addSubview:view]; + } - [uiManager flushUIBlocks]; - }); + [_uiManager _manageChildren:@20 + moveFromIndices:moveFromIndices + moveToIndices:moveToIndices + addChildReactTags:tagsToAdd + addAtIndices:addAtIndices + removeAtIndices:removeAtIndices + registry:_uiManager.viewRegistry]; - date = [NSDate dateWithTimeIntervalSinceNow:1.0]; - while ([date timeIntervalSinceNow] > 0 && !done) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + XCTAssertTrue([[containerView reactSubviews] count] == 8, + @"Expect to have 8 react subviews after calling manage children,\ + instead have the following subviews %@", [containerView reactSubviews]); + + NSArray *expectedReactTags = @[@11, @5, @1, @2, @7, @8, @12, @10]; + for (NSUInteger i = 0; i < containerView.subviews.count; i++) { + XCTAssertEqualObjects([[containerView reactSubviews][i] reactTag], expectedReactTags[i], + @"Expected subview at index %ld to have react tag #%@ but has tag #%@", + (long)i, expectedReactTags[i], [[containerView reactSubviews][i] reactTag]); + } + + // Clean up after ourselves + for (NSInteger i = 1; i < 13; i++) { + UIView *view = _uiManager.viewRegistry[i]; + [view removeFromSuperview]; + } + for (UIView *view in viewsToRemove) { + _uiManager.viewRegistry[view.reactTag] = view; } } diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png index 2451b5f92174ff1514f7dbc474aa4c7bc633fb99..fb2ab9feb2e7b98d46e1a2edd832713606dff0e6 100644 GIT binary patch delta 18714 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 B0~-JU delta 18747 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 g>tr%;5NPr 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 diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m index 0e9290814..b587d9bca 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m @@ -14,8 +14,6 @@ #import -#import "RCTRootView.h" -#import "RCTShadowView.h" #import "RCTSparseArray.h" #import "RCTUIManager.h" #import "UIView+React.h" @@ -23,13 +21,14 @@ @interface RCTUIManager (Testing) - (void)_manageChildren:(NSNumber *)containerReactTag + moveFromIndices:(NSArray *)moveFromIndices + moveToIndices:(NSArray *)moveToIndices addChildReactTags:(NSArray *)addChildReactTags addAtIndices:(NSArray *)addAtIndices removeAtIndices:(NSArray *)removeAtIndices registry:(RCTSparseArray *)registry; @property (nonatomic, readonly) RCTSparseArray *viewRegistry; -@property (nonatomic, readonly) RCTSparseArray *shadowViewRegistry; // RCT thread only @end @@ -52,11 +51,6 @@ UIView *registeredView = [[UIView alloc] init]; [registeredView setReactTag:@(i)]; _uiManager.viewRegistry[i] = registeredView; - - RCTShadowView *registeredShadowView = [[RCTShadowView alloc] init]; - registeredShadowView.viewName = @"RCTView"; - [registeredShadowView setReactTag:@(i)]; - _uiManager.shadowViewRegistry[i] = registeredShadowView; } } @@ -73,6 +67,8 @@ // Add views 1-5 to view 20 [_uiManager _manageChildren:@20 + moveFromIndices:nil + moveToIndices:nil addChildReactTags:tagsToAdd addAtIndices:addAtIndices removeAtIndices:nil @@ -105,6 +101,8 @@ // Remove views 1-5 from view 20 [_uiManager _manageChildren:@20 + moveFromIndices:nil + moveToIndices:nil addChildReactTags:nil addAtIndices:nil removeAtIndices:removeAtIndices @@ -142,9 +140,11 @@ { UIView *containerView = _uiManager.viewRegistry[20]; - NSArray *removeAtIndices = @[@2, @3, @5, @8, @4, @9]; - NSArray *addAtIndices = @[@0, @6, @1, @7]; - NSArray *tagsToAdd = @[@11, @12, @5, @10]; + NSArray *removeAtIndices = @[@2, @3, @5, @8]; + NSArray *addAtIndices = @[@0, @6]; + NSArray *tagsToAdd = @[@11, @12]; + NSArray *moveFromIndices = @[@4, @9]; + NSArray *moveToIndices = @[@1, @7]; // We need to keep these in array to keep them around NSMutableArray *viewsToRemove = [NSMutableArray array]; @@ -160,6 +160,8 @@ } [_uiManager _manageChildren:@20 + moveFromIndices:moveFromIndices + moveToIndices:moveToIndices addChildReactTags:tagsToAdd addAtIndices:addAtIndices removeAtIndices:removeAtIndices diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index f01cce58a..512d8dbd6 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -279,7 +279,6 @@ var ScrollView = React.createClass({ var contentContainer = diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index 6912c05db..707d97b7e 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -83,12 +83,6 @@ var View = React.createClass({ }, propTypes: { - /** - * When false, indicates that the view should not be collapsed, even if it is - * layout-only. Defaults to true. - */ - collapsible: PropTypes.bool, - /** * When true, indicates that the view is an accessibility element. By default, * all the touchable elements are accessible. diff --git a/Libraries/ReactNative/ReactNativeViewAttributes.js b/Libraries/ReactNative/ReactNativeViewAttributes.js index 367c6be2d..50b839e1d 100644 --- a/Libraries/ReactNative/ReactNativeViewAttributes.js +++ b/Libraries/ReactNative/ReactNativeViewAttributes.js @@ -24,22 +24,6 @@ ReactNativeViewAttributes.UIView = { onLayout: true, onAccessibilityTap: true, onMagicTap: true, - collapsible: true, - - // If editing layout-only view attributes, make sure - // -[RCTShadowView isLayoutOnly] in RCTShadowView.m - // is up-to-date! If any property below is set, the - // view should not be collapsible, but this is done - // on the native side. - onMoveShouldSetResponder: true, - onResponderGrant: true, - onResponderMove: true, - onResponderReject: true, - onResponderRelease: true, - onResponderTerminate: true, - onResponderTerminationRequest: true, - onStartShouldSetResponder: true, - onStartShouldSetResponderCapture: true, }; ReactNativeViewAttributes.RCTView = merge( diff --git a/Libraries/Text/RCTShadowRawText.m b/Libraries/Text/RCTShadowRawText.m index 00a3490bc..e99e1187b 100644 --- a/Libraries/Text/RCTShadowRawText.m +++ b/Libraries/Text/RCTShadowRawText.m @@ -20,11 +20,6 @@ } } -- (BOOL)isLayoutOnly -{ - return YES; -} - - (NSString *)description { NSString *superDescription = super.description; diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 0b3c2c043..724ace6e6 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -32,8 +32,15 @@ #import "RCTViewNodeProtocol.h" #import "UIView+React.h" -static void RCTTraverseViewNodes(id view, void (^block)(id)); -static NSDictionary *RCTPropsMerge(NSDictionary *beforeProps, NSDictionary *newProps); +typedef void (^react_view_node_block_t)(id); + +static void RCTTraverseViewNodes(id view, react_view_node_block_t block) +{ + if (view.reactTag) block(view); + for (id subview in view.reactSubviews) { + RCTTraverseViewNodes(subview, block); + } +} @interface RCTAnimation : NSObject @@ -460,24 +467,6 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass) [rootShadowView collectRootUpdatedFrames:viewsWithNewFrames parentConstraint:(CGSize){CSS_UNDEFINED, CSS_UNDEFINED}]; - NSSet *originalViewsWithNewFrames = [viewsWithNewFrames copy]; - NSMutableArray *viewsToCheck = [viewsWithNewFrames.allObjects mutableCopy]; - while (viewsToCheck.count > 0) { - // Better to remove from the front and append to the end - // because of how NSMutableArray is implemented. - // (It's a "round" buffer with stored size and offset.) - - RCTShadowView *viewToCheck = viewsToCheck.firstObject; - [viewsToCheck removeObjectAtIndex:0]; - - if (viewToCheck.layoutOnly) { - [viewsWithNewFrames removeObject:viewToCheck]; - [viewsToCheck addObjectsFromArray:[viewToCheck reactSubviews]]; - } else { - [viewsWithNewFrames addObject:viewToCheck]; - } - } - // Parallel arrays are built and then handed off to main thread NSMutableArray *frameReactTags = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count]; NSMutableArray *frames = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count]; @@ -486,30 +475,26 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass) NSMutableArray *onLayoutEvents = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count]; for (RCTShadowView *shadowView in viewsWithNewFrames) { - CGRect frame = shadowView.adjustedFrame; - NSNumber *reactTag = shadowView.reactTag; - [frameReactTags addObject:reactTag]; - [frames addObject:[NSValue valueWithCGRect:frame]]; + [frameReactTags addObject:shadowView.reactTag]; + [frames addObject:[NSValue valueWithCGRect:shadowView.frame]]; [areNew addObject:@(shadowView.isNewView)]; - - RCTShadowView *superview = shadowView; - BOOL parentIsNew = NO; - while (YES) { - superview = superview.superview; - parentIsNew = superview.isNewView; - if (!superview.layoutOnly) { - break; - } + [parentsAreNew addObject:@(shadowView.superview.isNewView)]; + id event = (id)kCFNull; + if (shadowView.hasOnLayout) { + event = @{ + @"target": shadowView.reactTag, + @"layout": @{ + @"x": @(shadowView.frame.origin.x), + @"y": @(shadowView.frame.origin.y), + @"width": @(shadowView.frame.size.width), + @"height": @(shadowView.frame.size.height), + }, + }; } - [parentsAreNew addObject:@(parentIsNew)]; - - id event = shadowView.hasOnLayout - ? RCTShadowViewOnLayoutEventPayload(shadowView.reactTag, frame) - : (id)kCFNull; [onLayoutEvents addObject:event]; } - for (RCTShadowView *shadowView in originalViewsWithNewFrames) { + for (RCTShadowView *shadowView in viewsWithNewFrames) { // We have to do this after we build the parentsAreNew array. shadowView.newView = NO; } @@ -526,28 +511,24 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass) } // Perform layout (possibly animated) - return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { - RCTResponseSenderBlock callback = uiManager->_layoutAnimation.callback; + return ^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { + RCTResponseSenderBlock callback = self->_layoutAnimation.callback; __block NSUInteger completionsCalled = 0; for (NSUInteger ii = 0; ii < frames.count; ii++) { NSNumber *reactTag = frameReactTags[ii]; UIView *view = viewRegistry[reactTag]; - if (!view) { - continue; - } - CGRect frame = [frames[ii] CGRectValue]; id event = onLayoutEvents[ii]; BOOL isNew = [areNew[ii] boolValue]; - RCTAnimation *updateAnimation = isNew ? nil : uiManager->_layoutAnimation.updateAnimation; + RCTAnimation *updateAnimation = isNew ? nil : _layoutAnimation.updateAnimation; BOOL shouldAnimateCreation = isNew && ![parentsAreNew[ii] boolValue]; - RCTAnimation *createAnimation = shouldAnimateCreation ? uiManager->_layoutAnimation.createAnimation : nil; + RCTAnimation *createAnimation = shouldAnimateCreation ? _layoutAnimation.createAnimation : nil; void (^completion)(BOOL) = ^(BOOL finished) { completionsCalled++; if (event != (id)kCFNull) { - [uiManager.bridge.eventDispatcher sendInputEventWithName:@"topLayout" body:event]; + [self.bridge.eventDispatcher sendInputEventWithName:@"topLayout" body:event]; } if (callback && completionsCalled == frames.count - 1) { callback(@[@(finished)]); @@ -559,13 +540,13 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass) [updateAnimation performAnimations:^{ [view reactSetFrame:frame]; for (RCTViewManagerUIBlock block in updateBlocks) { - block(uiManager, viewRegistry); + block(self, _viewRegistry); } } withCompletionBlock:completion]; } else { [view reactSetFrame:frame]; for (RCTViewManagerUIBlock block in updateBlocks) { - block(uiManager, viewRegistry); + block(self, _viewRegistry); } completion(YES); } @@ -587,7 +568,7 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass) createAnimation.property); } for (RCTViewManagerUIBlock block in updateBlocks) { - block(uiManager, viewRegistry); + block(self, _viewRegistry); } } withCompletionBlock:nil]; } @@ -710,135 +691,6 @@ RCT_EXPORT_METHOD(replaceExistingNonRootView:(NSNumber *)reactTag withView:(NSNu removeAtIndices:removeAtIndices]; } -/** - * This method modifies the indices received in manageChildren() to take into - * account views that are layout only. For example, if JS tells native to insert - * view with tag 12 at index 4, but view 12 is layout only, we would want to - * insert its children's tags, tags 13 and 14, at indices 4 and 5 instead. This - * causes us to have to shift the remaining indices to account for the new - * views. - */ -- (void)modifyManageChildren:(NSNumber *)containerReactTag - addChildReactTags:(NSMutableArray *)mutableAddChildReactTags - addAtIndices:(NSMutableArray *)mutableAddAtIndices - removeAtIndices:(NSMutableArray *)mutableRemoveAtIndices -{ - NSUInteger i; - NSMutableArray *containerSubviews = [[_shadowViewRegistry[containerReactTag] reactSubviews] mutableCopy]; - - i = 0; - while (i < containerSubviews.count) { - RCTShadowView *shadowView = containerSubviews[i]; - if (!shadowView.layoutOnly) { - i++; - continue; - } - - [containerSubviews removeObjectAtIndex:i]; - - NSArray *subviews = [shadowView reactSubviews]; - NSUInteger subviewsCount = subviews.count; - NSIndexSet *insertionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(i, subviewsCount)]; - [containerSubviews insertObjects:subviews atIndexes:insertionIndexes]; - - NSUInteger removalIndex = [mutableRemoveAtIndices indexOfObject:@(i)]; - if (removalIndex != NSNotFound) { - [mutableRemoveAtIndices removeObjectAtIndex:removalIndex]; - } - - if (subviewsCount != 1) { - for (NSUInteger j = 0, count = mutableRemoveAtIndices.count; j < count; j++) { - NSUInteger atIndex = [mutableRemoveAtIndices[j] unsignedIntegerValue]; - if (atIndex > i) { - mutableRemoveAtIndices[j] = @(atIndex + subviewsCount - 1); - } - } - } - - if (removalIndex != NSNotFound) { - for (NSUInteger j = 0; j < subviewsCount; j++) { - [mutableRemoveAtIndices insertObject:@(i + j) atIndex:removalIndex + j]; - } - } - - if (removalIndex == NSNotFound && subviewsCount != 1) { - for (NSUInteger j = 0, count = mutableAddAtIndices.count; j < count; j++) { - NSUInteger atIndex = [mutableAddAtIndices[j] unsignedIntegerValue]; - if (atIndex > i) { - mutableAddAtIndices[j] = @(atIndex + subviewsCount - 1); - } - } - } - } - - i = 0; - while (i < mutableAddChildReactTags.count) { - NSNumber *tag = mutableAddChildReactTags[i]; - NSNumber *index = mutableAddAtIndices[i]; - - RCTShadowView *shadowView = _shadowViewRegistry[tag]; - if (!shadowView.layoutOnly) { - i++; - continue; - } - - NSArray *subviews = [shadowView reactSubviews]; - NSUInteger subviewsCount = subviews.count; - [mutableAddAtIndices removeObjectAtIndex:i]; - [mutableAddChildReactTags removeObjectAtIndex:i]; - - for (NSUInteger j = 0; j < subviewsCount; j++) { - [mutableAddChildReactTags insertObject:[subviews[j] reactTag] atIndex:i + j]; - [mutableAddAtIndices insertObject:@(index.unsignedIntegerValue + j) atIndex:i + j]; - } - - for (NSUInteger j = i + subviewsCount, count = mutableAddAtIndices.count; j < count; j++) { - NSUInteger atIndex = [mutableAddAtIndices[j] unsignedIntegerValue]; - mutableAddAtIndices[j] = @(atIndex + subviewsCount - 1); - } - } -} - -- (NSNumber *)containerReactTag:(NSNumber *)containerReactTag offset:(inout NSUInteger *)offset -{ - RCTShadowView *container = _shadowViewRegistry[containerReactTag]; - NSNumber *containerSuperviewReactTag = containerReactTag; - RCTShadowView *superview = container; - - while (superview.layoutOnly) { - RCTShadowView *superviewSuperview = superview.superview; - containerSuperviewReactTag = superviewSuperview.reactTag; - NSMutableArray *reactSubviews = [[superviewSuperview reactSubviews] mutableCopy]; - NSUInteger superviewIndex = [reactSubviews indexOfObject:superview]; - - NSUInteger i = 0; - while (i < superviewIndex) { - RCTShadowView *child = reactSubviews[i]; - if (!child.layoutOnly) { - if (offset) { - (*offset)++; - } - - i++; - continue; - } - - [reactSubviews removeObjectAtIndex:i]; - - NSArray *subviews = [child reactSubviews]; - NSUInteger subviewsCount = subviews.count; - NSIndexSet *insertionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(i, subviewsCount)]; - [reactSubviews insertObjects:subviews atIndexes:insertionIndexes]; - - superviewIndex += subviewsCount - 1; - } - - superview = superviewSuperview; - } - - return containerSuperviewReactTag; -} - RCT_EXPORT_METHOD(manageChildren:(NSNumber *)containerReactTag moveFromIndices:(NSArray *)moveFromIndices moveToIndices:(NSArray *)moveToIndices @@ -846,109 +698,62 @@ RCT_EXPORT_METHOD(manageChildren:(NSNumber *)containerReactTag addAtIndices:(NSArray *)addAtIndices removeAtIndices:(NSArray *)removeAtIndices) { - RCTShadowView *container = _shadowViewRegistry[containerReactTag]; - NSUInteger offset = 0; - NSNumber *containerSuperviewReactTag = [self containerReactTag:containerReactTag offset:&offset]; - - RCTAssert(moveFromIndices.count == moveToIndices.count, @"Invalid argument: moveFromIndices.count != moveToIndices.count"); - if (moveFromIndices.count > 0) { - NSMutableArray *mutableAddChildReactTags = [addChildReactTags mutableCopy]; - NSMutableArray *mutableAddAtIndices = [addAtIndices mutableCopy]; - NSMutableArray *mutableRemoveAtIndices = [removeAtIndices mutableCopy]; - - NSArray *containerSubviews = [container reactSubviews]; - for (NSUInteger i = 0, count = moveFromIndices.count; i < count; i++) { - NSNumber *from = moveFromIndices[i]; - NSNumber *to = moveToIndices[i]; - [mutableAddChildReactTags addObject:[containerSubviews[from.unsignedIntegerValue] reactTag]]; - [mutableAddAtIndices addObject:to]; - [mutableRemoveAtIndices addObject:from]; - } - - addChildReactTags = mutableAddChildReactTags; - addAtIndices = mutableAddAtIndices; - removeAtIndices = mutableRemoveAtIndices; - } - - NSMutableArray *mutableAddChildReactTags; - NSMutableArray *mutableAddAtIndices; - NSMutableArray *mutableRemoveAtIndices; - - if (containerSuperviewReactTag) { - mutableAddChildReactTags = [addChildReactTags mutableCopy]; - mutableAddAtIndices = [addAtIndices mutableCopy]; - mutableRemoveAtIndices = [removeAtIndices mutableCopy]; - - [self modifyManageChildren:containerReactTag - addChildReactTags:mutableAddChildReactTags - addAtIndices:mutableAddAtIndices - removeAtIndices:mutableRemoveAtIndices]; - - if (offset > 0) { - NSUInteger count = MAX(mutableAddAtIndices.count, mutableRemoveAtIndices.count); - for (NSUInteger i = 0; i < count; i++) { - if (i < mutableAddAtIndices.count) { - NSUInteger index = [mutableAddAtIndices[i] unsignedIntegerValue]; - mutableAddAtIndices[i] = @(index + offset); - } - - if (i < mutableRemoveAtIndices.count) { - NSUInteger index = [mutableRemoveAtIndices[i] unsignedIntegerValue]; - mutableRemoveAtIndices[i] = @(index + offset); - } - } - } - } - [self _manageChildren:containerReactTag + moveFromIndices:moveFromIndices + moveToIndices:moveToIndices addChildReactTags:addChildReactTags addAtIndices:addAtIndices removeAtIndices:removeAtIndices registry:_shadowViewRegistry]; - if (containerSuperviewReactTag) { - [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ - (void)(id []){containerReactTag, @(offset), addChildReactTags, addAtIndices, removeAtIndices}; - [uiManager _manageChildren:containerSuperviewReactTag - addChildReactTags:mutableAddChildReactTags - addAtIndices:mutableAddAtIndices - removeAtIndices:mutableRemoveAtIndices - registry:viewRegistry]; - }]; - } + [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ + [uiManager _manageChildren:containerReactTag + moveFromIndices:moveFromIndices + moveToIndices:moveToIndices + addChildReactTags:addChildReactTags + addAtIndices:addAtIndices + removeAtIndices:removeAtIndices + registry:viewRegistry]; + }]; } - (void)_manageChildren:(NSNumber *)containerReactTag + moveFromIndices:(NSArray *)moveFromIndices + moveToIndices:(NSArray *)moveToIndices addChildReactTags:(NSArray *)addChildReactTags addAtIndices:(NSArray *)addAtIndices removeAtIndices:(NSArray *)removeAtIndices registry:(RCTSparseArray *)registry { id container = registry[containerReactTag]; - RCTAssert(addChildReactTags.count == addAtIndices.count, @"Invalid arguments: addChildReactTags.count == addAtIndices.count"); + RCTAssert(moveFromIndices.count == moveToIndices.count, @"moveFromIndices had size %tu, moveToIndices had size %tu", moveFromIndices.count, moveToIndices.count); + RCTAssert(addChildReactTags.count == addAtIndices.count, @"there should be at least one React child to add"); - // Removes are using "before" indices - NSArray *removedChildren = [self _childrenToRemoveFromContainer:container atIndices:removeAtIndices]; - [self _removeChildren:removedChildren fromContainer:container]; + // Removes (both permanent and temporary moves) are using "before" indices + NSArray *permanentlyRemovedChildren = [self _childrenToRemoveFromContainer:container atIndices:removeAtIndices]; + NSArray *temporarilyRemovedChildren = [self _childrenToRemoveFromContainer:container atIndices:moveFromIndices]; + [self _removeChildren:permanentlyRemovedChildren fromContainer:container]; + [self _removeChildren:temporarilyRemovedChildren fromContainer:container]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT reactTag in %@", addChildReactTags]; - NSArray *permanentlyRemovedChildren = [removedChildren filteredArrayUsingPredicate:predicate]; [self _purgeChildren:permanentlyRemovedChildren fromRegistry:registry]; // TODO (#5906496): optimize all these loops - constantly calling array.count is not efficient - // Figure out what to insert - NSMutableDictionary *childrenToAdd = [NSMutableDictionary dictionary]; - for (NSInteger index = 0, count = addAtIndices.count; index < count; index++) { - id view = registry[addChildReactTags[index]]; + // Figure out what to insert - merge temporary inserts and adds + NSMutableDictionary *destinationsToChildrenToAdd = [NSMutableDictionary dictionary]; + for (NSInteger index = 0, length = temporarilyRemovedChildren.count; index < length; index++) { + destinationsToChildrenToAdd[moveToIndices[index]] = temporarilyRemovedChildren[index]; + } + for (NSInteger index = 0, length = addAtIndices.count; index < length; index++) { + id view = registry[addChildReactTags[index]]; if (view) { - childrenToAdd[addAtIndices[index]] = view; + destinationsToChildrenToAdd[addAtIndices[index]] = view; } } - NSArray *sortedIndices = [[childrenToAdd allKeys] sortedArrayUsingSelector:@selector(compare:)]; + NSArray *sortedIndices = [[destinationsToChildrenToAdd allKeys] sortedArrayUsingSelector:@selector(compare:)]; for (NSNumber *reactIndex in sortedIndices) { - [container insertReactSubview:childrenToAdd[reactIndex] atIndex:reactIndex.integerValue]; + [container insertReactSubview:destinationsToChildrenToAdd[reactIndex] atIndex:reactIndex.integerValue]; } } @@ -1031,72 +836,45 @@ RCT_EXPORT_METHOD(createView:(NSNumber *)reactTag // Set properties shadowView.viewName = viewName; shadowView.reactTag = reactTag; - shadowView.allProps = props; RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[viewName], manager); } _shadowViewRegistry[reactTag] = shadowView; - if (!shadowView.layoutOnly) { - // Shadow view is the source of truth for background color this is a little - // bit counter-intuitive if people try to set background color when setting up - // the view, but it's the only way that makes sense given our threading model - UIColor *backgroundColor = shadowView.backgroundColor; - [self addUIBlock:^(RCTUIManager *uiManager, __unused RCTSparseArray *viewRegistry) { - [uiManager createView:reactTag viewName:viewName props:props withManager:manager backgroundColor:backgroundColor]; - }]; - } -} + // Shadow view is the source of truth for background color this is a little + // bit counter-intuitive if people try to set background color when setting up + // the view, but it's the only way that makes sense given our threading model + UIColor *backgroundColor = shadowView.backgroundColor; -- (UIView *)createView:(NSNumber *)reactTag viewName:(NSString *)viewName props:(NSDictionary *)props withManager:(RCTViewManager *)manager backgroundColor:(UIColor *)backgroundColor -{ - RCTAssertMainThread(); - UIView *view = [manager view]; - if (!view) { - return nil; - } + [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ + RCTAssertMainThread(); - // Generate default view, used for resetting default props - if (!_defaultViews[viewName]) { - // Note the default is setup after the props are read for the first time - // ever for this className - this is ok because we only use the default - // for restoring defaults, which never happens on first creation. - _defaultViews[viewName] = [manager view]; - } + UIView *view = [manager view]; + if (view) { - // Set properties - view.reactTag = reactTag; - view.backgroundColor = backgroundColor; - if ([view isKindOfClass:[UIView class]]) { - view.multipleTouchEnabled = YES; - view.userInteractionEnabled = YES; // required for touch handling - view.layer.allowsGroupOpacity = YES; // required for touch handling - } - RCTSetViewProps(props, view, _defaultViews[viewName], manager); + // Generate default view, used for resetting default props + if (!uiManager->_defaultViews[viewName]) { + // Note the default is setup after the props are read for the first time + // ever for this className - this is ok because we only use the default + // for restoring defaults, which never happens on first creation. + uiManager->_defaultViews[viewName] = [manager view]; + } - if ([view respondsToSelector:@selector(reactBridgeDidFinishTransaction)]) { - [_bridgeTransactionListeners addObject:view]; - } - _viewRegistry[reactTag] = view; + // Set properties + view.reactTag = reactTag; + view.backgroundColor = backgroundColor; + if ([view isKindOfClass:[UIView class]]) { + view.multipleTouchEnabled = YES; + view.userInteractionEnabled = YES; // required for touch handling + view.layer.allowsGroupOpacity = YES; // required for touch handling + } + RCTSetViewProps(props, view, uiManager->_defaultViews[viewName], manager); - return view; -} - -NS_INLINE BOOL RCTRectIsDefined(CGRect frame) -{ - return !(isnan(frame.origin.x) || isnan(frame.origin.y) || isnan(frame.size.width) || isnan(frame.size.height)); -} - -NS_INLINE NSDictionary *RCTShadowViewOnLayoutEventPayload(NSNumber *reactTag, CGRect frame) -{ - return @{ - @"target": reactTag, - @"layout": @{ - @"x": @(frame.origin.x), - @"y": @(frame.origin.y), - @"width": @(frame.size.width), - @"height": @(frame.size.height), - }, - }; + if ([view respondsToSelector:@selector(reactBridgeDidFinishTransaction)]) { + [uiManager->_bridgeTransactionListeners addObject:view]; + } + } + viewRegistry[reactTag] = view; + }]; } // TODO: remove viewName param as it isn't needed @@ -1110,100 +888,10 @@ RCT_EXPORT_METHOD(updateView:(NSNumber *)reactTag RCTShadowView *shadowView = _shadowViewRegistry[reactTag]; RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[viewName], viewManager); - const BOOL wasLayoutOnly = shadowView.layoutOnly; - NSDictionary *newProps = RCTPropsMerge(shadowView.allProps, props); - shadowView.allProps = newProps; - - const BOOL isLayoutOnly = shadowView.layoutOnly; - - if (wasLayoutOnly != isLayoutOnly) { - // Add/remove node - - if (isLayoutOnly) { - [self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { - RCTAssertMainThread(); - - UIView *container = viewRegistry[reactTag]; - - const CGRect containerFrame = container.frame; - const CGFloat deltaX = containerFrame.origin.x; - const CGFloat deltaY = containerFrame.origin.y; - - NSUInteger offset = [container.superview.subviews indexOfObject:container]; - [container.subviews enumerateObjectsUsingBlock:^(UIView *subview, NSUInteger idx, __unused BOOL *stop) { - [container removeReactSubview:subview]; - - CGRect subviewFrame = subview.frame; - subviewFrame.origin.x += deltaX; - subviewFrame.origin.y += deltaY; - subview.frame = subviewFrame; - - [container.superview insertReactSubview:subview atIndex:idx + offset]; - }]; - - [container.superview removeReactSubview:container]; - if ([container conformsToProtocol:@protocol(RCTInvalidating)]) { - [(id)container invalidate]; - } - - viewRegistry[reactTag] = nil; - }]; - } else { - NSMutableArray *mutableAddChildReactTags = [[[shadowView reactSubviews] valueForKey:@"reactTag"] mutableCopy]; - NSMutableArray *mutableAddAtIndices = [NSMutableArray arrayWithCapacity:mutableAddChildReactTags.count]; - for (NSUInteger i = 0, count = mutableAddChildReactTags.count; i < count; i++) { - [mutableAddAtIndices addObject:@(i)]; - } - - [self modifyManageChildren:reactTag - addChildReactTags:mutableAddChildReactTags - addAtIndices:mutableAddAtIndices - removeAtIndices:nil]; - - NSUInteger offset = [shadowView.superview.reactSubviews indexOfObject:shadowView]; - NSNumber *containerSuperviewReactTag = [self containerReactTag:shadowView.superview.reactTag offset:&offset]; - UIColor *backgroundColor = shadowView.backgroundColor; - - CGRect shadowViewFrame = shadowView.adjustedFrame; - NSMutableDictionary *newFrames = [NSMutableDictionary dictionaryWithCapacity:mutableAddChildReactTags.count]; - for (NSNumber *childTag in mutableAddChildReactTags) { - RCTShadowView *child = _shadowViewRegistry[childTag]; - newFrames[childTag] = [NSValue valueWithCGRect:child.adjustedFrame]; - } - - [self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { - RCTAssertMainThread(); - - UIView *containerSuperview = viewRegistry[containerSuperviewReactTag]; - UIView *container = [uiManager createView:reactTag viewName:viewName props:newProps withManager:viewManager backgroundColor:backgroundColor]; - - [containerSuperview insertReactSubview:container atIndex:offset]; - if (RCTRectIsDefined(shadowViewFrame)) { - container.frame = shadowViewFrame; - } - - for (NSUInteger i = 0, count = mutableAddAtIndices.count; i < count; i++) { - NSNumber *tag = mutableAddChildReactTags[i]; - UIView *subview = viewRegistry[tag]; - [containerSuperview removeReactSubview:subview]; - - NSUInteger atIndex = [mutableAddAtIndices[i] unsignedIntegerValue]; - [container insertReactSubview:subview atIndex:atIndex]; - - CGRect subviewFrame = [newFrames[tag] CGRectValue]; - if (RCTRectIsDefined(subviewFrame)) { - subview.frame = subviewFrame; - } - } - }]; - } - } else if (!isLayoutOnly) { - // Update node - [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { - UIView *view = viewRegistry[reactTag]; - RCTSetViewProps(props, view, uiManager->_defaultViews[viewName], viewManager); - }]; - } + [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { + UIView *view = viewRegistry[reactTag]; + RCTSetViewProps(props, view, uiManager->_defaultViews[viewName], viewManager); + }]; } RCT_EXPORT_METHOD(focus:(NSNumber *)reactTag) @@ -1541,16 +1229,12 @@ RCT_EXPORT_METHOD(zoomToRect:(NSNumber *)reactTag RCT_EXPORT_METHOD(setJSResponder:(NSNumber *)reactTag blockNativeResponder:(__unused BOOL)blockNativeResponder) { - RCTShadowView *shadowView = _shadowViewRegistry[reactTag]; - if (!shadowView) { - RCTLogError(@"Invalid view set to be the JS responder - tag %@", reactTag); - } else if (shadowView.layoutOnly) { - RCTLogError(@"Cannot set JS responder to layout-only view with tag %@ - %@, %@", reactTag, shadowView, shadowView.allProps); - } else { - [self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { - _jsResponder = viewRegistry[reactTag]; - }]; - } + [self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { + _jsResponder = viewRegistry[reactTag]; + if (!_jsResponder) { + RCTLogError(@"Invalid view set to be the JS responder - tag %zd", reactTag); + } + }]; } RCT_EXPORT_METHOD(clearJSResponder) @@ -1806,27 +1490,3 @@ static UIView *_jsResponder; } @end - -static void RCTTraverseViewNodes(id view, void (^block)(id)) -{ - if (view.reactTag) block(view); - for (id subview in view.reactSubviews) { - RCTTraverseViewNodes(subview, block); - } -} - -static NSDictionary *RCTPropsMerge(NSDictionary *beforeProps, NSDictionary *newProps) -{ - NSMutableDictionary *afterProps = [NSMutableDictionary dictionaryWithDictionary:beforeProps]; - - // Can't use -addEntriesFromDictionary: because we want to remove keys with NSNull values. - [newProps enumerateKeysAndObjectsUsingBlock:^(id key, id obj, __unused BOOL *stop) { - if (obj == (id)kCFNull) { - [afterProps removeObjectForKey:key]; - } else { - afterProps[key] = obj; - } - }]; - - return afterProps; -} diff --git a/React/Views/RCTShadowView.h b/React/Views/RCTShadowView.h index 38edc6e50..1c44033f6 100644 --- a/React/Views/RCTShadowView.h +++ b/React/Views/RCTShadowView.h @@ -41,12 +41,6 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *viewRegistry); @property (nonatomic, assign) RCTUpdateLifecycle layoutLifecycle; @property (nonatomic, assign) BOOL hasOnLayout; -@property (nonatomic, assign, readonly, getter=isLayoutOnly) BOOL layoutOnly; -@property (nonatomic, copy) NSDictionary *allProps; - -/// `frame` adjusted for recursive superview `layoutOnly` status. -@property (nonatomic, assign, readonly) CGRect adjustedFrame; - /** * isNewView - Used to track the first time the view is introduced into the hierarchy. It is initialized YES, then is * set to NO in RCTUIManager after the layout pass is done and all frames have been extracted to be applied to the diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m index e5cccbd3f..9d56bb906 100644 --- a/React/Views/RCTShadowView.m +++ b/React/Views/RCTShadowView.m @@ -367,10 +367,8 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st - (NSString *)description { NSString *description = super.description; - if (self.layoutOnly) { - description = [@"* " stringByAppendingString:description]; - } - return [[description substringToIndex:description.length - 1] stringByAppendingFormat:@"; viewName: %@; reactTag: %@; frame: %@>", self.viewName, self.reactTag, NSStringFromCGRect(self.frame)]; + description = [[description substringToIndex:description.length - 1] stringByAppendingFormat:@"; viewName: %@; reactTag: %@; frame: %@>", self.viewName, self.reactTag, NSStringFromCGRect(self.frame)]; + return description; } - (void)addRecursiveDescriptionToString:(NSMutableString *)string atLevel:(NSUInteger)level @@ -394,94 +392,6 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st return description; } -- (BOOL)isLayoutOnly -{ - if (![self.viewName isEqualToString:@"RCTView"]) { - // For now, only `RCTView`s can be layout-only. - return NO; - } - - // dispatch_once is unnecessary because this property SHOULD only be accessed - // on the shadow queue - static NSSet *layoutKeys; - static NSSet *specialKeys; - if (!layoutKeys || !specialKeys) { - // Taken from LayoutPropTypes.js with the exception that borderWidth, - // borderTopWidth, borderBottomWidth, borderLeftWidth, and borderRightWidth - // were removed because black color is assumed - static NSString *const layoutKeyStrings[] = { - @"width", - @"height", - @"top", - @"left", - @"right", - @"bottom", - @"margin", - @"marginVertical", - @"marginHorizontal", - @"marginTop", - @"marginBottom", - @"marginLeft", - @"marginRight", - @"padding", - @"paddingVertical", - @"paddingHorizontal", - @"paddingTop", - @"paddingBottom", - @"paddingLeft", - @"paddingRight", - @"position", - @"flexDirection", - @"flexWrap", - @"justifyContent", - @"alignItems", - @"alignSelf", - @"flex", - }; - layoutKeys = [NSSet setWithObjects:layoutKeyStrings count:sizeof(layoutKeyStrings)/sizeof(*layoutKeyStrings)]; - // layoutKeys are the only keys whose presence does not reject layout-only status. - - static NSString *const specialKeyStrings[] = { - @"accessible", - @"collapsible", - }; - specialKeys = [NSSet setWithObjects:specialKeyStrings count:sizeof(specialKeyStrings)/sizeof(*specialKeyStrings)]; - // specialKeys are keys whose presence does not indicate whether layout-only or not - // their values must be tested below - } - - NSNumber *collapsible = self.allProps[@"collapsible"]; - if (collapsible && !collapsible.boolValue) { - return NO; - } - - NSNumber *accessible = self.allProps[@"accessible"]; - if (accessible && accessible.boolValue) { - return NO; - } - - for (NSString *key in self.allProps) { - if (![specialKeys containsObject:key] && ![layoutKeys containsObject:key]) { - return NO; - } - } - - return YES; -} - -- (CGRect)adjustedFrame -{ - CGRect frame = self.frame; - RCTShadowView *superview = self; - while ((superview = superview.superview) && superview.layoutOnly) { - const CGRect superviewFrame = superview.frame; - frame.origin.x += superviewFrame.origin.x; - frame.origin.y += superviewFrame.origin.y; - } - - return frame; -} - // Margin #define RCT_MARGIN_PROPERTY(prop, metaProp) \ diff --git a/React/Views/RCTViewNodeProtocol.h b/React/Views/RCTViewNodeProtocol.h index 96eb78f1a..e78cc2ce7 100644 --- a/React/Views/RCTViewNodeProtocol.h +++ b/React/Views/RCTViewNodeProtocol.h @@ -15,11 +15,10 @@ @protocol RCTViewNodeProtocol @property (nonatomic, copy) NSNumber *reactTag; -@property (nonatomic, assign) CGRect frame; - (void)insertReactSubview:(id)subview atIndex:(NSInteger)atIndex; - (void)removeReactSubview:(id)subview; -- (NSArray *)reactSubviews; +- (NSMutableArray *)reactSubviews; - (id)reactSuperview; - (NSNumber *)reactTagAtPoint:(CGPoint)point;