From 3c5b4b0a9f6bcc659ee4eb88dc7fc8765bea203e Mon Sep 17 00:00:00 2001 From: Alex Akers Date: Thu, 25 Jun 2015 09:07:32 -0700 Subject: [PATCH 01/11] [React Native] Remove layout-only nodes Summary: Remove layout-only views. Works by checking properties against a list of known properties that only affect layout. The `RCTShadowView` hierarchy still has a 1:1 correlation with the JS nodes. This works by adjusting the tags and indices in `manageChildren`. For example, if JS told us to insert tag 1 at index 0 and tag 1 is layout-only with children whose tags are 2 and 3, we adjust it so we insert tags 2 and 3 at indices 0 and 1. This keeps changes out of `RCTView` and `RCTScrollView`. In order to simplify this logic, view moves are now processed as view removals followed by additions. A move from index 0 to 1 is recorded as a removal of view at indices 0 and 1 and an insertion of tags 1 and 2 at indices 0 and 1. Of course, the remaining indices have to be offset to take account for this. The `collapsible` attribute is a bit of a hack to force `RCTScrollView` to always have one child. This was easier than rethinking out the logic there, but we could change this later. @public Test Plan: There are tests in `RCTUIManagerTests.m` that test the tag- and index-manipulation logic works. There are various scenarios including add-only, remove-only, and move. In addition, two scenario tests verify that the optimization works by checking the number of views and shadow views after various situations happen. --- .../UIExplorer.xcodeproj/project.pbxproj | 4 + .../testViewExampleSnapshot_1@2x.png | Bin 87728 -> 87728 bytes .../UIExplorerUnitTests/RCTUIManagerTests.m | 374 +++++- .../Test.includeRequire.runModule.bundle | 1120 +++++++++++++++++ .../UIExplorer/UIExplorerUnitTests/Test.js | 13 + Libraries/Components/ScrollView/ScrollView.js | 1 + Libraries/Components/View/View.js | 6 + .../ReactNative/ReactNativeViewAttributes.js | 1 + Libraries/Text/RCTShadowRawText.m | 5 + React/Modules/RCTUIManager.m | 520 ++++++-- React/Views/RCTShadowView.h | 6 + React/Views/RCTShadowView.m | 82 +- React/Views/RCTViewNodeProtocol.h | 3 +- 13 files changed, 2022 insertions(+), 113 deletions(-) create mode 100644 Examples/UIExplorer/UIExplorerUnitTests/Test.includeRequire.runModule.bundle create mode 100644 Examples/UIExplorer/UIExplorerUnitTests/Test.js diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index c266cabcfe1592..ae90506621012f 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ 14DC67F41AB71881001358AB /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14DC67F11AB71876001358AB /* libRCTPushNotification.a */; }; 3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 357859011B28D2C500341EDB /* libRCTLinking.a */; }; 834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; }; + 83686E801B39D26300CBA10B /* Test.includeRequire.runModule.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 83686E7F1B39D26300CBA10B /* Test.includeRequire.runModule.bundle */; }; D85B829E1AB6D5D7003F4FE2 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D85B829C1AB6D5CE003F4FE2 /* libRCTVibration.a */; }; /* End PBXBuildFile section */ @@ -202,6 +203,7 @@ 14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../../Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = ""; }; 357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = ""; }; 58005BE41ABA80530062E044 /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = ../../Libraries/RCTTest/RCTTest.xcodeproj; sourceTree = ""; }; + 83686E7F1B39D26300CBA10B /* Test.includeRequire.runModule.bundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Test.includeRequire.runModule.bundle; sourceTree = ""; }; D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = ../../Libraries/Vibration/RCTVibration.xcodeproj; sourceTree = ""; }; /* End PBXFileReference section */ @@ -351,6 +353,7 @@ 1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */, 143BC57E1B21E18100462512 /* Info.plist */, 14D6D7101B220EB3001FB087 /* libOCMock.a */, + 83686E7F1B39D26300CBA10B /* Test.includeRequire.runModule.bundle */, 14D6D7011B220AE3001FB087 /* OCMock */, 143BC57F1B21E18100462512 /* ReferenceImages */, ); @@ -745,6 +748,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 83686E801B39D26300CBA10B /* Test.includeRequire.runModule.bundle in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExampleSnapshot_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExampleSnapshot_1@2x.png index d37b708794c43ab16e1fd7326207067117212cbc..cf264cbe726f09ea226f77bc6e8027cef106a28e 100644 GIT binary patch delta 18752 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 TPO=VMFw8*L^m64z=ez$4hE+VV delta 18707 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 diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m index 388761a8f939b0..8e848ede46e7fb 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m @@ -2,6 +2,8 @@ #import +#import "RCTRootView.h" +#import "RCTShadowView.h" #import "RCTSparseArray.h" #import "RCTUIManager.h" #import "UIView+React.h" @@ -9,14 +11,36 @@ @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 @@ -39,6 +63,11 @@ - (void)setUp 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; } } @@ -55,8 +84,6 @@ - (void)testManagingChildrenToAddViews // Add views 1-5 to view 20 [_uiManager _manageChildren:@20 - moveFromIndices:nil - moveToIndices:nil addChildReactTags:tagsToAdd addAtIndices:addAtIndices removeAtIndices:nil @@ -89,8 +116,6 @@ - (void)testManagingChildrenToRemoveViews // Remove views 1-5 from view 20 [_uiManager _manageChildren:@20 - moveFromIndices:nil - moveToIndices:nil addChildReactTags:nil addAtIndices:nil removeAtIndices:removeAtIndices @@ -128,11 +153,9 @@ - (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]; + NSArray *removeAtIndices = @[@2, @3, @5, @8, @4, @9]; + NSArray *addAtIndices = @[@0, @6, @1, @7]; + NSArray *tagsToAdd = @[@11, @12, @5, @10]; // We need to keep these in array to keep them around NSMutableArray *viewsToRemove = [NSMutableArray array]; @@ -148,8 +171,6 @@ - (void)testManagingChildrenToAddRemoveAndMove } [_uiManager _manageChildren:@20 - moveFromIndices:moveFromIndices - moveToIndices:moveToIndices addChildReactTags:tagsToAdd addAtIndices:addAtIndices removeAtIndices:removeAtIndices @@ -176,4 +197,331 @@ - (void)testManagingChildrenToAddRemoveAndMove } } +/* +-----------------------------------------------------------+ +----------------------+ + * | 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 +{ + 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]; + }]; +} + +- (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)testScenario1 +{ + RCTUIManager *uiManager = [[RCTUIManager alloc] init]; + NSURL *bundleURL = [[NSBundle bundleForClass:self.class] URLForResource:@"Test.includeRequire.runModule" withExtension:nil]; + RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL moduleProvider:^{ return @[uiManager]; } launchOptions:nil]; + NS_VALID_UNTIL_END_OF_SCOPE RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"Test"]; + + XCTestExpectation *expectation = [self expectationWithDescription:@""]; + + 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); + [expectation fulfill]; + }]; + + [uiManager flushUIBlocks]; + }); + + [self waitForExpectationsWithTimeout:1 handler:nil]; + + expectation = [self expectationWithDescription:@""]; + 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); + [expectation fulfill]; + }]; + + [uiManager flushUIBlocks]; + }); + + [self waitForExpectationsWithTimeout:1 handler:nil]; + + expectation = [self expectationWithDescription:@""]; + 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); + [expectation fulfill]; + }]; + + [uiManager flushUIBlocks]; + }); + + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + +- (void)testScenario2 +{ + RCTUIManager *uiManager = [[RCTUIManager alloc] init]; + NSURL *bundleURL = [[NSBundle bundleForClass:self.class] URLForResource:@"Test.includeRequire.runModule" withExtension:nil]; + RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL moduleProvider:^{ return @[uiManager]; } launchOptions:nil]; + NS_VALID_UNTIL_END_OF_SCOPE RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"Test"]; + + XCTestExpectation *expectation = [self expectationWithDescription:@""]; + + 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); + [expectation fulfill]; + }]; + + [uiManager flushUIBlocks]; + }); + + [self waitForExpectationsWithTimeout:1 handler:nil]; + + expectation = [self expectationWithDescription:@""]; + 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); + [expectation fulfill]; + }]; + + [uiManager flushUIBlocks]; + }); + + [self waitForExpectationsWithTimeout:1 handler:nil]; + + expectation = [self expectationWithDescription:@""]; + 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); + [expectation fulfill]; + }]; + + [uiManager flushUIBlocks]; + }); + + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + @end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/Test.includeRequire.runModule.bundle b/Examples/UIExplorer/UIExplorerUnitTests/Test.includeRequire.runModule.bundle new file mode 100644 index 00000000000000..519e3950ed89c3 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/Test.includeRequire.runModule.bundle @@ -0,0 +1,1120 @@ +__DEV__ = +true; +( +function(global){ + + +if(global.require){ +return;} + + +var __DEV__=global.__DEV__; + +var toString=Object.prototype.toString; + + + + + + + + + + + + + +var modulesMap={}, + + + + + + +dependencyMap={}, + + + +predefinedRefCounts={}, + +_counter=0, + +REQUIRE_WHEN_READY=1, +USED_AS_TRANSPORT=2, + +hop=Object.prototype.hasOwnProperty; + +function _debugUnresolvedDependencies(names){ +var unresolved=Array.prototype.slice.call(names); +var visited={}; +var ii, name, module, dependency; + +while(unresolved.length) { +name = unresolved.shift(); +if(visited[name]){ +continue;} + +visited[name] = true; + +module = modulesMap[name]; +if(!module || !module.waiting){ +continue;} + + +for(ii = 0; ii < module.dependencies.length; ii++) { +dependency = module.dependencies[ii]; +if(!modulesMap[dependency] || modulesMap[dependency].waiting){ +unresolved.push(dependency);}}} + + + + +for(name in visited) if(hop.call(visited, name)){ +unresolved.push(name);} + + +var messages=[]; +for(ii = 0; ii < unresolved.length; ii++) { +name = unresolved[ii]; +var message=name; +module = modulesMap[name]; +if(!module){ +message += ' is not defined';}else +if(!module.waiting){ +message += ' is ready';}else +{ +var unresolvedDependencies=[]; +for(var jj=0; jj < module.dependencies.length; jj++) { +dependency = module.dependencies[jj]; +if(!modulesMap[dependency] || modulesMap[dependency].waiting){ +unresolvedDependencies.push(dependency);}} + + +message += ' is waiting for ' + unresolvedDependencies.join(', ');} + +messages.push(message);} + +return messages.join('\n');} + + + + + +function ModuleError(msg){ +this.name = 'ModuleError'; +this.message = msg; +this.stack = Error(msg).stack; +this.framesToPop = 2;} + +ModuleError.prototype = Object.create(Error.prototype); +ModuleError.prototype.constructor = ModuleError; + +var _performance= +global.performance || +global.msPerformance || +global.webkitPerformance || {}; + +if(!_performance.now){ +_performance = global.Date;} + + +var _now=_performance? +_performance.now.bind(_performance):function(){return 0;}; + +var _factoryStackCount=0; +var _factoryTime=0; +var _totalFactories=0; +var _inGuard=false; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +function require(id){ +var module=modulesMap[id], dep, i, msg; +if(module && module.exports){ + + +if(module.refcount-- === 1){ +delete modulesMap[id];} + +return module.exports;} + +if(global.ErrorUtils && !_inGuard){ +_inGuard = true; +try{ +var ret=require.apply(this, arguments);} +catch(e) { +global.ErrorUtils.reportFatalError(e);} + +_inGuard = false; +return ret;} + + +if(!module){ +msg = 'Requiring unknown module "' + id + '"'; +if(__DEV__){ +msg += '. If you are sure the module is there, try restarting the packager.';} + +throw new ModuleError(msg);} + + +if(module.hasError){ +throw new ModuleError( +'Requiring module "' + id + '" which threw an exception');} + + + +if(module.waiting){ +throw new ModuleError( +'Requiring module "' + id + '" with unresolved dependencies: ' + +_debugUnresolvedDependencies([id]));} + + + +var exports=module.exports = {}; +var factory=module.factory; +if(toString.call(factory) === '[object Function]'){ +var args=[], +dependencies=module.dependencies, +length=dependencies.length, +ret; +if(module.special & USED_AS_TRANSPORT){ +length = Math.min(length, factory.length);} + +try{ +for(i = 0; args.length < length; i++) { +dep = dependencies[i]; +if(!module.inlineRequires[dep]){ +args.push(dep === 'module'?module: +dep === 'exports'?exports: +require.call(null, dep));}} + + + +++_totalFactories; +if(_factoryStackCount++ === 0){ +_factoryTime -= _now();} + +try{ +ret = factory.apply(module.context || global, args);} +catch(e) { +if(modulesMap.ex && modulesMap.erx){ + + +var ex=require.call(null, 'ex'); +var erx=require.call(null, 'erx'); +var messageWithParams=erx(e.message); +if(messageWithParams[0].indexOf(' from module "%s"') < 0){ +messageWithParams[0] += ' from module "%s"'; +messageWithParams[messageWithParams.length] = id;} + +e.message = ex.apply(null, messageWithParams);} + +throw e;}finally +{ +if(--_factoryStackCount === 0){ +_factoryTime += _now();}}} + + +catch(e) { +module.hasError = true; +module.exports = null; +throw e;} + +if(ret){ +if(__DEV__){ +if(typeof ret != 'object' && typeof ret != 'function'){ +throw new ModuleError( +'Factory for module "' + id + '" returned ' + +'an invalid value "' + ret + '". ' + +'Returned value should be either a function or an object.');}} + + + +module.exports = ret;}}else + +{ +module.exports = factory;} + + + + +if(module.refcount-- === 1){ +delete modulesMap[id];} + +return module.exports;} + + +require.__getFactoryTime = function(){ +return (_factoryStackCount?_now():0) + _factoryTime;}; + + +require.__getTotalFactories = function(){ +return _totalFactories;}; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +function define(id, dependencies, factory, +_special, _context, _refCount, _inlineRequires){ +if(dependencies === undefined){ +dependencies = []; +factory = id; +id = _uid();}else +if(factory === undefined){ +factory = dependencies; +if(toString.call(id) === '[object Array]'){ +dependencies = id; +id = _uid();}else +{ +dependencies = [];}} + + + + + +var canceler={cancel:_undefine.bind(this, id)}; + +var record=modulesMap[id]; + + + + + + +if(record){ +if(_refCount){ +record.refcount += _refCount;} + + +return canceler;}else +if(!dependencies && !factory && _refCount){ + + +predefinedRefCounts[id] = (predefinedRefCounts[id] || 0) + _refCount; +return canceler;}else +{ + +record = {id:id}; +record.refcount = (predefinedRefCounts[id] || 0) + (_refCount || 0); +delete predefinedRefCounts[id];} + + +if(__DEV__){ +if( +!factory || +typeof factory != 'object' && typeof factory != 'function' && +typeof factory != 'string'){ +throw new ModuleError( +'Invalid factory "' + factory + '" for module "' + id + '". ' + +'Factory should be either a function or an object.');} + + + +if(toString.call(dependencies) !== '[object Array]'){ +throw new ModuleError( +'Invalid dependencies for module "' + id + '". ' + +'Dependencies must be passed as an array.');}} + + + + +record.factory = factory; +record.dependencies = dependencies; +record.context = _context; +record.special = _special; +record.inlineRequires = _inlineRequires || {}; +record.waitingMap = {}; +record.waiting = 0; +record.hasError = false; +modulesMap[id] = record; +_initDependencies(id); + +return canceler;} + + +function _undefine(id){ +if(!modulesMap[id]){ +return;} + + +var module=modulesMap[id]; +delete modulesMap[id]; + +for(var dep in module.waitingMap) { +if(module.waitingMap[dep]){ +delete dependencyMap[dep][id];}} + + + +for(var ii=0; ii < module.dependencies.length; ii++) { +dep = module.dependencies[ii]; +if(modulesMap[dep]){ +if(modulesMap[dep].refcount-- === 1){ +_undefine(dep);}}else + +if(predefinedRefCounts[dep]){ +predefinedRefCounts[dep]--;}}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +function requireLazy(dependencies, factory, context){ +return define( +dependencies, +factory, +undefined, +REQUIRE_WHEN_READY, +context, +1);} + + + +function _uid(){ +return '__mod__' + _counter++;} + + +function _addDependency(module, dep){ + +if(!module.waitingMap[dep] && module.id !== dep){ +module.waiting++; +module.waitingMap[dep] = 1; +dependencyMap[dep] || (dependencyMap[dep] = {}); +dependencyMap[dep][module.id] = 1;}} + + + +function _initDependencies(id){ +var modulesToRequire=[]; +var module=modulesMap[id]; +var dep, i, subdep; + + +for(i = 0; i < module.dependencies.length; i++) { +dep = module.dependencies[i]; +if(!modulesMap[dep]){ +_addDependency(module, dep);}else +if(modulesMap[dep].waiting){ +for(subdep in modulesMap[dep].waitingMap) { +if(modulesMap[dep].waitingMap[subdep]){ +_addDependency(module, subdep);}}}} + + + + +if(module.waiting === 0 && module.special & REQUIRE_WHEN_READY){ +modulesToRequire.push(id);} + + + +if(dependencyMap[id]){ +var deps=dependencyMap[id]; +var submodule; +dependencyMap[id] = undefined; +for(dep in deps) { +submodule = modulesMap[dep]; + + +for(subdep in module.waitingMap) { +if(module.waitingMap[subdep]){ +_addDependency(submodule, subdep);}} + + + +if(submodule.waitingMap[id]){ +submodule.waitingMap[id] = undefined; +submodule.waiting--;} + +if(submodule.waiting === 0 && +submodule.special & REQUIRE_WHEN_READY){ +modulesToRequire.push(dep);}}} + + + + + +for(i = 0; i < modulesToRequire.length; i++) { +require.call(null, modulesToRequire[i]);}} + + + +function _register(id, exports){ +var module=modulesMap[id] = {id:id}; +module.exports = exports; +module.refcount = 0;} + + + + +_register('module', 0); +_register('exports', 0); + +_register('global', global); +_register('require', require); +_register('requireDynamic', require); +_register('requireLazy', requireLazy); + +global.require = require; +global.requireDynamic = require; +global.requireLazy = requireLazy; + +require.__debug = { +modules:modulesMap, +deps:dependencyMap, +printDependencyInfo:function(){ +if(!global.console){ +return;} + +var names=Object.keys(require.__debug.deps); +global.console.log(_debugUnresolvedDependencies(names));}}; + + + + + + + + + +global.__d = function(id, deps, factory, _special, _inlineRequires){ +var defaultDeps=['global', 'require', 'requireDynamic', 'requireLazy', +'module', 'exports']; +define(id, defaultDeps.concat(deps), factory, _special || USED_AS_TRANSPORT, +null, null, _inlineRequires);};})( + + +this); +Object. + + + + + + + + + + + + + + + + + +assign = function(target, sources){ +if(__DEV__){ +if(target == null){ +throw new TypeError('Object.assign target cannot be null or undefined');} + +if(typeof target !== 'object' && typeof target !== 'function'){ +throw new TypeError( +'In this environment the target of assign MUST be an object.' + +'This error is a performance optimization and not spec compliant.');}} + + + + +for(var nextIndex=1; nextIndex < arguments.length; nextIndex++) { +var nextSource=arguments[nextIndex]; +if(nextSource == null){ +continue;} + + +if(__DEV__){ +if(typeof nextSource !== 'object' && +typeof nextSource !== 'function'){ +throw new TypeError( +'In this environment the target of assign MUST be an object.' + +'This error is a performance optimization and not spec compliant.');}} + + + + + + + + +for(var key in nextSource) { +if(__DEV__){ +var hasOwnProperty=Object.prototype.hasOwnProperty; +if(!hasOwnProperty.call(nextSource, key)){ +throw new TypeError( +'One of the sources to assign has an enumerable key on the ' + +'prototype chain. This is an edge case that we do not support. ' + +'This error is a performance optimization and not spec compliant.');}} + + + +target[key] = nextSource[key];}} + + + +return target;}; +( + + + + + + + + + + + + + + + +function(global){ +'use strict'; + +var OBJECT_COLUMN_NAME='(index)'; +var LOG_LEVELS={ +trace:0, +log:1, +info:2, +warn:3, +error:4}; + + +function setupConsole(global){ + +if(!global.nativeLoggingHook){ +return;} + + +function getNativeLogFunction(level){ +return function(){ +var str=Array.prototype.map.call(arguments, function(arg){ +var ret; +var type=typeof arg; +if(arg === null){ +ret = 'null';}else +if(arg === undefined){ +ret = 'undefined';}else +if(type === 'string'){ +ret = '"' + arg + '"';}else +if(type === 'function'){ +try{ +ret = arg.toString();} +catch(e) { +ret = '[function unknown]';}}else + +{ + + +try{ +ret = JSON.stringify(arg);} +catch(e) { +if(typeof arg.toString === 'function'){ +try{ +ret = arg.toString();} +catch(E) {}}}} + + + +return ret || '["' + type + '" failed to stringify]';}). +join(', '); +global.nativeLoggingHook(str, level);};} + + + +var repeat=function(element, n){ +return Array.apply(null, Array(n)).map(function(){return element;});}; + + +function consoleTablePolyfill(rows){ + +if(!Array.isArray(rows)){ +var data=rows; +rows = []; +for(var key in data) { +if(data.hasOwnProperty(key)){ +var row=data[key]; +row[OBJECT_COLUMN_NAME] = key; +rows.push(row);}}} + + + +if(rows.length === 0){ +global.nativeLoggingHook('', LOG_LEVELS.log); +return;} + + +var columns=Object.keys(rows[0]).sort(); +var stringRows=[]; +var columnWidths=[]; + + + +columns.forEach(function(k, i){ +columnWidths[i] = k.length; +for(var j=0; j < rows.length; j++) { +var cellStr=rows[j][k].toString(); +stringRows[j] = stringRows[j] || []; +stringRows[j][i] = cellStr; +columnWidths[i] = Math.max(columnWidths[i], cellStr.length);}}); + + + + + +var joinRow=function(row, space){ +var cells=row.map(function(cell, i){ +var extraSpaces=repeat(' ', columnWidths[i] - cell.length).join(''); +return cell + extraSpaces;}); + +space = space || ' '; +return cells.join(space + '|' + space);}; + + +var separators=columnWidths.map(function(columnWidth){ +return repeat('-', columnWidth).join('');}); + +var separatorRow=joinRow(separators, '-'); +var header=joinRow(columns); +var table=[header, separatorRow]; + +for(var i=0; i < rows.length; i++) { +table.push(joinRow(stringRows[i]));} + + + + + + +global.nativeLoggingHook('\n' + table.join('\n'), LOG_LEVELS.log);} + + +global.console = { +error:getNativeLogFunction(LOG_LEVELS.error), +info:getNativeLogFunction(LOG_LEVELS.info), +log:getNativeLogFunction(LOG_LEVELS.log), +warn:getNativeLogFunction(LOG_LEVELS.warn), +trace:getNativeLogFunction(LOG_LEVELS.trace), +table:consoleTablePolyfill};} + + + + +if(typeof module !== 'undefined'){ +module.exports = setupConsole;}else +{ +setupConsole(global);}})( + + +this); +( + + + + + + + + + + + + + + + +function(global){ +var ErrorUtils={ +_inGuard:0, +_globalHandler:null, +setGlobalHandler:function(fun){ +ErrorUtils._globalHandler = fun;}, + +reportError:function(error){ +ErrorUtils._globalHandler && ErrorUtils._globalHandler(error);}, + +reportFatalError:function(error){ +ErrorUtils._globalHandler && ErrorUtils._globalHandler(error, true);}, + +applyWithGuard:function(fun, context, args){ +try{ +ErrorUtils._inGuard++; +return fun.apply(context, args);} +catch(e) { +ErrorUtils.reportError(e);}finally +{ +ErrorUtils._inGuard--;}}, + + +applyWithGuardIfNeeded:function(fun, context, args){ +if(ErrorUtils.inGuard()){ +return fun.apply(context, args);}else +{ +ErrorUtils.applyWithGuard(fun, context, args);}}, + + +inGuard:function(){ +return ErrorUtils._inGuard;}, + +guard:function(fun, name, context){ +if(typeof fun !== 'function'){ +console.warn('A function must be passed to ErrorUtils.guard, got ', fun); +return null;} + +name = name || fun.name || ''; +function guarded(){ +return ( +ErrorUtils.applyWithGuard( +fun, +context || this, +arguments, +null, +name));} + + + + +return guarded;}}; + + +global.ErrorUtils = ErrorUtils; + + + + + +function setupErrorGuard(){ +var onError=function(e){ +global.console.error( +'Error: ' + +'\n stack: ' + e.stack + +'\n line: ' + e.line + +'\n message: ' + e.message, +e);}; + + +global.ErrorUtils.setGlobalHandler(onError);} + + +setupErrorGuard();})( +this); +if( + + + + + + + + + + + +!String.prototype.startsWith){ +String.prototype.startsWith = function(search){ +'use strict'; +if(this == null){ +throw TypeError();} + +var string=String(this); +var pos=arguments.length > 1? +Number(arguments[1]) || 0:0; +var start=Math.min(Math.max(pos, 0), string.length); +return string.indexOf(String(search), pos) === start;};} + + + +if(!String.prototype.endsWith){ +String.prototype.endsWith = function(search){ +'use strict'; +if(this == null){ +throw TypeError();} + +var string=String(this); +var stringLength=string.length; +var searchString=String(search); +var pos=arguments.length > 1? +Number(arguments[1]) || 0:stringLength; +var end=Math.min(Math.max(pos, 0), stringLength); +var start=end - searchString.length; +if(start < 0){ +return false;} + +return string.lastIndexOf(searchString, start) === start;};} + + + +if(!String.prototype.contains){ +String.prototype.contains = function(search){ +'use strict'; +if(this == null){ +throw TypeError();} + +var string=String(this); +var pos=arguments.length > 1? +Number(arguments[1]) || 0:0; +return string.indexOf(String(search), pos) !== -1;};} + + + +if(!String.prototype.repeat){ +String.prototype.repeat = function(count){ +'use strict'; +if(this == null){ +throw TypeError();} + +var string=String(this); +count = Number(count) || 0; +if(count < 0 || count === Infinity){ +throw RangeError();} + +if(count === 1){ +return string;} + +var result=''; +while(count) { +if(count & 1){ +result += string;} + +if(count >>= 1){ +string += string;}} + + +return result;};} +( + + + + + + + + + +function(undefined){ + +function findIndex(predicate, context){ +if(this == null){ +throw new TypeError( +'Array.prototype.findIndex called on null or undefined');} + + +if(typeof predicate !== 'function'){ +throw new TypeError('predicate must be a function');} + +var list=Object(this); +var length=list.length >>> 0; +for(var i=0; i < length; i++) { +if(predicate.call(context, list[i], i, list)){ +return i;}} + + +return -1;} + + +if(!Array.prototype.findIndex){ +Object.defineProperty(Array.prototype, 'findIndex', { +enumerable:false, +writable:true, +configurable:true, +value:findIndex});} + + + + +if(!Array.prototype.find){ +Object.defineProperty(Array.prototype, 'find', { +enumerable:false, +writable:true, +configurable:true, +value:function(predicate, context){ +if(this == null){ +throw new TypeError( +'Array.prototype.find called on null or undefined');} + + +var index=findIndex.call(this, predicate, context); +return index === -1?undefined:this[index];}});}})(); +( +function(GLOBAL){ + + + + + + + +function getInvalidGlobalUseError(name){ +return new Error( +'You are trying to render the global ' + name + ' variable as a ' + +'React element. You probably forgot to require ' + name + '.');} + + +GLOBAL.Text = { +get defaultProps() { +throw getInvalidGlobalUseError('Text');}}; + + +GLOBAL.Image = { +get defaultProps() { +throw getInvalidGlobalUseError('Image');}}; + + + +if(GLOBAL.document){ +GLOBAL.document.createElement = null;} + + + + +GLOBAL.MutationObserver = undefined;})( +this); +__d('react-native/Examples/UIExplorer/UIExplorerUnitTests/Test.js',[],function(global, require, requireDynamic, requireLazy, module, exports) { +}); +;require("react-native/Examples/UIExplorer/UIExplorerUnitTests/Test.js"); +//@ sourceMappingURL=/Examples/UIExplorer/UIExplorerUnitTests/Test.includeRequire.runModule.map \ No newline at end of file diff --git a/Examples/UIExplorer/UIExplorerUnitTests/Test.js b/Examples/UIExplorer/UIExplorerUnitTests/Test.js new file mode 100644 index 00000000000000..3f0a21ff35b0b0 --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/Test.js @@ -0,0 +1,13 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 512d8dbd6a4192..f01cce58a44781 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -279,6 +279,7 @@ var ScrollView = React.createClass({ var contentContainer = diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index c6a279a22b8820..0cb6e4a4f3e6c5 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -77,6 +77,12 @@ 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 50b839e1db7441..0de78cb8f73eeb 100644 --- a/Libraries/ReactNative/ReactNativeViewAttributes.js +++ b/Libraries/ReactNative/ReactNativeViewAttributes.js @@ -24,6 +24,7 @@ ReactNativeViewAttributes.UIView = { onLayout: true, onAccessibilityTap: true, onMagicTap: true, + collapsible: true, }; ReactNativeViewAttributes.RCTView = merge( diff --git a/Libraries/Text/RCTShadowRawText.m b/Libraries/Text/RCTShadowRawText.m index e99e1187b345c6..00a3490bcf283b 100644 --- a/Libraries/Text/RCTShadowRawText.m +++ b/Libraries/Text/RCTShadowRawText.m @@ -20,6 +20,11 @@ - (void)setText:(NSString *)text } } +- (BOOL)isLayoutOnly +{ + return YES; +} + - (NSString *)description { NSString *superDescription = super.description; diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index c6855fbf0dd4fd..a69b8e7ddc4053 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -33,15 +33,8 @@ #import "RCTViewNodeProtocol.h" #import "UIView+React.h" -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); - } -} +static void RCTTraverseViewNodes(id view, void (^block)(id)); +static NSDictionary *RCTPropsMerge(NSDictionary *beforeProps, NSDictionary *newProps); @interface RCTAnimation : NSObject @@ -464,6 +457,23 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo [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 implementated. + + 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]; @@ -472,26 +482,30 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo NSMutableArray *onLayoutEvents = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count]; for (RCTShadowView *shadowView in viewsWithNewFrames) { - [frameReactTags addObject:shadowView.reactTag]; - [frames addObject:[NSValue valueWithCGRect:shadowView.frame]]; + CGRect frame = shadowView.adjustedFrame; + NSNumber *reactTag = shadowView.reactTag; + [frameReactTags addObject:reactTag]; + [frames addObject:[NSValue valueWithCGRect:frame]]; [areNew addObject:@(shadowView.isNewView)]; - [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), - }, - }; + + RCTShadowView *superview = shadowView; + BOOL parentIsNew = NO; + while (YES) { + superview = superview.superview; + parentIsNew = superview.isNewView; + if (!superview.layoutOnly) { + break; + } } + [parentsAreNew addObject:@(parentIsNew)]; + + id event = shadowView.hasOnLayout + ? RCTShadowViewOnLayoutEventPayload(shadowView.reactTag, frame) + : (id)kCFNull; [onLayoutEvents addObject:event]; } - for (RCTShadowView *shadowView in viewsWithNewFrames) { + for (RCTShadowView *shadowView in originalViewsWithNewFrames) { // We have to do this after we build the parentsAreNew array. shadowView.newView = NO; } @@ -508,24 +522,28 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo } // Perform layout (possibly animated) - return ^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { - RCTResponseSenderBlock callback = self->_layoutAnimation.callback; + return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { + RCTResponseSenderBlock callback = uiManager->_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 : _layoutAnimation.updateAnimation; + RCTAnimation *updateAnimation = isNew ? nil : uiManager->_layoutAnimation.updateAnimation; BOOL shouldAnimateCreation = isNew && ![parentsAreNew[ii] boolValue]; - RCTAnimation *createAnimation = shouldAnimateCreation ? _layoutAnimation.createAnimation : nil; + RCTAnimation *createAnimation = shouldAnimateCreation ? uiManager->_layoutAnimation.createAnimation : nil; void (^completion)(BOOL) = ^(BOOL finished) { completionsCalled++; if (event != (id)kCFNull) { - [self.bridge.eventDispatcher sendInputEventWithName:@"topLayout" body:event]; + [uiManager.bridge.eventDispatcher sendInputEventWithName:@"topLayout" body:event]; } if (callback && completionsCalled == frames.count - 1) { callback(@[@(finished)]); @@ -537,13 +555,13 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo [updateAnimation performAnimations:^{ [view reactSetFrame:frame]; for (RCTViewManagerUIBlock block in updateBlocks) { - block(self, _viewRegistry); + block(uiManager, viewRegistry); } } withCompletionBlock:completion]; } else { [view reactSetFrame:frame]; for (RCTViewManagerUIBlock block in updateBlocks) { - block(self, _viewRegistry); + block(uiManager, viewRegistry); } completion(YES); } @@ -565,7 +583,7 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo createAnimation.property); } for (RCTViewManagerUIBlock block in updateBlocks) { - block(self, _viewRegistry); + block(uiManager, viewRegistry); } } withCompletionBlock:nil]; } @@ -688,6 +706,126 @@ - (void)_removeChildren:(NSArray *)children fromContainer:(id 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:(out NSUInteger *)outOffset +{ + RCTShadowView *container = _shadowViewRegistry[containerReactTag]; + NSNumber *containerSuperviewReactTag = containerReactTag; + RCTShadowView *superview = container; + NSUInteger offset = 0; + + 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) { + 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; + } + + if (outOffset) *outOffset = offset; + return containerSuperviewReactTag; +} + RCT_EXPORT_METHOD(manageChildren:(NSNumber *)containerReactTag moveFromIndices:(NSArray *)moveFromIndices moveToIndices:(NSArray *)moveToIndices @@ -695,62 +833,109 @@ - (void)_removeChildren:(NSArray *)children fromContainer:(id 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]; - [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ - [uiManager _manageChildren:containerReactTag - moveFromIndices:moveFromIndices - moveToIndices:moveToIndices - addChildReactTags:addChildReactTags - addAtIndices:addAtIndices - removeAtIndices:removeAtIndices - registry:viewRegistry]; - }]; + 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]; + }]; + } } - (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(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"); + RCTAssert(addChildReactTags.count == addAtIndices.count, @"Invalid arguments: addChildReactTags.count == addAtIndices.count"); - // 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]; + // Removes are using "before" indices + NSArray *removedChildren = [self _childrenToRemoveFromContainer:container atIndices:removeAtIndices]; + [self _removeChildren:removedChildren 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 - 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]]; + // Figure out what to insert + NSMutableDictionary *childrenToAdd = [NSMutableDictionary dictionary]; + for (NSInteger index = 0, count = addAtIndices.count; index < count; index++) { + id view = registry[addChildReactTags[index]]; if (view) { - destinationsToChildrenToAdd[addAtIndices[index]] = view; + childrenToAdd[addAtIndices[index]] = view; } } - NSArray *sortedIndices = [[destinationsToChildrenToAdd allKeys] sortedArrayUsingSelector:@selector(compare:)]; + NSArray *sortedIndices = [[childrenToAdd allKeys] sortedArrayUsingSelector:@selector(compare:)]; for (NSNumber *reactIndex in sortedIndices) { - [container insertReactSubview:destinationsToChildrenToAdd[reactIndex] atIndex:reactIndex.integerValue]; + [container insertReactSubview:childrenToAdd[reactIndex] atIndex:reactIndex.integerValue]; } } @@ -833,45 +1018,72 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView // Set properties shadowView.viewName = viewName; shadowView.reactTag = reactTag; + shadowView.allProps = props; RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[viewName], manager); } _shadowViewRegistry[reactTag] = shadowView; - // 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; + 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]; + }]; + } +} - [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ - RCTAssertMainThread(); +- (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; + } - UIView *view = [manager view]; - if (view) { + // 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]; + } - // 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]; - } + // 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); - // 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); + if ([view respondsToSelector:@selector(reactBridgeDidFinishTransaction)]) { + [_bridgeTransactionListeners addObject:view]; + } + _viewRegistry[reactTag] = view; - if ([view respondsToSelector:@selector(reactBridgeDidFinishTransaction)]) { - [uiManager->_bridgeTransactionListeners addObject:view]; - } - } - viewRegistry[reactTag] = view; - }]; + 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), + }, + }; } // TODO: remove viewName param as it isn't needed @@ -885,10 +1097,100 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView RCTShadowView *shadowView = _shadowViewRegistry[reactTag]; RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[viewName], viewManager); - [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { - UIView *view = viewRegistry[reactTag]; - RCTSetViewProps(props, view, uiManager->_defaultViews[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; + 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); + }]; + } } RCT_EXPORT_METHOD(focus:(NSNumber *)reactTag) @@ -1227,7 +1529,7 @@ static void RCTMeasureLayout(RCTShadowView *view, [self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { _jsResponder = viewRegistry[reactTag]; if (!_jsResponder) { - RCTLogError(@"Invalid view set to be the JS responder - tag %zd", reactTag); + RCTLogError(@"Invalid view set to be the JS responder - tag %@", reactTag); } }]; } @@ -1485,3 +1787,27 @@ - (RCTUIManager *)uiManager } @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 1c44033f6df59f..38edc6e502d7cf 100644 --- a/React/Views/RCTShadowView.h +++ b/React/Views/RCTShadowView.h @@ -41,6 +41,12 @@ 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 9d56bb90624a8f..a6c49549870811 100644 --- a/React/Views/RCTShadowView.m +++ b/React/Views/RCTShadowView.m @@ -367,8 +367,10 @@ - (NSNumber *)reactTagAtPoint:(CGPoint)point - (NSString *)description { NSString *description = super.description; - description = [[description substringToIndex:description.length - 1] stringByAppendingFormat:@"; viewName: %@; reactTag: %@; frame: %@>", self.viewName, self.reactTag, NSStringFromCGRect(self.frame)]; - return description; + if (self.layoutOnly) { + description = [@"* " stringByAppendingString:description]; + } + return [[description substringToIndex:description.length - 1] stringByAppendingFormat:@"; viewName: %@; reactTag: %@; frame: %@>", self.viewName, self.reactTag, NSStringFromCGRect(self.frame)]; } - (void)addRecursiveDescriptionToString:(NSMutableString *)string atLevel:(NSUInteger)level @@ -392,6 +394,82 @@ - (NSString *)recursiveDescription 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; + if (!layoutKeys) { + // Taken from LayoutPropTypes.js with the exception that borderWidth, + // borderTopWidth, borderBottomWidth, borderLeftWidth, and borderRightWidth + // were removed because black color is assumed + static NSString *const keys[] = { + @"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", + + // Special case is handled below. + @"collapsible", + }; + layoutKeys = [NSSet setWithObjects:keys count:sizeof(keys)/sizeof(*keys)]; + } + + NSNumber *collapsible = self.allProps[@"collapsible"]; + if (collapsible && !collapsible.boolValue) { + return NO; + } + + for (NSString *key in self.allProps) { + if (![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 e78cc2ce7b26fc..96eb78f1a77e90 100644 --- a/React/Views/RCTViewNodeProtocol.h +++ b/React/Views/RCTViewNodeProtocol.h @@ -15,10 +15,11 @@ @protocol RCTViewNodeProtocol @property (nonatomic, copy) NSNumber *reactTag; +@property (nonatomic, assign) CGRect frame; - (void)insertReactSubview:(id)subview atIndex:(NSInteger)atIndex; - (void)removeReactSubview:(id)subview; -- (NSMutableArray *)reactSubviews; +- (NSArray *)reactSubviews; - (id)reactSuperview; - (NSNumber *)reactTagAtPoint:(CGPoint)point; From 99bc08cf615341853c46a439210c2c0d2a492702 Mon Sep 17 00:00:00 2001 From: David Mohl Date: Thu, 25 Jun 2015 09:07:19 -0700 Subject: [PATCH 02/11] [MapView] Support for annotation callouts, annotation press, callout presses and pin animation Summary: Started from here - https://github.com/facebook/react-native/issues/1120. Most functionality for annotations were missing so I started implementing and somehow got caught up until the entire thing was done. ![screen shot 2015-05-12 at 10 07 43 pm](https://cloud.githubusercontent.com/assets/688326/7588677/8479a7a4-f8f9-11e4-99a4-1dc3c7691810.png) 2 new events: - callout presses (left / right) - annotation presses 6 new properties for annotations: - hasLeftCallout - hasRightCallout - onLeftCalloutPress - onRightCalloutPress - animateDrop - id 1 new property for MapView - onAnnotationPress --- Now the important thing is, that I implemented all of this the way "I would do it". I am not sure this is the 'reacty' way so please let me know my mistakes :smile: The problem is that there is no real way to identify annotations which makes it difficult to distinguish which one got clicked. The idea is to pass a `id` and whether it has callouts the entire way with the annotation. I had to Closes https://github.com/facebook/react-native/pull/1247 Github Author: David Mohl Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Libraries/Components/MapView/MapView.js | 90 +++++++++++++++++++++++-- React/Modules/RCTPointAnnotation.h | 19 ++++++ React/Modules/RCTPointAnnotation.m | 14 ++++ React/React.xcodeproj/project.pbxproj | 6 ++ React/Views/RCTConvert+CoreLocation.h | 15 +++-- React/Views/RCTConvert+CoreLocation.m | 15 +++-- React/Views/RCTConvert+MapKit.h | 20 ++++-- React/Views/RCTConvert+MapKit.m | 33 ++++++--- React/Views/RCTMap.h | 3 +- React/Views/RCTMap.m | 48 +++++++++++-- React/Views/RCTMapManager.m | 74 +++++++++++++++++++- React/Views/RCTSegmentedControl.h | 15 +++-- React/Views/RCTSegmentedControl.m | 15 +++-- 13 files changed, 313 insertions(+), 54 deletions(-) create mode 100644 React/Modules/RCTPointAnnotation.h create mode 100644 React/Modules/RCTPointAnnotation.m diff --git a/Libraries/Components/MapView/MapView.js b/Libraries/Components/MapView/MapView.js index 50e36954bd00a0..ea10b959e82ad9 100644 --- a/Libraries/Components/MapView/MapView.js +++ b/Libraries/Components/MapView/MapView.js @@ -35,6 +35,34 @@ type MapRegion = { var MapView = React.createClass({ mixins: [NativeMethodsMixin], + checkAnnotationIds: function (annotations: Array) { + + var newAnnotations = annotations.map(function (annotation) { + if (!annotation.id) { + // TODO: add a base64 (or similar) encoder here + annotation.id = encodeURIComponent(JSON.stringify(annotation)); + } + + return annotation; + }); + + this.setState({ + annotations: newAnnotations + }); + }, + + componentWillMount: function() { + if (this.props.annotations) { + this.checkAnnotationIds(this.props.annotations); + } + }, + + componentWillReceiveProps: function(nextProps: Object) { + if (nextProps.annotations) { + this.checkAnnotationIds(nextProps.annotations); + } + }, + propTypes: { /** * Used to style and layout the `MapView`. See `StyleSheet.js` and @@ -84,14 +112,14 @@ var MapView = React.createClass({ /** * The map type to be displayed. - * + * * - standard: standard road map (default) * - satellite: satellite view * - hybrid: satellite view with roads and points of interest overlayed */ mapType: React.PropTypes.oneOf([ - 'standard', - 'satellite', + 'standard', + 'satellite', 'hybrid', ]), @@ -126,11 +154,34 @@ var MapView = React.createClass({ latitude: React.PropTypes.number.isRequired, longitude: React.PropTypes.number.isRequired, + /** + * Whether the pin drop should be animated or not + */ + animateDrop: React.PropTypes.bool, + /** * Annotation title/subtile. */ title: React.PropTypes.string, subtitle: React.PropTypes.string, + + /** + * Whether the Annotation has callout buttons. + */ + hasLeftCallout: React.PropTypes.bool, + hasRightCallout: React.PropTypes.bool, + + /** + * Event handlers for callout buttons. + */ + onLeftCalloutPress: React.PropTypes.func, + onRightCalloutPress: React.PropTypes.func, + + /** + * annotation id + */ + id: React.PropTypes.string + })), /** @@ -158,6 +209,11 @@ var MapView = React.createClass({ * Callback that is called once, when the user is done moving the map. */ onRegionChangeComplete: React.PropTypes.func, + + /** + * Callback that is called once, when the user is clicked on a annotation. + */ + onAnnotationPress: React.PropTypes.func, }, _onChange: function(event: Event) { @@ -170,8 +226,34 @@ var MapView = React.createClass({ } }, + _onPress: function(event: Event) { + if (event.nativeEvent.action === 'annotation-click') { + this.props.onAnnotationPress && this.props.onAnnotationPress(event.nativeEvent.annotation); + } + + if (event.nativeEvent.action === 'callout-click') { + if (!this.props.annotations) { + return; + } + + // Find the annotation with the id of what has been pressed + for (var i = 0; i < this.props.annotations.length; i++) { + var annotation = this.props.annotations[i]; + if (annotation.id === event.nativeEvent.annotationId) { + // Pass the right function + if (event.nativeEvent.side === 'left') { + annotation.onLeftCalloutPress && annotation.onLeftCalloutPress(event.nativeEvent); + } else if (event.nativeEvent.side === 'right') { + annotation.onRightCalloutPress && annotation.onRightCalloutPress(event.nativeEvent); + } + } + } + + } + }, + render: function() { - return ; + return ; }, }); diff --git a/React/Modules/RCTPointAnnotation.h b/React/Modules/RCTPointAnnotation.h new file mode 100644 index 00000000000000..0646608d4805bc --- /dev/null +++ b/React/Modules/RCTPointAnnotation.h @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@interface RCTPointAnnotation : MKPointAnnotation + +@property (nonatomic, copy) NSString *identifier; +@property (nonatomic, assign) BOOL hasLeftCallout; +@property (nonatomic, assign) BOOL hasRightCallout; +@property (nonatomic, assign) BOOL animateDrop; + +@end diff --git a/React/Modules/RCTPointAnnotation.m b/React/Modules/RCTPointAnnotation.m new file mode 100644 index 00000000000000..aaaf2d7e20a254 --- /dev/null +++ b/React/Modules/RCTPointAnnotation.m @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTPointAnnotation.h" + +@implementation RCTPointAnnotation + +@end diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 51f02e7b2204e9..099c120a03a4fc 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -64,6 +64,7 @@ 58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A151AAE854800E7D092 /* RCTPickerManager.m */; }; 58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */; }; 58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */; }; + 63F014C01B02080B003B75D2 /* RCTPointAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 63F014BF1B02080B003B75D2 /* RCTPointAnnotation.m */; }; 830A229E1A66C68A008503DA /* RCTRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = 830A229D1A66C68A008503DA /* RCTRootView.m */; }; 830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 830BA4541A8E3BDA00D53203 /* RCTCache.m */; }; 832348161A77A5AA00B55238 /* Layout.c in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FC71A68125100A75B9A /* Layout.c */; }; @@ -213,6 +214,8 @@ 58114A4F1AAE93D500E7D092 /* RCTAsyncLocalStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAsyncLocalStorage.h; sourceTree = ""; }; 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDatePickerManager.m; sourceTree = ""; }; 58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDatePickerManager.h; sourceTree = ""; }; + 63F014BE1B02080B003B75D2 /* RCTPointAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPointAnnotation.h; sourceTree = ""; }; + 63F014BF1B02080B003B75D2 /* RCTPointAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPointAnnotation.m; sourceTree = ""; }; 830213F31A654E0800B993E6 /* RCTBridgeModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeModule.h; sourceTree = ""; }; 830A229C1A66C68A008503DA /* RCTRootView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootView.h; sourceTree = ""; }; 830A229D1A66C68A008503DA /* RCTRootView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootView.m; sourceTree = ""; }; @@ -291,6 +294,8 @@ 13B07FEE1A69327A00A75B9A /* RCTTiming.m */, 13E067481A70F434002CDEE1 /* RCTUIManager.h */, 13E067491A70F434002CDEE1 /* RCTUIManager.m */, + 63F014BE1B02080B003B75D2 /* RCTPointAnnotation.h */, + 63F014BF1B02080B003B75D2 /* RCTPointAnnotation.m */, ); path = Modules; sourceTree = ""; @@ -599,6 +604,7 @@ 830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */, 137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */, 00C1A2B31AC0B7E000E89A1C /* RCTDevMenu.m in Sources */, + 63F014C01B02080B003B75D2 /* RCTPointAnnotation.m in Sources */, 14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */, 134FCB3E1A6E7F0800051CC8 /* RCTWebViewExecutor.m in Sources */, 13B0801C1A69489C00A75B9A /* RCTNavItem.m in Sources */, diff --git a/React/Views/RCTConvert+CoreLocation.h b/React/Views/RCTConvert+CoreLocation.h index 89e0c729c33361..e8c1e73853b307 100644 --- a/React/Views/RCTConvert+CoreLocation.h +++ b/React/Views/RCTConvert+CoreLocation.h @@ -1,10 +1,11 @@ -// -// RCTConvert+CoreLocation.h -// React -// -// Created by Nick Lockwood on 12/04/2015. -// Copyright (c) 2015 Facebook. All rights reserved. -// +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ #import diff --git a/React/Views/RCTConvert+CoreLocation.m b/React/Views/RCTConvert+CoreLocation.m index a347c7fea750d3..505a6aba3f3368 100644 --- a/React/Views/RCTConvert+CoreLocation.m +++ b/React/Views/RCTConvert+CoreLocation.m @@ -1,10 +1,11 @@ -// -// RCTConvert+CoreLocation.m -// React -// -// Created by Nick Lockwood on 12/04/2015. -// Copyright (c) 2015 Facebook. All rights reserved. -// +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ #import "RCTConvert+CoreLocation.h" diff --git a/React/Views/RCTConvert+MapKit.h b/React/Views/RCTConvert+MapKit.h index d4bf8d2d765fb9..d3e7fbc153f3e7 100644 --- a/React/Views/RCTConvert+MapKit.h +++ b/React/Views/RCTConvert+MapKit.h @@ -1,13 +1,15 @@ -// -// RCTConvert+MapKit.h -// React -// -// Created by Nick Lockwood on 12/04/2015. -// Copyright (c) 2015 Facebook. All rights reserved. -// +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ #import +#import "RCTPointAnnotation.h" #import "RCTConvert.h" @interface RCTConvert (MapKit) @@ -16,8 +18,12 @@ + (MKCoordinateRegion)MKCoordinateRegion:(id)json; + (MKShape *)MKShape:(id)json; + (MKMapType)MKMapType:(id)json; ++ (RCTPointAnnotation *)RCTPointAnnotation:(id)json; typedef NSArray MKShapeArray; + (MKShapeArray *)MKShapeArray:(id)json; +typedef NSArray RCTPointAnnotationArray; ++ (RCTPointAnnotationArray *)RCTPointAnnotationArray:(id)json; + @end diff --git a/React/Views/RCTConvert+MapKit.m b/React/Views/RCTConvert+MapKit.m index 6dc541a460931a..a1ba0ac98efa65 100644 --- a/React/Views/RCTConvert+MapKit.m +++ b/React/Views/RCTConvert+MapKit.m @@ -1,14 +1,15 @@ -// -// RCTConvert+MapKit.m -// React -// -// Created by Nick Lockwood on 12/04/2015. -// Copyright (c) 2015 Facebook. All rights reserved. -// +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ #import "RCTConvert+MapKit.h" - #import "RCTConvert+CoreLocation.h" +#import "RCTPointAnnotation.h" @implementation RCTConvert(MapKit) @@ -49,4 +50,20 @@ + (MKShape *)MKShape:(id)json @"hybrid": @(MKMapTypeHybrid), }), MKMapTypeStandard, integerValue) ++ (RCTPointAnnotation *)RCTPointAnnotation:(id)json +{ + json = [self NSDictionary:json]; + RCTPointAnnotation *shape = [[RCTPointAnnotation alloc] init]; + shape.coordinate = [self CLLocationCoordinate2D:json]; + shape.title = [RCTConvert NSString:json[@"title"]]; + shape.subtitle = [RCTConvert NSString:json[@"subtitle"]]; + shape.identifier = [RCTConvert NSString:json[@"id"]]; + shape.hasLeftCallout = [RCTConvert BOOL:json[@"hasLeftCallout"]]; + shape.hasRightCallout = [RCTConvert BOOL:json[@"hasRightCallout"]]; + shape.animateDrop = [RCTConvert BOOL:json[@"animateDrop"]]; + return shape; +} + +RCT_ARRAY_CONVERTER(RCTPointAnnotation) + @end diff --git a/React/Views/RCTMap.h b/React/Views/RCTMap.h index d372db56e465d9..41cc13a12e56b2 100644 --- a/React/Views/RCTMap.h +++ b/React/Views/RCTMap.h @@ -26,7 +26,8 @@ extern const CGFloat RCTMapZoomBoundBuffer; @property (nonatomic, assign) CGFloat maxDelta; @property (nonatomic, assign) UIEdgeInsets legalLabelInsets; @property (nonatomic, strong) NSTimer *regionChangeObserveTimer; +@property (nonatomic, strong) NSMutableArray *annotationIds; -- (void)setAnnotations:(MKShapeArray *)annotations; +- (void)setAnnotations:(RCTPointAnnotationArray *)annotations; @end diff --git a/React/Views/RCTMap.m b/React/Views/RCTMap.m index 40b60508e26da4..d51af5a01ddb00 100644 --- a/React/Views/RCTMap.m +++ b/React/Views/RCTMap.m @@ -112,12 +112,52 @@ - (void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated [super setRegion:region animated:animated]; } -- (void)setAnnotations:(MKShapeArray *)annotations +- (void)setAnnotations:(RCTPointAnnotationArray *)annotations { - [self removeAnnotations:self.annotations]; - if (annotations.count) { - [self addAnnotations:annotations]; + NSMutableArray *newAnnotationIds = [[NSMutableArray alloc] init]; + NSMutableArray *annotationsToDelete = [[NSMutableArray alloc] init]; + NSMutableArray *annotationsToAdd = [[NSMutableArray alloc] init]; + + for (RCTPointAnnotation *annotation in annotations) { + if (![annotation isKindOfClass:[RCTPointAnnotation class]]) { + continue; + } + + [newAnnotationIds addObject:annotation.identifier]; + + // If the current set does not contain the new annotation, mark it as add + if (![self.annotationIds containsObject:annotation.identifier]) { + [annotationsToAdd addObject:annotation]; + } + } + + for (RCTPointAnnotation *annotation in self.annotations) { + if (![annotation isKindOfClass:[RCTPointAnnotation class]]) { + continue; + } + + // If the new set does not contain an existing annotation, mark it as delete + if (![newAnnotationIds containsObject:annotation.identifier]) { + [annotationsToDelete addObject:annotation]; + } + } + + if (annotationsToDelete.count) { + [self removeAnnotations:annotationsToDelete]; + } + + if (annotationsToAdd.count) { + [self addAnnotations:annotationsToAdd]; + } + + NSMutableArray *newIds = [[NSMutableArray alloc] init]; + for (RCTPointAnnotation *anno in self.annotations) { + if ([anno isKindOfClass:[MKUserLocation class]]) { + continue; + } + [newIds addObject:anno.identifier]; } + self.annotationIds = newIds; } @end diff --git a/React/Views/RCTMapManager.m b/React/Views/RCTMapManager.m index b1c5c84b836163..fba2a60fab7b7a 100644 --- a/React/Views/RCTMapManager.m +++ b/React/Views/RCTMapManager.m @@ -15,6 +15,9 @@ #import "RCTEventDispatcher.h" #import "RCTMap.h" #import "UIView+React.h" +#import "RCTPointAnnotation.h" + +#import static NSString *const RCTMapViewKey = @"MapView"; @@ -42,7 +45,7 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(minDelta, CGFloat) RCT_EXPORT_VIEW_PROPERTY(legalLabelInsets, UIEdgeInsets) RCT_EXPORT_VIEW_PROPERTY(mapType, MKMapType) -RCT_EXPORT_VIEW_PROPERTY(annotations, MKShapeArray) +RCT_EXPORT_VIEW_PROPERTY(annotations, RCTPointAnnotationArray) RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap) { [view setRegion:json ? [RCTConvert MKCoordinateRegion:json] : defaultView.region animated:YES]; @@ -50,6 +53,73 @@ - (UIView *)view #pragma mark MKMapViewDelegate + + +- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view +{ + if (![view.annotation isKindOfClass:[MKUserLocation class]]) { + + RCTPointAnnotation *annotation = (RCTPointAnnotation *)view.annotation; + NSString *title = view.annotation.title ?: @""; + NSString *subtitle = view.annotation.subtitle ?: @""; + + NSDictionary *event = @{ + @"target": mapView.reactTag, + @"action": @"annotation-click", + @"annotation": @{ + @"id": annotation.identifier, + @"title": title, + @"subtitle": subtitle, + @"latitude": @(annotation.coordinate.latitude), + @"longitude": @(annotation.coordinate.longitude) + } + }; + + [self.bridge.eventDispatcher sendInputEventWithName:@"topTap" body:event]; + } +} + +- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(RCTPointAnnotation *)annotation +{ + if ([annotation isKindOfClass:[MKUserLocation class]]) { + return nil; + } + + MKPinAnnotationView *annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"RCTAnnotation"]; + + annotationView.canShowCallout = true; + annotationView.animatesDrop = annotation.animateDrop; + + annotationView.leftCalloutAccessoryView = nil; + if (annotation.hasLeftCallout) { + annotationView.leftCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; + } + + annotationView.rightCalloutAccessoryView = nil; + if (annotation.hasRightCallout) { + annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; + } + + return annotationView; +} + +- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control +{ + // Pass to js + RCTPointAnnotation *annotation = (RCTPointAnnotation *)view.annotation; + NSString *side = (control == view.leftCalloutAccessoryView) ? @"left" : @"right"; + + NSDictionary *event = @{ + @"target": mapView.reactTag, + @"side": side, + @"action": @"callout-click", + @"annotationId": annotation.identifier + }; + + [self.bridge.eventDispatcher sendInputEventWithName:@"topTap" body:event]; +} + + - (void)mapView:(RCTMap *)mapView didUpdateUserLocation:(MKUserLocation *)location { if (mapView.followUserLocation) { @@ -143,7 +213,7 @@ - (void)_emitRegionChangeEvent:(RCTMap *)mapView continuous:(BOOL)continuous #define FLUSH_NAN(value) (isnan(value) ? 0 : value) NSDictionary *event = @{ - @"target": [mapView reactTag], + @"target": mapView.reactTag, @"continuous": @(continuous), @"region": @{ @"latitude": @(FLUSH_NAN(region.center.latitude)), diff --git a/React/Views/RCTSegmentedControl.h b/React/Views/RCTSegmentedControl.h index 8e6e1255ef0492..3e95735bd31f75 100644 --- a/React/Views/RCTSegmentedControl.h +++ b/React/Views/RCTSegmentedControl.h @@ -1,10 +1,11 @@ -// -// RCTSegmentedControl.h -// React -// -// Created by Clay Allsopp on 3/31/15. -// Copyright (c) 2015 Facebook. All rights reserved. -// +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ #import diff --git a/React/Views/RCTSegmentedControl.m b/React/Views/RCTSegmentedControl.m index 59e4cfb86b5fa4..58e5629937a562 100644 --- a/React/Views/RCTSegmentedControl.m +++ b/React/Views/RCTSegmentedControl.m @@ -1,10 +1,11 @@ -// -// RCTSegmentedControl.m -// React -// -// Created by Clay Allsopp on 3/31/15. -// Copyright (c) 2015 Facebook. All rights reserved. -// +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ #import "RCTSegmentedControl.h" From f383bf2b83c42cca96df5704c3b6c5ceba7fbb54 Mon Sep 17 00:00:00 2001 From: Stanislav Vishnevskiy Date: Thu, 25 Jun 2015 09:14:19 -0700 Subject: [PATCH 03/11] [LayoutAnimation] RCTAnimationTypeKeyboard Summary: This adds the Keyboard animation type for when you want to animate UI based on the keyboard appearing/disappearing. Closes https://github.com/facebook/react-native/pull/1366 Github Author: Stanislav Vishnevskiy Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Libraries/Animation/LayoutAnimation.js | 1 + React/Base/RCTConvert.m | 1 + React/Modules/RCTUIManager.m | 17 ++++++++++------- React/Views/RCTAnimationType.h | 1 + 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Libraries/Animation/LayoutAnimation.js b/Libraries/Animation/LayoutAnimation.js index c297123ba23494..fbfc2d44f36da6 100644 --- a/Libraries/Animation/LayoutAnimation.js +++ b/Libraries/Animation/LayoutAnimation.js @@ -23,6 +23,7 @@ var TypesEnum = { easeInEaseOut: true, easeIn: true, easeOut: true, + keyboard: true, }; var Types = keyMirror(TypesEnum); diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 8461db3e98f826..6347bcc01bc94d 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -997,6 +997,7 @@ + (NSPropertyList)NSPropertyList:(id)json @"easeIn": @(RCTAnimationTypeEaseIn), @"easeOut": @(RCTAnimationTypeEaseOut), @"easeInEaseOut": @(RCTAnimationTypeEaseInEaseOut), + @"keyboard": @(RCTAnimationTypeKeyboard), }), RCTAnimationTypeEaseInEaseOut, integerValue) @end diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index a69b8e7ddc4053..959379a87e7377 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -51,20 +51,23 @@ @interface RCTAnimation : NSObject @implementation RCTAnimation -static UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType type) +static UIViewAnimationOptions UIViewAnimationOptionsFromRCTAnimationType(RCTAnimationType type) { switch (type) { case RCTAnimationTypeLinear: - return UIViewAnimationCurveLinear; + return UIViewAnimationOptionCurveLinear; case RCTAnimationTypeEaseIn: - return UIViewAnimationCurveEaseIn; + return UIViewAnimationOptionCurveEaseIn; case RCTAnimationTypeEaseOut: - return UIViewAnimationCurveEaseOut; + return UIViewAnimationOptionCurveEaseOut; case RCTAnimationTypeEaseInEaseOut: - return UIViewAnimationCurveEaseInOut; + return UIViewAnimationOptionCurveEaseInOut; + case RCTAnimationTypeKeyboard: + // http://stackoverflow.com/questions/18870447/how-to-use-the-default-ios7-uianimation-curve + return (UIViewAnimationOptions)(7 << 16); default: RCTLogError(@"Unsupported animation type %zd", type); - return UIViewAnimationCurveEaseInOut; + return UIViewAnimationOptionCurveEaseInOut; } } @@ -116,7 +119,7 @@ - (void)performAnimations:(void (^)(void))animations } else { UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState | - UIViewAnimationCurveFromRCTAnimationType(_animationType); + UIViewAnimationOptionsFromRCTAnimationType(_animationType); [UIView animateWithDuration:_duration delay:_delay diff --git a/React/Views/RCTAnimationType.h b/React/Views/RCTAnimationType.h index 1426b54cbcc128..0f2703ea6b3295 100644 --- a/React/Views/RCTAnimationType.h +++ b/React/Views/RCTAnimationType.h @@ -15,4 +15,5 @@ typedef NS_ENUM(NSInteger, RCTAnimationType) { RCTAnimationTypeEaseIn, RCTAnimationTypeEaseOut, RCTAnimationTypeEaseInEaseOut, + RCTAnimationTypeKeyboard, }; From 5e71d352a6114afb6f43163838d9c326cce37972 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Thu, 25 Jun 2015 09:38:54 -0700 Subject: [PATCH 04/11] [ReactNative] Guard agains errors during reconciliation Summary: @public After refactoring the MessageQueue a guard was missing on around `batchedUpdates` call. Test Plan: Introduce an error on `getInitialState` of `AdsManagerTabsModalView.ios.js` --- Libraries/Utilities/MessageQueue.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index 589ee5e9ada077..660d0987657261 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -76,15 +76,17 @@ class MessageQueue { * Public APIs */ processBatch(batch) { - ReactUpdates.batchedUpdates(() => { - batch.forEach((call) => { - let method = call.method === 'callFunctionReturnFlushedQueue' ? - '__callFunction' : '__invokeCallback'; - guard(() => this[method].apply(this, call.args)); + guard(() => { + ReactUpdates.batchedUpdates(() => { + batch.forEach((call) => { + let method = call.method === 'callFunctionReturnFlushedQueue' ? + '__callFunction' : '__invokeCallback'; + guard(() => this[method].apply(this, call.args)); + }); + BridgeProfiling.profile('ReactUpdates.batchedUpdates()'); }); - BridgeProfiling.profile('ReactUpdates.batchedUpdates()'); + BridgeProfiling.profileEnd(); }); - BridgeProfiling.profileEnd(); return this.flushedQueue(); } From 7963add0d558a16c8440601c24ce4e632f23ac89 Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Thu, 25 Jun 2015 10:19:22 -0700 Subject: [PATCH 05/11] [ReactNative] Revamp Navigator scene cache strategy Summary: Updating range is too complicated. We can keep cached versions of the previously rendered scenes in a map. @public Test Plan: Verify that the active scene is the only thing that get re-rendered, and that rendering doesn't happen during transitions or gestures. Test navigation thouroughly in AdsManager --- .../CustomComponents/Navigator/Navigator.js | 117 +++++------------- 1 file changed, 32 insertions(+), 85 deletions(-) diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index d9e452d22d9db1..a881a6c3dfd56d 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -30,6 +30,7 @@ var AnimationsDebugModule = require('NativeModules').AnimationsDebugModule; var Dimensions = require('Dimensions'); var InteractionMixin = require('InteractionMixin'); +var Map = require('Map'); var NavigationContext = require('NavigationContext'); var NavigatorBreadcrumbNavigationBar = require('NavigatorBreadcrumbNavigationBar'); var NavigatorNavigationBar = require('NavigatorNavigationBar'); @@ -257,6 +258,8 @@ var Navigator = React.createClass({ }, getInitialState: function() { + this._renderedSceneMap = new Map(); + var routeStack = this.props.initialRouteStack || [this.props.initialRoute]; invariant( routeStack.length >= 1, @@ -276,10 +279,6 @@ var Navigator = React.createClass({ ), idStack: routeStack.map(() => getuid()), routeStack, - // `updatingRange*` allows us to only render the visible or staged scenes - // On first render, we will render every scene in the initialRouteStack - updatingRangeStart: 0, - updatingRangeLength: routeStack.length, presentedIndex: initialRouteIndex, transitionFromIndex: null, activeGesture: null, @@ -351,8 +350,6 @@ var Navigator = React.createClass({ sceneConfigStack: nextRouteStack.map( this.props.configureScene ), - updatingRangeStart: 0, - updatingRangeLength: nextRouteStack.length, presentedIndex: destIndex, activeGesture: null, transitionFromIndex: null, @@ -829,11 +826,6 @@ var Navigator = React.createClass({ return false; }, - _resetUpdatingRange: function() { - this.state.updatingRangeStart = 0; - this.state.updatingRangeLength = this.state.routeStack.length; - }, - _getDestIndexWithinBounds: function(n) { var currentIndex = this.state.presentedIndex; var destIndex = currentIndex + n; @@ -851,15 +843,8 @@ var Navigator = React.createClass({ _jumpN: function(n) { var destIndex = this._getDestIndexWithinBounds(n); - var requestTransitionAndResetUpdatingRange = () => { - this._enableScene(destIndex); - this._transitionTo(destIndex); - this._resetUpdatingRange(); - }; - this.setState({ - updatingRangeStart: destIndex, - updatingRangeLength: 1, - }, requestTransitionAndResetUpdatingRange); + this._enableScene(destIndex); + this._transitionTo(destIndex); }, jumpTo: function(route) { @@ -891,18 +876,14 @@ var Navigator = React.createClass({ var nextAnimationConfigStack = activeAnimationConfigStack.concat([ this.props.configureScene(route), ]); - var requestTransitionAndResetUpdatingRange = () => { - this._enableScene(destIndex); - this._transitionTo(destIndex); - this._resetUpdatingRange(); - }; this.setState({ idStack: nextIDStack, routeStack: nextStack, sceneConfigStack: nextAnimationConfigStack, - updatingRangeStart: nextStack.length - 1, - updatingRangeLength: 1, - }, requestTransitionAndResetUpdatingRange); + }, () => { + this._enableScene(destIndex); + this._transitionTo(destIndex); + }); }, _popN: function(n) { @@ -958,10 +939,7 @@ var Navigator = React.createClass({ idStack: nextIDStack, routeStack: nextRouteStack, sceneConfigStack: nextAnimationModeStack, - updatingRangeStart: index, - updatingRangeLength: 1, }, () => { - this._resetUpdatingRange(); if (index === this.state.presentedIndex) { this._emitWillFocus(route); this._emitDidFocus(route); @@ -1034,67 +1012,15 @@ var Navigator = React.createClass({ var newStackLength = index + 1; // Remove any unneeded rendered routes. if (newStackLength < this.state.routeStack.length) { - var updatingRangeStart = newStackLength; // One past the top - var updatingRangeLength = this.state.routeStack.length - newStackLength + 1; this.state.idStack.slice(newStackLength).map((removingId) => { this._itemRefs[removingId] = null; }); this.setState({ - updatingRangeStart: updatingRangeStart, - updatingRangeLength: updatingRangeLength, sceneConfigStack: this.state.sceneConfigStack.slice(0, newStackLength), idStack: this.state.idStack.slice(0, newStackLength), routeStack: this.state.routeStack.slice(0, newStackLength), - }, this._resetUpdatingRange); - } - }, - - _renderOptimizedScenes: function() { - // To avoid rendering scenes that are not visible, we use - // updatingRangeStart and updatingRangeLength to track the scenes that need - // to be updated. - - // To avoid visual glitches, we never re-render scenes during a transition. - // We assume that `state.updatingRangeLength` will have a length during the - // initial render of any scene - var shouldRenderScenes = this.state.updatingRangeLength !== 0; - if (shouldRenderScenes) { - return ( - - - {this.state.routeStack.map(this._renderOptimizedScene)} - - - ); + }); } - // If no scenes are changing, we can save render time. React will notice - // that we are rendering a StaticContainer in the same place, so the - // existing element will be updated. When React asks the element - // shouldComponentUpdate, the StaticContainer will return false, and the - // children from the previous reconciliation will remain. - return ( - - ); - }, - - _renderOptimizedScene: function(route, i) { - var shouldRenderScene = - i >= this.state.updatingRangeStart && - i <= this.state.updatingRangeStart + this.state.updatingRangeLength; - var scene = shouldRenderScene ? this._renderScene(route, i) : null; - return ( - - {scene} - - ); }, _renderScene: function(route, i) { @@ -1146,9 +1072,30 @@ var Navigator = React.createClass({ }, render: function() { + var newRenderedSceneMap = new Map(); + var scenes = this.state.routeStack.map((route, index) => { + var renderedScene; + if (this._renderedSceneMap.has(route) && + index !== this.state.presentedIndex) { + renderedScene = this._renderedSceneMap.get(route); + } else { + renderedScene = this._renderScene(route, index); + } + newRenderedSceneMap.set(route, renderedScene); + return renderedScene; + }); + this._renderedSceneMap = newRenderedSceneMap; return ( - {this._renderOptimizedScenes()} + + {scenes} + {this._renderNavigationBar()} ); From 7159a4e9477796284d2dfc5a6f8952b25aee8b5c Mon Sep 17 00:00:00 2001 From: chaceliang Date: Thu, 25 Jun 2015 14:30:06 -0700 Subject: [PATCH 06/11] Revert "[React Native] Remove layout-only nodes" --- .../UIExplorer.xcodeproj/project.pbxproj | 4 - .../testViewExampleSnapshot_1@2x.png | Bin 87728 -> 87728 bytes .../UIExplorerUnitTests/RCTUIManagerTests.m | 374 +----- .../Test.includeRequire.runModule.bundle | 1120 ----------------- .../UIExplorer/UIExplorerUnitTests/Test.js | 13 - Libraries/Components/ScrollView/ScrollView.js | 1 - Libraries/Components/View/View.js | 6 - .../ReactNative/ReactNativeViewAttributes.js | 1 - Libraries/Text/RCTShadowRawText.m | 5 - React/Modules/RCTUIManager.m | 520 ++------ React/Views/RCTShadowView.h | 6 - React/Views/RCTShadowView.m | 82 +- React/Views/RCTViewNodeProtocol.h | 3 +- 13 files changed, 113 insertions(+), 2022 deletions(-) delete mode 100644 Examples/UIExplorer/UIExplorerUnitTests/Test.includeRequire.runModule.bundle delete mode 100644 Examples/UIExplorer/UIExplorerUnitTests/Test.js diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index ae90506621012f..c266cabcfe1592 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -47,7 +47,6 @@ 14DC67F41AB71881001358AB /* libRCTPushNotification.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14DC67F11AB71876001358AB /* libRCTPushNotification.a */; }; 3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 357859011B28D2C500341EDB /* libRCTLinking.a */; }; 834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; }; - 83686E801B39D26300CBA10B /* Test.includeRequire.runModule.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 83686E7F1B39D26300CBA10B /* Test.includeRequire.runModule.bundle */; }; D85B829E1AB6D5D7003F4FE2 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D85B829C1AB6D5CE003F4FE2 /* libRCTVibration.a */; }; /* End PBXBuildFile section */ @@ -203,7 +202,6 @@ 14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../../Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = ""; }; 357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = ""; }; 58005BE41ABA80530062E044 /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = ../../Libraries/RCTTest/RCTTest.xcodeproj; sourceTree = ""; }; - 83686E7F1B39D26300CBA10B /* Test.includeRequire.runModule.bundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Test.includeRequire.runModule.bundle; sourceTree = ""; }; D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = ../../Libraries/Vibration/RCTVibration.xcodeproj; sourceTree = ""; }; /* End PBXFileReference section */ @@ -353,7 +351,6 @@ 1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */, 143BC57E1B21E18100462512 /* Info.plist */, 14D6D7101B220EB3001FB087 /* libOCMock.a */, - 83686E7F1B39D26300CBA10B /* Test.includeRequire.runModule.bundle */, 14D6D7011B220AE3001FB087 /* OCMock */, 143BC57F1B21E18100462512 /* ReferenceImages */, ); @@ -748,7 +745,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 83686E801B39D26300CBA10B /* Test.includeRequire.runModule.bundle in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExampleSnapshot_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExampleSnapshot_1@2x.png index cf264cbe726f09ea226f77bc6e8027cef106a28e..d37b708794c43ab16e1fd7326207067117212cbc 100644 GIT binary patch delta 18707 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 delta 18752 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 TPO=VMFw8*L^m64z=ez$4hE+VV diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m index 8e848ede46e7fb..388761a8f939b0 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m @@ -2,8 +2,6 @@ #import -#import "RCTRootView.h" -#import "RCTShadowView.h" #import "RCTSparseArray.h" #import "RCTUIManager.h" #import "UIView+React.h" @@ -11,36 +9,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; -- (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 @@ -63,11 +39,6 @@ - (void)setUp 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; } } @@ -84,6 +55,8 @@ - (void)testManagingChildrenToAddViews // Add views 1-5 to view 20 [_uiManager _manageChildren:@20 + moveFromIndices:nil + moveToIndices:nil addChildReactTags:tagsToAdd addAtIndices:addAtIndices removeAtIndices:nil @@ -116,6 +89,8 @@ - (void)testManagingChildrenToRemoveViews // Remove views 1-5 from view 20 [_uiManager _manageChildren:@20 + moveFromIndices:nil + moveToIndices:nil addChildReactTags:nil addAtIndices:nil removeAtIndices:removeAtIndices @@ -153,9 +128,11 @@ - (void)testManagingChildrenToAddRemoveAndMove { 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]; @@ -171,6 +148,8 @@ - (void)testManagingChildrenToAddRemoveAndMove } [_uiManager _manageChildren:@20 + moveFromIndices:moveFromIndices + moveToIndices:moveToIndices addChildReactTags:tagsToAdd addAtIndices:addAtIndices removeAtIndices:removeAtIndices @@ -197,331 +176,4 @@ - (void)testManagingChildrenToAddRemoveAndMove } } -/* +-----------------------------------------------------------+ +----------------------+ - * | 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 -{ - 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]; - }]; -} - -- (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)testScenario1 -{ - RCTUIManager *uiManager = [[RCTUIManager alloc] init]; - NSURL *bundleURL = [[NSBundle bundleForClass:self.class] URLForResource:@"Test.includeRequire.runModule" withExtension:nil]; - RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL moduleProvider:^{ return @[uiManager]; } launchOptions:nil]; - NS_VALID_UNTIL_END_OF_SCOPE RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"Test"]; - - XCTestExpectation *expectation = [self expectationWithDescription:@""]; - - 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); - [expectation fulfill]; - }]; - - [uiManager flushUIBlocks]; - }); - - [self waitForExpectationsWithTimeout:1 handler:nil]; - - expectation = [self expectationWithDescription:@""]; - 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); - [expectation fulfill]; - }]; - - [uiManager flushUIBlocks]; - }); - - [self waitForExpectationsWithTimeout:1 handler:nil]; - - expectation = [self expectationWithDescription:@""]; - 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); - [expectation fulfill]; - }]; - - [uiManager flushUIBlocks]; - }); - - [self waitForExpectationsWithTimeout:1 handler:nil]; -} - -- (void)testScenario2 -{ - RCTUIManager *uiManager = [[RCTUIManager alloc] init]; - NSURL *bundleURL = [[NSBundle bundleForClass:self.class] URLForResource:@"Test.includeRequire.runModule" withExtension:nil]; - RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL moduleProvider:^{ return @[uiManager]; } launchOptions:nil]; - NS_VALID_UNTIL_END_OF_SCOPE RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"Test"]; - - XCTestExpectation *expectation = [self expectationWithDescription:@""]; - - 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); - [expectation fulfill]; - }]; - - [uiManager flushUIBlocks]; - }); - - [self waitForExpectationsWithTimeout:1 handler:nil]; - - expectation = [self expectationWithDescription:@""]; - 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); - [expectation fulfill]; - }]; - - [uiManager flushUIBlocks]; - }); - - [self waitForExpectationsWithTimeout:1 handler:nil]; - - expectation = [self expectationWithDescription:@""]; - 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); - [expectation fulfill]; - }]; - - [uiManager flushUIBlocks]; - }); - - [self waitForExpectationsWithTimeout:1 handler:nil]; -} - @end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/Test.includeRequire.runModule.bundle b/Examples/UIExplorer/UIExplorerUnitTests/Test.includeRequire.runModule.bundle deleted file mode 100644 index 519e3950ed89c3..00000000000000 --- a/Examples/UIExplorer/UIExplorerUnitTests/Test.includeRequire.runModule.bundle +++ /dev/null @@ -1,1120 +0,0 @@ -__DEV__ = -true; -( -function(global){ - - -if(global.require){ -return;} - - -var __DEV__=global.__DEV__; - -var toString=Object.prototype.toString; - - - - - - - - - - - - - -var modulesMap={}, - - - - - - -dependencyMap={}, - - - -predefinedRefCounts={}, - -_counter=0, - -REQUIRE_WHEN_READY=1, -USED_AS_TRANSPORT=2, - -hop=Object.prototype.hasOwnProperty; - -function _debugUnresolvedDependencies(names){ -var unresolved=Array.prototype.slice.call(names); -var visited={}; -var ii, name, module, dependency; - -while(unresolved.length) { -name = unresolved.shift(); -if(visited[name]){ -continue;} - -visited[name] = true; - -module = modulesMap[name]; -if(!module || !module.waiting){ -continue;} - - -for(ii = 0; ii < module.dependencies.length; ii++) { -dependency = module.dependencies[ii]; -if(!modulesMap[dependency] || modulesMap[dependency].waiting){ -unresolved.push(dependency);}}} - - - - -for(name in visited) if(hop.call(visited, name)){ -unresolved.push(name);} - - -var messages=[]; -for(ii = 0; ii < unresolved.length; ii++) { -name = unresolved[ii]; -var message=name; -module = modulesMap[name]; -if(!module){ -message += ' is not defined';}else -if(!module.waiting){ -message += ' is ready';}else -{ -var unresolvedDependencies=[]; -for(var jj=0; jj < module.dependencies.length; jj++) { -dependency = module.dependencies[jj]; -if(!modulesMap[dependency] || modulesMap[dependency].waiting){ -unresolvedDependencies.push(dependency);}} - - -message += ' is waiting for ' + unresolvedDependencies.join(', ');} - -messages.push(message);} - -return messages.join('\n');} - - - - - -function ModuleError(msg){ -this.name = 'ModuleError'; -this.message = msg; -this.stack = Error(msg).stack; -this.framesToPop = 2;} - -ModuleError.prototype = Object.create(Error.prototype); -ModuleError.prototype.constructor = ModuleError; - -var _performance= -global.performance || -global.msPerformance || -global.webkitPerformance || {}; - -if(!_performance.now){ -_performance = global.Date;} - - -var _now=_performance? -_performance.now.bind(_performance):function(){return 0;}; - -var _factoryStackCount=0; -var _factoryTime=0; -var _totalFactories=0; -var _inGuard=false; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -function require(id){ -var module=modulesMap[id], dep, i, msg; -if(module && module.exports){ - - -if(module.refcount-- === 1){ -delete modulesMap[id];} - -return module.exports;} - -if(global.ErrorUtils && !_inGuard){ -_inGuard = true; -try{ -var ret=require.apply(this, arguments);} -catch(e) { -global.ErrorUtils.reportFatalError(e);} - -_inGuard = false; -return ret;} - - -if(!module){ -msg = 'Requiring unknown module "' + id + '"'; -if(__DEV__){ -msg += '. If you are sure the module is there, try restarting the packager.';} - -throw new ModuleError(msg);} - - -if(module.hasError){ -throw new ModuleError( -'Requiring module "' + id + '" which threw an exception');} - - - -if(module.waiting){ -throw new ModuleError( -'Requiring module "' + id + '" with unresolved dependencies: ' + -_debugUnresolvedDependencies([id]));} - - - -var exports=module.exports = {}; -var factory=module.factory; -if(toString.call(factory) === '[object Function]'){ -var args=[], -dependencies=module.dependencies, -length=dependencies.length, -ret; -if(module.special & USED_AS_TRANSPORT){ -length = Math.min(length, factory.length);} - -try{ -for(i = 0; args.length < length; i++) { -dep = dependencies[i]; -if(!module.inlineRequires[dep]){ -args.push(dep === 'module'?module: -dep === 'exports'?exports: -require.call(null, dep));}} - - - -++_totalFactories; -if(_factoryStackCount++ === 0){ -_factoryTime -= _now();} - -try{ -ret = factory.apply(module.context || global, args);} -catch(e) { -if(modulesMap.ex && modulesMap.erx){ - - -var ex=require.call(null, 'ex'); -var erx=require.call(null, 'erx'); -var messageWithParams=erx(e.message); -if(messageWithParams[0].indexOf(' from module "%s"') < 0){ -messageWithParams[0] += ' from module "%s"'; -messageWithParams[messageWithParams.length] = id;} - -e.message = ex.apply(null, messageWithParams);} - -throw e;}finally -{ -if(--_factoryStackCount === 0){ -_factoryTime += _now();}}} - - -catch(e) { -module.hasError = true; -module.exports = null; -throw e;} - -if(ret){ -if(__DEV__){ -if(typeof ret != 'object' && typeof ret != 'function'){ -throw new ModuleError( -'Factory for module "' + id + '" returned ' + -'an invalid value "' + ret + '". ' + -'Returned value should be either a function or an object.');}} - - - -module.exports = ret;}}else - -{ -module.exports = factory;} - - - - -if(module.refcount-- === 1){ -delete modulesMap[id];} - -return module.exports;} - - -require.__getFactoryTime = function(){ -return (_factoryStackCount?_now():0) + _factoryTime;}; - - -require.__getTotalFactories = function(){ -return _totalFactories;}; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -function define(id, dependencies, factory, -_special, _context, _refCount, _inlineRequires){ -if(dependencies === undefined){ -dependencies = []; -factory = id; -id = _uid();}else -if(factory === undefined){ -factory = dependencies; -if(toString.call(id) === '[object Array]'){ -dependencies = id; -id = _uid();}else -{ -dependencies = [];}} - - - - - -var canceler={cancel:_undefine.bind(this, id)}; - -var record=modulesMap[id]; - - - - - - -if(record){ -if(_refCount){ -record.refcount += _refCount;} - - -return canceler;}else -if(!dependencies && !factory && _refCount){ - - -predefinedRefCounts[id] = (predefinedRefCounts[id] || 0) + _refCount; -return canceler;}else -{ - -record = {id:id}; -record.refcount = (predefinedRefCounts[id] || 0) + (_refCount || 0); -delete predefinedRefCounts[id];} - - -if(__DEV__){ -if( -!factory || -typeof factory != 'object' && typeof factory != 'function' && -typeof factory != 'string'){ -throw new ModuleError( -'Invalid factory "' + factory + '" for module "' + id + '". ' + -'Factory should be either a function or an object.');} - - - -if(toString.call(dependencies) !== '[object Array]'){ -throw new ModuleError( -'Invalid dependencies for module "' + id + '". ' + -'Dependencies must be passed as an array.');}} - - - - -record.factory = factory; -record.dependencies = dependencies; -record.context = _context; -record.special = _special; -record.inlineRequires = _inlineRequires || {}; -record.waitingMap = {}; -record.waiting = 0; -record.hasError = false; -modulesMap[id] = record; -_initDependencies(id); - -return canceler;} - - -function _undefine(id){ -if(!modulesMap[id]){ -return;} - - -var module=modulesMap[id]; -delete modulesMap[id]; - -for(var dep in module.waitingMap) { -if(module.waitingMap[dep]){ -delete dependencyMap[dep][id];}} - - - -for(var ii=0; ii < module.dependencies.length; ii++) { -dep = module.dependencies[ii]; -if(modulesMap[dep]){ -if(modulesMap[dep].refcount-- === 1){ -_undefine(dep);}}else - -if(predefinedRefCounts[dep]){ -predefinedRefCounts[dep]--;}}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -function requireLazy(dependencies, factory, context){ -return define( -dependencies, -factory, -undefined, -REQUIRE_WHEN_READY, -context, -1);} - - - -function _uid(){ -return '__mod__' + _counter++;} - - -function _addDependency(module, dep){ - -if(!module.waitingMap[dep] && module.id !== dep){ -module.waiting++; -module.waitingMap[dep] = 1; -dependencyMap[dep] || (dependencyMap[dep] = {}); -dependencyMap[dep][module.id] = 1;}} - - - -function _initDependencies(id){ -var modulesToRequire=[]; -var module=modulesMap[id]; -var dep, i, subdep; - - -for(i = 0; i < module.dependencies.length; i++) { -dep = module.dependencies[i]; -if(!modulesMap[dep]){ -_addDependency(module, dep);}else -if(modulesMap[dep].waiting){ -for(subdep in modulesMap[dep].waitingMap) { -if(modulesMap[dep].waitingMap[subdep]){ -_addDependency(module, subdep);}}}} - - - - -if(module.waiting === 0 && module.special & REQUIRE_WHEN_READY){ -modulesToRequire.push(id);} - - - -if(dependencyMap[id]){ -var deps=dependencyMap[id]; -var submodule; -dependencyMap[id] = undefined; -for(dep in deps) { -submodule = modulesMap[dep]; - - -for(subdep in module.waitingMap) { -if(module.waitingMap[subdep]){ -_addDependency(submodule, subdep);}} - - - -if(submodule.waitingMap[id]){ -submodule.waitingMap[id] = undefined; -submodule.waiting--;} - -if(submodule.waiting === 0 && -submodule.special & REQUIRE_WHEN_READY){ -modulesToRequire.push(dep);}}} - - - - - -for(i = 0; i < modulesToRequire.length; i++) { -require.call(null, modulesToRequire[i]);}} - - - -function _register(id, exports){ -var module=modulesMap[id] = {id:id}; -module.exports = exports; -module.refcount = 0;} - - - - -_register('module', 0); -_register('exports', 0); - -_register('global', global); -_register('require', require); -_register('requireDynamic', require); -_register('requireLazy', requireLazy); - -global.require = require; -global.requireDynamic = require; -global.requireLazy = requireLazy; - -require.__debug = { -modules:modulesMap, -deps:dependencyMap, -printDependencyInfo:function(){ -if(!global.console){ -return;} - -var names=Object.keys(require.__debug.deps); -global.console.log(_debugUnresolvedDependencies(names));}}; - - - - - - - - - -global.__d = function(id, deps, factory, _special, _inlineRequires){ -var defaultDeps=['global', 'require', 'requireDynamic', 'requireLazy', -'module', 'exports']; -define(id, defaultDeps.concat(deps), factory, _special || USED_AS_TRANSPORT, -null, null, _inlineRequires);};})( - - -this); -Object. - - - - - - - - - - - - - - - - - -assign = function(target, sources){ -if(__DEV__){ -if(target == null){ -throw new TypeError('Object.assign target cannot be null or undefined');} - -if(typeof target !== 'object' && typeof target !== 'function'){ -throw new TypeError( -'In this environment the target of assign MUST be an object.' + -'This error is a performance optimization and not spec compliant.');}} - - - - -for(var nextIndex=1; nextIndex < arguments.length; nextIndex++) { -var nextSource=arguments[nextIndex]; -if(nextSource == null){ -continue;} - - -if(__DEV__){ -if(typeof nextSource !== 'object' && -typeof nextSource !== 'function'){ -throw new TypeError( -'In this environment the target of assign MUST be an object.' + -'This error is a performance optimization and not spec compliant.');}} - - - - - - - - -for(var key in nextSource) { -if(__DEV__){ -var hasOwnProperty=Object.prototype.hasOwnProperty; -if(!hasOwnProperty.call(nextSource, key)){ -throw new TypeError( -'One of the sources to assign has an enumerable key on the ' + -'prototype chain. This is an edge case that we do not support. ' + -'This error is a performance optimization and not spec compliant.');}} - - - -target[key] = nextSource[key];}} - - - -return target;}; -( - - - - - - - - - - - - - - - -function(global){ -'use strict'; - -var OBJECT_COLUMN_NAME='(index)'; -var LOG_LEVELS={ -trace:0, -log:1, -info:2, -warn:3, -error:4}; - - -function setupConsole(global){ - -if(!global.nativeLoggingHook){ -return;} - - -function getNativeLogFunction(level){ -return function(){ -var str=Array.prototype.map.call(arguments, function(arg){ -var ret; -var type=typeof arg; -if(arg === null){ -ret = 'null';}else -if(arg === undefined){ -ret = 'undefined';}else -if(type === 'string'){ -ret = '"' + arg + '"';}else -if(type === 'function'){ -try{ -ret = arg.toString();} -catch(e) { -ret = '[function unknown]';}}else - -{ - - -try{ -ret = JSON.stringify(arg);} -catch(e) { -if(typeof arg.toString === 'function'){ -try{ -ret = arg.toString();} -catch(E) {}}}} - - - -return ret || '["' + type + '" failed to stringify]';}). -join(', '); -global.nativeLoggingHook(str, level);};} - - - -var repeat=function(element, n){ -return Array.apply(null, Array(n)).map(function(){return element;});}; - - -function consoleTablePolyfill(rows){ - -if(!Array.isArray(rows)){ -var data=rows; -rows = []; -for(var key in data) { -if(data.hasOwnProperty(key)){ -var row=data[key]; -row[OBJECT_COLUMN_NAME] = key; -rows.push(row);}}} - - - -if(rows.length === 0){ -global.nativeLoggingHook('', LOG_LEVELS.log); -return;} - - -var columns=Object.keys(rows[0]).sort(); -var stringRows=[]; -var columnWidths=[]; - - - -columns.forEach(function(k, i){ -columnWidths[i] = k.length; -for(var j=0; j < rows.length; j++) { -var cellStr=rows[j][k].toString(); -stringRows[j] = stringRows[j] || []; -stringRows[j][i] = cellStr; -columnWidths[i] = Math.max(columnWidths[i], cellStr.length);}}); - - - - - -var joinRow=function(row, space){ -var cells=row.map(function(cell, i){ -var extraSpaces=repeat(' ', columnWidths[i] - cell.length).join(''); -return cell + extraSpaces;}); - -space = space || ' '; -return cells.join(space + '|' + space);}; - - -var separators=columnWidths.map(function(columnWidth){ -return repeat('-', columnWidth).join('');}); - -var separatorRow=joinRow(separators, '-'); -var header=joinRow(columns); -var table=[header, separatorRow]; - -for(var i=0; i < rows.length; i++) { -table.push(joinRow(stringRows[i]));} - - - - - - -global.nativeLoggingHook('\n' + table.join('\n'), LOG_LEVELS.log);} - - -global.console = { -error:getNativeLogFunction(LOG_LEVELS.error), -info:getNativeLogFunction(LOG_LEVELS.info), -log:getNativeLogFunction(LOG_LEVELS.log), -warn:getNativeLogFunction(LOG_LEVELS.warn), -trace:getNativeLogFunction(LOG_LEVELS.trace), -table:consoleTablePolyfill};} - - - - -if(typeof module !== 'undefined'){ -module.exports = setupConsole;}else -{ -setupConsole(global);}})( - - -this); -( - - - - - - - - - - - - - - - -function(global){ -var ErrorUtils={ -_inGuard:0, -_globalHandler:null, -setGlobalHandler:function(fun){ -ErrorUtils._globalHandler = fun;}, - -reportError:function(error){ -ErrorUtils._globalHandler && ErrorUtils._globalHandler(error);}, - -reportFatalError:function(error){ -ErrorUtils._globalHandler && ErrorUtils._globalHandler(error, true);}, - -applyWithGuard:function(fun, context, args){ -try{ -ErrorUtils._inGuard++; -return fun.apply(context, args);} -catch(e) { -ErrorUtils.reportError(e);}finally -{ -ErrorUtils._inGuard--;}}, - - -applyWithGuardIfNeeded:function(fun, context, args){ -if(ErrorUtils.inGuard()){ -return fun.apply(context, args);}else -{ -ErrorUtils.applyWithGuard(fun, context, args);}}, - - -inGuard:function(){ -return ErrorUtils._inGuard;}, - -guard:function(fun, name, context){ -if(typeof fun !== 'function'){ -console.warn('A function must be passed to ErrorUtils.guard, got ', fun); -return null;} - -name = name || fun.name || ''; -function guarded(){ -return ( -ErrorUtils.applyWithGuard( -fun, -context || this, -arguments, -null, -name));} - - - - -return guarded;}}; - - -global.ErrorUtils = ErrorUtils; - - - - - -function setupErrorGuard(){ -var onError=function(e){ -global.console.error( -'Error: ' + -'\n stack: ' + e.stack + -'\n line: ' + e.line + -'\n message: ' + e.message, -e);}; - - -global.ErrorUtils.setGlobalHandler(onError);} - - -setupErrorGuard();})( -this); -if( - - - - - - - - - - - -!String.prototype.startsWith){ -String.prototype.startsWith = function(search){ -'use strict'; -if(this == null){ -throw TypeError();} - -var string=String(this); -var pos=arguments.length > 1? -Number(arguments[1]) || 0:0; -var start=Math.min(Math.max(pos, 0), string.length); -return string.indexOf(String(search), pos) === start;};} - - - -if(!String.prototype.endsWith){ -String.prototype.endsWith = function(search){ -'use strict'; -if(this == null){ -throw TypeError();} - -var string=String(this); -var stringLength=string.length; -var searchString=String(search); -var pos=arguments.length > 1? -Number(arguments[1]) || 0:stringLength; -var end=Math.min(Math.max(pos, 0), stringLength); -var start=end - searchString.length; -if(start < 0){ -return false;} - -return string.lastIndexOf(searchString, start) === start;};} - - - -if(!String.prototype.contains){ -String.prototype.contains = function(search){ -'use strict'; -if(this == null){ -throw TypeError();} - -var string=String(this); -var pos=arguments.length > 1? -Number(arguments[1]) || 0:0; -return string.indexOf(String(search), pos) !== -1;};} - - - -if(!String.prototype.repeat){ -String.prototype.repeat = function(count){ -'use strict'; -if(this == null){ -throw TypeError();} - -var string=String(this); -count = Number(count) || 0; -if(count < 0 || count === Infinity){ -throw RangeError();} - -if(count === 1){ -return string;} - -var result=''; -while(count) { -if(count & 1){ -result += string;} - -if(count >>= 1){ -string += string;}} - - -return result;};} -( - - - - - - - - - -function(undefined){ - -function findIndex(predicate, context){ -if(this == null){ -throw new TypeError( -'Array.prototype.findIndex called on null or undefined');} - - -if(typeof predicate !== 'function'){ -throw new TypeError('predicate must be a function');} - -var list=Object(this); -var length=list.length >>> 0; -for(var i=0; i < length; i++) { -if(predicate.call(context, list[i], i, list)){ -return i;}} - - -return -1;} - - -if(!Array.prototype.findIndex){ -Object.defineProperty(Array.prototype, 'findIndex', { -enumerable:false, -writable:true, -configurable:true, -value:findIndex});} - - - - -if(!Array.prototype.find){ -Object.defineProperty(Array.prototype, 'find', { -enumerable:false, -writable:true, -configurable:true, -value:function(predicate, context){ -if(this == null){ -throw new TypeError( -'Array.prototype.find called on null or undefined');} - - -var index=findIndex.call(this, predicate, context); -return index === -1?undefined:this[index];}});}})(); -( -function(GLOBAL){ - - - - - - - -function getInvalidGlobalUseError(name){ -return new Error( -'You are trying to render the global ' + name + ' variable as a ' + -'React element. You probably forgot to require ' + name + '.');} - - -GLOBAL.Text = { -get defaultProps() { -throw getInvalidGlobalUseError('Text');}}; - - -GLOBAL.Image = { -get defaultProps() { -throw getInvalidGlobalUseError('Image');}}; - - - -if(GLOBAL.document){ -GLOBAL.document.createElement = null;} - - - - -GLOBAL.MutationObserver = undefined;})( -this); -__d('react-native/Examples/UIExplorer/UIExplorerUnitTests/Test.js',[],function(global, require, requireDynamic, requireLazy, module, exports) { -}); -;require("react-native/Examples/UIExplorer/UIExplorerUnitTests/Test.js"); -//@ sourceMappingURL=/Examples/UIExplorer/UIExplorerUnitTests/Test.includeRequire.runModule.map \ No newline at end of file diff --git a/Examples/UIExplorer/UIExplorerUnitTests/Test.js b/Examples/UIExplorer/UIExplorerUnitTests/Test.js deleted file mode 100644 index 3f0a21ff35b0b0..00000000000000 --- a/Examples/UIExplorer/UIExplorerUnitTests/Test.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * The examples provided by Facebook are for non-commercial testing and - * evaluation purposes only. - * - * Facebook reserves all rights not expressly granted. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL - * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index f01cce58a44781..512d8dbd6a4192 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 0cb6e4a4f3e6c5..c6a279a22b8820 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -77,12 +77,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 0de78cb8f73eeb..50b839e1db7441 100644 --- a/Libraries/ReactNative/ReactNativeViewAttributes.js +++ b/Libraries/ReactNative/ReactNativeViewAttributes.js @@ -24,7 +24,6 @@ ReactNativeViewAttributes.UIView = { onLayout: true, onAccessibilityTap: true, onMagicTap: true, - collapsible: true, }; ReactNativeViewAttributes.RCTView = merge( diff --git a/Libraries/Text/RCTShadowRawText.m b/Libraries/Text/RCTShadowRawText.m index 00a3490bcf283b..e99e1187b345c6 100644 --- a/Libraries/Text/RCTShadowRawText.m +++ b/Libraries/Text/RCTShadowRawText.m @@ -20,11 +20,6 @@ - (void)setText:(NSString *)text } } -- (BOOL)isLayoutOnly -{ - return YES; -} - - (NSString *)description { NSString *superDescription = super.description; diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 959379a87e7377..ef2d367a08a2b5 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -33,8 +33,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,23 +467,6 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo [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 implementated. - - 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]; @@ -485,30 +475,26 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo 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; } @@ -525,28 +511,24 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo } // 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)]); @@ -558,13 +540,13 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo [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); } @@ -586,7 +568,7 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo createAnimation.property); } for (RCTViewManagerUIBlock block in updateBlocks) { - block(uiManager, viewRegistry); + block(self, _viewRegistry); } } withCompletionBlock:nil]; } @@ -709,126 +691,6 @@ - (void)_removeChildren:(NSArray *)children fromContainer:(id 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:(out NSUInteger *)outOffset -{ - RCTShadowView *container = _shadowViewRegistry[containerReactTag]; - NSNumber *containerSuperviewReactTag = containerReactTag; - RCTShadowView *superview = container; - NSUInteger offset = 0; - - 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) { - 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; - } - - if (outOffset) *outOffset = offset; - return containerSuperviewReactTag; -} - RCT_EXPORT_METHOD(manageChildren:(NSNumber *)containerReactTag moveFromIndices:(NSArray *)moveFromIndices moveToIndices:(NSArray *)moveToIndices @@ -836,109 +698,62 @@ - (NSNumber *)containerReactTag:(NSNumber *)containerReactTag offset:(out NSUInt 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]; } } @@ -1021,72 +836,45 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView // 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]; - }]; - } -} - -- (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; - } + // 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; - // 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]; - } - - // 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); + [self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){ + RCTAssertMainThread(); - if ([view respondsToSelector:@selector(reactBridgeDidFinishTransaction)]) { - [_bridgeTransactionListeners addObject:view]; - } - _viewRegistry[reactTag] = view; + UIView *view = [manager view]; + if (view) { - return view; -} + // 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]; + } -NS_INLINE BOOL RCTRectIsDefined(CGRect frame) -{ - return !(isnan(frame.origin.x) || isnan(frame.origin.y) || isnan(frame.size.width) || isnan(frame.size.height)); -} + // 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); -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 @@ -1100,100 +888,10 @@ NS_INLINE BOOL RCTRectIsDefined(CGRect frame) 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; - 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) @@ -1532,7 +1230,7 @@ static void RCTMeasureLayout(RCTShadowView *view, [self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { _jsResponder = viewRegistry[reactTag]; if (!_jsResponder) { - RCTLogError(@"Invalid view set to be the JS responder - tag %@", reactTag); + RCTLogError(@"Invalid view set to be the JS responder - tag %zd", reactTag); } }]; } @@ -1790,27 +1488,3 @@ - (RCTUIManager *)uiManager } @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 38edc6e502d7cf..1c44033f6df59f 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 a6c49549870811..9d56bb90624a8f 100644 --- a/React/Views/RCTShadowView.m +++ b/React/Views/RCTShadowView.m @@ -367,10 +367,8 @@ - (NSNumber *)reactTagAtPoint:(CGPoint)point - (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,82 +392,6 @@ - (NSString *)recursiveDescription 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; - if (!layoutKeys) { - // Taken from LayoutPropTypes.js with the exception that borderWidth, - // borderTopWidth, borderBottomWidth, borderLeftWidth, and borderRightWidth - // were removed because black color is assumed - static NSString *const keys[] = { - @"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", - - // Special case is handled below. - @"collapsible", - }; - layoutKeys = [NSSet setWithObjects:keys count:sizeof(keys)/sizeof(*keys)]; - } - - NSNumber *collapsible = self.allProps[@"collapsible"]; - if (collapsible && !collapsible.boolValue) { - return NO; - } - - for (NSString *key in self.allProps) { - if (![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 96eb78f1a77e90..e78cc2ce7b26fc 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; From e3e60983e64aaa33fa08e827121c6db84ae14cef Mon Sep 17 00:00:00 2001 From: Eric Vicenti Date: Thu, 25 Jun 2015 14:40:56 -0700 Subject: [PATCH 07/11] [ReactNative] Navigator improved willFocus logic Summary: This makes sure to call willFocus before new scenes get mounted. This fixes cases where the keyboard is dismissed on willfocus events which incorrectly happens *after* the autofocus in a new scene. The keyboard was opening and getting immediately closed @public Test Plan: Test keyboard autofocus in new nav scenes on iOS --- Libraries/CustomComponents/Navigator/Navigator.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index a881a6c3dfd56d..7b7bf1ae629307 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -392,7 +392,6 @@ var Navigator = React.createClass({ this.spring.getSpringConfig().tension = sceneConfig.springTension; this.spring.setVelocity(velocity || sceneConfig.defaultTransitionVelocity); this.spring.setEndValue(1); - this._emitWillFocus(this.state.routeStack[this.state.presentedIndex]); }, /** @@ -458,6 +457,7 @@ var Navigator = React.createClass({ if (this.state.transitionQueue.length) { var queuedTransition = this.state.transitionQueue.shift(); this._enableScene(queuedTransition.destIndex); + this._emitWillFocus(this.state.routeStack[queuedTransition.destIndex]); this._transitionTo( queuedTransition.destIndex, queuedTransition.velocity, @@ -657,6 +657,7 @@ var Navigator = React.createClass({ } } else { // The gesture has enough velocity to complete, so we transition to the gesture's destination + this._emitWillFocus(this.state.routeStack[destIndex]); this._transitionTo( destIndex, transitionVelocity, @@ -844,6 +845,7 @@ var Navigator = React.createClass({ _jumpN: function(n) { var destIndex = this._getDestIndexWithinBounds(n); this._enableScene(destIndex); + this._emitWillFocus(this.state.routeStack[destIndex]); this._transitionTo(destIndex); }, @@ -876,6 +878,7 @@ var Navigator = React.createClass({ var nextAnimationConfigStack = activeAnimationConfigStack.concat([ this.props.configureScene(route), ]); + this._emitWillFocus(nextStack[destIndex]); this.setState({ idStack: nextIDStack, routeStack: nextStack, @@ -896,6 +899,7 @@ var Navigator = React.createClass({ ); var popIndex = this.state.presentedIndex - n; this._enableScene(popIndex); + this._emitWillFocus(this.state.routeStack[popIndex]); this._transitionTo( popIndex, null, // default velocity @@ -935,13 +939,15 @@ var Navigator = React.createClass({ nextRouteStack[index] = route; nextAnimationModeStack[index] = this.props.configureScene(route); + if (index === this.state.presentedIndex) { + this._emitWillFocus(route); + } this.setState({ idStack: nextIDStack, routeStack: nextRouteStack, sceneConfigStack: nextAnimationModeStack, }, () => { if (index === this.state.presentedIndex) { - this._emitWillFocus(route); this._emitDidFocus(route); } cb && cb(); From 3ec3312c7cc74d67a62b87fe32ad0a84db78ce93 Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Thu, 25 Jun 2015 21:02:53 -0700 Subject: [PATCH 08/11] [madman]: Fix MapView crashing problem. --- Libraries/Components/MapView/MapView.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Libraries/Components/MapView/MapView.js b/Libraries/Components/MapView/MapView.js index ea10b959e82ad9..da4350e4c268d4 100644 --- a/Libraries/Components/MapView/MapView.js +++ b/Libraries/Components/MapView/MapView.js @@ -261,6 +261,7 @@ if (Platform.OS === 'android') { var RCTMap = createReactNativeComponentClass({ validAttributes: merge( ReactNativeViewAttributes.UIView, { + active: true, showsUserLocation: true, zoomEnabled: true, rotateEnabled: true, From 1cca4fb769ccac403e85ac07da907e329cd59039 Mon Sep 17 00:00:00 2001 From: Owen Kelly Date: Fri, 26 Jun 2015 07:06:51 -0700 Subject: [PATCH 09/11] [NavigatorIOS] Allow translucent on NavigatorIOS Summary: Hi, I've updated the NavigatorIOS component to allow setting the translucent property. usage is: ``` ``` This is my first contrib to react-native, so apologies if I've missed something. Cheers, Owen Closes https://github.com/facebook/react-native/pull/1273 Github Author: Owen Kelly Test Plan: Imported from GitHub, without a `Test Plan:` line. --- Examples/UIExplorer/NavigatorIOSColorsExample.js | 1 + Libraries/Components/Navigation/NavigatorIOS.ios.js | 7 +++++++ React/Views/RCTNavItem.h | 1 + React/Views/RCTNavItemManager.m | 1 + React/Views/RCTWrapperViewController.m | 1 + 5 files changed, 11 insertions(+) diff --git a/Examples/UIExplorer/NavigatorIOSColorsExample.js b/Examples/UIExplorer/NavigatorIOSColorsExample.js index 0f695d956a8ded..74118690267f62 100644 --- a/Examples/UIExplorer/NavigatorIOSColorsExample.js +++ b/Examples/UIExplorer/NavigatorIOSColorsExample.js @@ -66,6 +66,7 @@ var NavigatorIOSColors = React.createClass({ tintColor="#FFFFFF" barTintColor="#183E63" titleTextColor="#FFFFFF" + translucent="true" /> ); }, diff --git a/Libraries/Components/Navigation/NavigatorIOS.ios.js b/Libraries/Components/Navigation/NavigatorIOS.ios.js index 9ecf6bdc8049a4..788273ec53e099 100644 --- a/Libraries/Components/Navigation/NavigatorIOS.ios.js +++ b/Libraries/Components/Navigation/NavigatorIOS.ios.js @@ -57,6 +57,7 @@ var RCTNavigatorItem = createReactNativeComponentClass({ backButtonIcon: true, backButtonTitle: true, tintColor: true, + translucent: true, navigationBarHidden: true, titleTextColor: true, style: true, @@ -300,6 +301,11 @@ var NavigatorIOS = React.createClass({ */ titleTextColor: PropTypes.string, + /** + * A Boolean value that indicates whether the navigation bar is translucent + */ + translucent: PropTypes.bool, + }, navigator: (undefined: ?Object), @@ -609,6 +615,7 @@ var NavigatorIOS = React.createClass({ navigationBarHidden={this.props.navigationBarHidden} tintColor={this.props.tintColor} barTintColor={this.props.barTintColor} + translucent={this.props.translucent} titleTextColor={this.props.titleTextColor}> Date: Fri, 26 Jun 2015 10:25:54 -0700 Subject: [PATCH 10/11] [ListView] Defer measurement one frame after componentDidMount to fix error Summary: When `UIManager.measure` is called from `componentDidMount` it causes the error "Attempted to measure layout but offset or dimensions were NaN". Deferring the layout by one frame solves this problem. Layout measurement is already asynchronous anyway, so I believe adding the `requestAnimationFrame` call doesn't affect the program's correctness. Fixes #1749 Closes https://github.com/facebook/react-native/pull/1750 Github Author: James Ide Test Plan: Load UIExplorer and no longer get a redbox that says "Attempted to measure layout but offset or dimensions were NaN". --- Libraries/CustomComponents/ListView/ListView.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js index dda32340c22fab..cebfac68e994e5 100644 --- a/Libraries/CustomComponents/ListView/ListView.js +++ b/Libraries/CustomComponents/ListView/ListView.js @@ -276,7 +276,9 @@ var ListView = React.createClass({ }, componentDidUpdate: function() { - this._measureAndUpdateScrollProps(); + this.requestAnimationFrame(() => { + this._measureAndUpdateScrollProps(); + }); }, onRowHighlighted: function(sectionID, rowID) { From 29e49bdb9151b02d91f106dd893e561ae96c77b1 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Fri, 26 Jun 2015 10:44:43 -0700 Subject: [PATCH 11/11] [ReactNative] LayoutAnimation brevity Summary: @public Less verbose - now can just do `LayoutAnimation.easeInEaseOut()` instead of `LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)` Test Plan: D2171336, play with AdsManager pickers. --- Libraries/Animation/LayoutAnimation.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Libraries/Animation/LayoutAnimation.js b/Libraries/Animation/LayoutAnimation.js index fbfc2d44f36da6..1a31f98d48559a 100644 --- a/Libraries/Animation/LayoutAnimation.js +++ b/Libraries/Animation/LayoutAnimation.js @@ -114,4 +114,10 @@ var LayoutAnimation = { } }; +for (var key in LayoutAnimation.Presets) { + LayoutAnimation[key] = LayoutAnimation.configureNext.bind( + null, LayoutAnimation.Presets[key] + ); +} + module.exports = LayoutAnimation;