Updates from Fri July 17th
This commit is contained in:
commit
4a5f12aa29
|
@ -0,0 +1,149 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var React = require('react-native');
|
||||||
|
var {
|
||||||
|
Image,
|
||||||
|
ListView,
|
||||||
|
TouchableHighlight,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
View,
|
||||||
|
} = React;
|
||||||
|
|
||||||
|
var THUMB_URLS = [
|
||||||
|
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-ash3/t39.1997/p128x128/851549_767334479959628_274486868_n.png',
|
||||||
|
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851561_767334496626293_1958532586_n.png',
|
||||||
|
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-ash3/t39.1997/p128x128/851579_767334503292959_179092627_n.png',
|
||||||
|
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851589_767334513292958_1747022277_n.png',
|
||||||
|
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851563_767334559959620_1193692107_n.png',
|
||||||
|
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851593_767334566626286_1953955109_n.png',
|
||||||
|
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851591_767334523292957_797560749_n.png',
|
||||||
|
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851567_767334529959623_843148472_n.png',
|
||||||
|
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851548_767334489959627_794462220_n.png',
|
||||||
|
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851575_767334539959622_441598241_n.png',
|
||||||
|
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-ash3/t39.1997/p128x128/851573_767334549959621_534583464_n.png',
|
||||||
|
'https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-prn1/t39.1997/p128x128/851583_767334573292952_1519550680_n.png',
|
||||||
|
];
|
||||||
|
|
||||||
|
var ListViewGridLayoutExample = React.createClass({
|
||||||
|
|
||||||
|
statics: {
|
||||||
|
title: '<ListView> - Grid Layout',
|
||||||
|
description: 'Flexbox grid layout.'
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
|
||||||
|
return {
|
||||||
|
dataSource: ds.cloneWithRows(this._genRows({})),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
_pressData: ({}: {[key: number]: boolean}),
|
||||||
|
|
||||||
|
componentWillMount: function() {
|
||||||
|
this._pressData = {};
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
// ListView wraps ScrollView and so takes on its properties.
|
||||||
|
// With that in mind you can use the ScrollView's contentContainerStyle prop to style the items.
|
||||||
|
<ListView
|
||||||
|
contentContainerStyle={styles.list}
|
||||||
|
dataSource={this.state.dataSource}
|
||||||
|
renderRow={this._renderRow}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderRow: function(rowData: string, sectionID: number, rowID: number) {
|
||||||
|
var rowHash = Math.abs(hashCode(rowData));
|
||||||
|
var imgSource = {
|
||||||
|
uri: THUMB_URLS[rowHash % THUMB_URLS.length],
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<TouchableHighlight onPress={() => this._pressRow(rowID)} underlayColor="transparent">
|
||||||
|
<View>
|
||||||
|
<View style={styles.row}>
|
||||||
|
<Image style={styles.thumb} source={imgSource} />
|
||||||
|
<Text style={styles.text}>
|
||||||
|
{rowData}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</TouchableHighlight>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
_genRows: function(pressData: {[key: number]: boolean}): Array<string> {
|
||||||
|
var dataBlob = [];
|
||||||
|
for (var ii = 0; ii < 100; ii++) {
|
||||||
|
var pressedText = pressData[ii] ? ' (X)' : '';
|
||||||
|
dataBlob.push('Cell ' + ii + pressedText);
|
||||||
|
}
|
||||||
|
return dataBlob;
|
||||||
|
},
|
||||||
|
|
||||||
|
_pressRow: function(rowID: number) {
|
||||||
|
this._pressData[rowID] = !this._pressData[rowID];
|
||||||
|
this.setState({dataSource: this.state.dataSource.cloneWithRows(
|
||||||
|
this._genRows(this._pressData)
|
||||||
|
)});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/* eslint no-bitwise: 0 */
|
||||||
|
var hashCode = function(str) {
|
||||||
|
var hash = 15;
|
||||||
|
for (var ii = str.length - 1; ii >= 0; ii--) {
|
||||||
|
hash = ((hash << 5) - hash) + str.charCodeAt(ii);
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
};
|
||||||
|
|
||||||
|
var styles = StyleSheet.create({
|
||||||
|
list: {
|
||||||
|
justifyContent: 'space-around',
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap'
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: 5,
|
||||||
|
margin: 3,
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
backgroundColor: '#F6F6F6',
|
||||||
|
alignItems: 'center',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 5,
|
||||||
|
borderColor: '#CCC'
|
||||||
|
},
|
||||||
|
thumb: {
|
||||||
|
width: 64,
|
||||||
|
height: 64
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
flex: 1,
|
||||||
|
marginTop: 5,
|
||||||
|
fontWeight: 'bold'
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = ListViewGridLayoutExample;
|
|
@ -15,46 +15,21 @@
|
||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
#import <XCTest/XCTest.h>
|
#import <XCTest/XCTest.h>
|
||||||
|
|
||||||
#import "RCTShadowView.h"
|
|
||||||
#import "RCTUIManager.h"
|
|
||||||
#import "RCTRootView.h"
|
|
||||||
#import "RCTSparseArray.h"
|
#import "RCTSparseArray.h"
|
||||||
|
#import "RCTUIManager.h"
|
||||||
#import "UIView+React.h"
|
#import "UIView+React.h"
|
||||||
|
|
||||||
@interface RCTUIManager (Testing)
|
@interface RCTUIManager (Testing)
|
||||||
|
|
||||||
- (void)_manageChildren:(NSNumber *)containerReactTag
|
- (void)_manageChildren:(NSNumber *)containerReactTag
|
||||||
|
moveFromIndices:(NSArray *)moveFromIndices
|
||||||
|
moveToIndices:(NSArray *)moveToIndices
|
||||||
addChildReactTags:(NSArray *)addChildReactTags
|
addChildReactTags:(NSArray *)addChildReactTags
|
||||||
addAtIndices:(NSArray *)addAtIndices
|
addAtIndices:(NSArray *)addAtIndices
|
||||||
removeAtIndices:(NSArray *)removeAtIndices
|
removeAtIndices:(NSArray *)removeAtIndices
|
||||||
registry:(RCTSparseArray *)registry;
|
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 *viewRegistry;
|
||||||
@property (nonatomic, readonly) RCTSparseArray *shadowViewRegistry; // RCT thread only
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@ -77,360 +52,140 @@
|
||||||
UIView *registeredView = [[UIView alloc] init];
|
UIView *registeredView = [[UIView alloc] init];
|
||||||
[registeredView setReactTag:@(i)];
|
[registeredView setReactTag:@(i)];
|
||||||
_uiManager.viewRegistry[i] = registeredView;
|
_uiManager.viewRegistry[i] = registeredView;
|
||||||
|
|
||||||
RCTShadowView *registeredShadowView = [[RCTShadowView alloc] init];
|
|
||||||
registeredShadowView.viewName = @"RCTView";
|
|
||||||
[registeredShadowView setReactTag:@(i)];
|
|
||||||
_uiManager.shadowViewRegistry[i] = registeredShadowView;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* +-----------------------------------------------------------+ +----------------------+
|
- (void)testManagingChildrenToAddViews
|
||||||
* | 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];
|
UIView *containerView = _uiManager.viewRegistry[20];
|
||||||
shadowView.allProps = isLayoutOnly ? @{} : @{@"collapsible": @NO};
|
NSMutableArray *addedViews = [NSMutableArray array];
|
||||||
[childTags enumerateObjectsUsingBlock:^(NSNumber *childTag, NSUInteger idx, __unused BOOL *stop) {
|
|
||||||
[shadowView insertReactSubview:_uiManager.shadowViewRegistry[childTag] atIndex:idx];
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setUpShadowViewHierarchy
|
NSArray *tagsToAdd = @[@1, @2, @3, @4, @5];
|
||||||
{
|
NSArray *addAtIndices = @[@0, @1, @2, @3, @4];
|
||||||
[self updateShadowViewWithReactTag:@1 layoutOnly:NO childTags:@[@2, @3, @4, @5]];
|
for (NSNumber *tag in tagsToAdd) {
|
||||||
[self updateShadowViewWithReactTag:@2 layoutOnly:YES childTags:@[@6, @7]];
|
[addedViews addObject:_uiManager.viewRegistry[tag]];
|
||||||
[self updateShadowViewWithReactTag:@3 layoutOnly:NO childTags:nil];
|
|
||||||
[self updateShadowViewWithReactTag:@4 layoutOnly:YES childTags:@[@8]];
|
|
||||||
[self updateShadowViewWithReactTag:@5 layoutOnly:NO childTags:@[@9, @10]];
|
|
||||||
[self updateShadowViewWithReactTag:@6 layoutOnly:NO childTags:nil];
|
|
||||||
[self updateShadowViewWithReactTag:@7 layoutOnly:NO childTags:nil];
|
|
||||||
[self updateShadowViewWithReactTag:@8 layoutOnly:NO childTags:nil];
|
|
||||||
[self updateShadowViewWithReactTag:@9 layoutOnly:NO childTags:nil];
|
|
||||||
[self updateShadowViewWithReactTag:@10 layoutOnly:YES childTags:nil];
|
|
||||||
[self updateShadowViewWithReactTag:@11 layoutOnly:YES childTags:@[@12, @13]];
|
|
||||||
[self updateShadowViewWithReactTag:@12 layoutOnly:NO childTags:nil];
|
|
||||||
[self updateShadowViewWithReactTag:@13 layoutOnly:NO childTags:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)testModifyIndices1
|
|
||||||
{
|
|
||||||
[self setUpShadowViewHierarchy];
|
|
||||||
|
|
||||||
NSMutableArray *addTags = [@[@2] mutableCopy];
|
|
||||||
NSMutableArray *addIndices = [@[@3] mutableCopy];
|
|
||||||
NSMutableArray *removeIndices = [@[@0] mutableCopy];
|
|
||||||
[self.uiManager modifyManageChildren:@1
|
|
||||||
addChildReactTags:addTags
|
|
||||||
addAtIndices:addIndices
|
|
||||||
removeAtIndices:removeIndices];
|
|
||||||
XCTAssertEqualObjects(addTags, (@[@6, @7]));
|
|
||||||
XCTAssertEqualObjects(addIndices, (@[@3, @4]));
|
|
||||||
XCTAssertEqualObjects(removeIndices, (@[@0, @1]));
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)testModifyIndices2
|
|
||||||
{
|
|
||||||
[self setUpShadowViewHierarchy];
|
|
||||||
|
|
||||||
NSMutableArray *addTags = [@[@11] mutableCopy];
|
|
||||||
NSMutableArray *addIndices = [@[@4] mutableCopy];
|
|
||||||
NSMutableArray *removeIndices = [@[] mutableCopy];
|
|
||||||
[self.uiManager modifyManageChildren:@1
|
|
||||||
addChildReactTags:addTags
|
|
||||||
addAtIndices:addIndices
|
|
||||||
removeAtIndices:removeIndices];
|
|
||||||
XCTAssertEqualObjects(addTags, (@[@12, @13]));
|
|
||||||
XCTAssertEqualObjects(addIndices, (@[@5, @6]));
|
|
||||||
XCTAssertEqualObjects(removeIndices, (@[]));
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)testModifyIndices3
|
|
||||||
{
|
|
||||||
[self setUpShadowViewHierarchy];
|
|
||||||
|
|
||||||
NSMutableArray *addTags = [@[] mutableCopy];
|
|
||||||
NSMutableArray *addIndices = [@[] mutableCopy];
|
|
||||||
NSMutableArray *removeIndices = [@[@2] mutableCopy];
|
|
||||||
[self.uiManager modifyManageChildren:@1
|
|
||||||
addChildReactTags:addTags
|
|
||||||
addAtIndices:addIndices
|
|
||||||
removeAtIndices:removeIndices];
|
|
||||||
XCTAssertEqualObjects(addTags, (@[]));
|
|
||||||
XCTAssertEqualObjects(addIndices, (@[]));
|
|
||||||
XCTAssertEqualObjects(removeIndices, (@[@3]));
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)testModifyIndices4
|
|
||||||
{
|
|
||||||
[self setUpShadowViewHierarchy];
|
|
||||||
|
|
||||||
NSMutableArray *addTags = [@[@11] mutableCopy];
|
|
||||||
NSMutableArray *addIndices = [@[@3] mutableCopy];
|
|
||||||
NSMutableArray *removeIndices = [@[@2] mutableCopy];
|
|
||||||
[self.uiManager modifyManageChildren:@1
|
|
||||||
addChildReactTags:addTags
|
|
||||||
addAtIndices:addIndices
|
|
||||||
removeAtIndices:removeIndices];
|
|
||||||
XCTAssertEqualObjects(addTags, (@[@12, @13]));
|
|
||||||
XCTAssertEqualObjects(addIndices, (@[@4, @5]));
|
|
||||||
XCTAssertEqualObjects(removeIndices, (@[@3]));
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)testModifyIndices5
|
|
||||||
{
|
|
||||||
[self setUpShadowViewHierarchy];
|
|
||||||
|
|
||||||
NSMutableArray *addTags = [@[] mutableCopy];
|
|
||||||
NSMutableArray *addIndices = [@[] mutableCopy];
|
|
||||||
NSMutableArray *removeIndices = [@[@0, @1, @2, @3] mutableCopy];
|
|
||||||
[self.uiManager modifyManageChildren:@1
|
|
||||||
addChildReactTags:addTags
|
|
||||||
addAtIndices:addIndices
|
|
||||||
removeAtIndices:removeIndices];
|
|
||||||
XCTAssertEqualObjects(addTags, (@[]));
|
|
||||||
XCTAssertEqualObjects(addIndices, (@[]));
|
|
||||||
XCTAssertEqualObjects(removeIndices, (@[@0, @1, @2, @3, @4]));
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)testModifyIndices6
|
|
||||||
{
|
|
||||||
[self setUpShadowViewHierarchy];
|
|
||||||
|
|
||||||
NSMutableArray *addTags = [@[@11] mutableCopy];
|
|
||||||
NSMutableArray *addIndices = [@[@0] mutableCopy];
|
|
||||||
NSMutableArray *removeIndices = [@[@0, @1, @2, @3] mutableCopy];
|
|
||||||
[self.uiManager modifyManageChildren:@1
|
|
||||||
addChildReactTags:addTags
|
|
||||||
addAtIndices:addIndices
|
|
||||||
removeAtIndices:removeIndices];
|
|
||||||
XCTAssertEqualObjects(addTags, (@[@12, @13]));
|
|
||||||
XCTAssertEqualObjects(addIndices, (@[@0, @1]));
|
|
||||||
XCTAssertEqualObjects(removeIndices, (@[@0, @1, @2, @3, @4]));
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)testModifyIndices7
|
|
||||||
{
|
|
||||||
[self setUpShadowViewHierarchy];
|
|
||||||
|
|
||||||
NSMutableArray *addTags = [@[@11] mutableCopy];
|
|
||||||
NSMutableArray *addIndices = [@[@1] mutableCopy];
|
|
||||||
NSMutableArray *removeIndices = [@[@0, @2, @3] mutableCopy];
|
|
||||||
[self.uiManager modifyManageChildren:@1
|
|
||||||
addChildReactTags:addTags
|
|
||||||
addAtIndices:addIndices
|
|
||||||
removeAtIndices:removeIndices];
|
|
||||||
XCTAssertEqualObjects(addTags, (@[@12, @13]));
|
|
||||||
XCTAssertEqualObjects(addIndices, (@[@1, @2]));
|
|
||||||
XCTAssertEqualObjects(removeIndices, (@[@0, @1, @3, @4]));
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)DISABLED_testScenario1
|
|
||||||
{
|
|
||||||
RCTUIManager *uiManager = [[RCTUIManager alloc] init];
|
|
||||||
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[uiManager]; } launchOptions:nil];
|
|
||||||
NS_VALID_UNTIL_END_OF_SCOPE RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"Test"];
|
|
||||||
|
|
||||||
__block BOOL done = NO;
|
|
||||||
|
|
||||||
dispatch_queue_t shadowQueue = [uiManager valueForKey:@"shadowQueue"];
|
|
||||||
dispatch_async(shadowQueue, ^{
|
|
||||||
// Make sure root view finishes loading.
|
|
||||||
dispatch_sync(dispatch_get_main_queue(), ^{});
|
|
||||||
|
|
||||||
/* */[uiManager createView:@2 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}];
|
|
||||||
/* */[uiManager createView:@3 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}];
|
|
||||||
/* V */[uiManager createView:@4 viewName:@"RCTView" rootTag:@1 props:@{@"alignItems":@"center",@"backgroundColor":@"#F5FCFF",@"flex":@1,@"justifyContent":@"center"}];
|
|
||||||
/* V */[uiManager createView:@5 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"blue",@"height":@50,@"width":@50}];
|
|
||||||
/* */[uiManager createView:@6 viewName:@"RCTView" rootTag:@1 props:@{@"width":@250}];
|
|
||||||
/* V */[uiManager createView:@7 viewName:@"RCTView" rootTag:@1 props:@{@"borderWidth":@10,@"margin":@50}];
|
|
||||||
/* V */[uiManager createView:@8 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"yellow",@"height":@50}];
|
|
||||||
/* V */[uiManager createView:@9 viewName:@"RCTText" rootTag:@1 props:@{@"accessible":@1,@"fontSize":@20,@"isHighlighted":@0,@"margin":@10,@"textAlign":@"center"}];
|
|
||||||
/* */[uiManager createView:@10 viewName:@"RCTRawText" rootTag:@1 props:@{@"text":@"This tests removal of layout-only views."}];
|
|
||||||
/* */[uiManager manageChildren:@9 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@10] addAtIndices:@[@0] removeAtIndices:nil];
|
|
||||||
/* V */[uiManager createView:@12 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"green",@"height":@50}];
|
|
||||||
/* */[uiManager manageChildren:@7 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@8,@9,@12] addAtIndices:@[@0,@1,@2] removeAtIndices:nil];
|
|
||||||
/* */[uiManager manageChildren:@6 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@7] addAtIndices:@[@0] removeAtIndices:nil];
|
|
||||||
/* V */[uiManager createView:@13 viewName:@"RCTView" rootTag:@1 props:@{@"backgroundColor":@"red",@"height":@50,@"width":@50}];
|
|
||||||
/* */[uiManager manageChildren:@4 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@5,@6,@13] addAtIndices:@[@0,@1,@2] removeAtIndices:nil];
|
|
||||||
/* */[uiManager manageChildren:@3 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@4] addAtIndices:@[@0] removeAtIndices:nil];
|
|
||||||
/* */[uiManager manageChildren:@2 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@3] addAtIndices:@[@0] removeAtIndices:nil];
|
|
||||||
/* */[uiManager manageChildren:@1 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@2] addAtIndices:@[@0] removeAtIndices:nil];
|
|
||||||
|
|
||||||
[uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) {
|
|
||||||
XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)12);
|
|
||||||
XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)8);
|
|
||||||
done = YES;
|
|
||||||
}];
|
|
||||||
|
|
||||||
[uiManager flushUIBlocks];
|
|
||||||
});
|
|
||||||
|
|
||||||
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:1.0];
|
|
||||||
while ([date timeIntervalSinceNow] > 0 && !done) {
|
|
||||||
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
|
||||||
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
done = NO;
|
// Add views 1-5 to view 20
|
||||||
dispatch_async(shadowQueue, ^{
|
[_uiManager _manageChildren:@20
|
||||||
[uiManager updateView:@7 viewName:@"RCTView" props:@{@"borderWidth":[NSNull null]}];
|
moveFromIndices:nil
|
||||||
[uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) {
|
moveToIndices:nil
|
||||||
XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)12);
|
addChildReactTags:tagsToAdd
|
||||||
XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)7);
|
addAtIndices:addAtIndices
|
||||||
done = YES;
|
removeAtIndices:nil
|
||||||
}];
|
registry:_uiManager.viewRegistry];
|
||||||
|
|
||||||
[uiManager flushUIBlocks];
|
XCTAssertTrue([[containerView reactSubviews] count] == 5,
|
||||||
});
|
@"Expect to have 5 react subviews after calling manage children \
|
||||||
|
with 5 tags to add, instead have %lu", (unsigned long)[[containerView reactSubviews] count]);
|
||||||
date = [NSDate dateWithTimeIntervalSinceNow:1.0];
|
for (UIView *view in addedViews) {
|
||||||
while ([date timeIntervalSinceNow] > 0 && !done) {
|
XCTAssertTrue([view superview] == containerView,
|
||||||
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
@"Expected to have manage children successfully add children");
|
||||||
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
[view removeFromSuperview];
|
||||||
}
|
|
||||||
|
|
||||||
done = NO;
|
|
||||||
dispatch_async(shadowQueue, ^{
|
|
||||||
[uiManager updateView:@7 viewName:@"RCTView" props:@{@"borderWidth":@10}];
|
|
||||||
[uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) {
|
|
||||||
XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)12);
|
|
||||||
XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)8);
|
|
||||||
done = YES;
|
|
||||||
}];
|
|
||||||
|
|
||||||
[uiManager flushUIBlocks];
|
|
||||||
});
|
|
||||||
|
|
||||||
date = [NSDate dateWithTimeIntervalSinceNow:1.0];
|
|
||||||
while ([date timeIntervalSinceNow] > 0 && !done) {
|
|
||||||
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
|
||||||
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)DISABLED_testScenario2
|
- (void)testManagingChildrenToRemoveViews
|
||||||
{
|
{
|
||||||
RCTUIManager *uiManager = [[RCTUIManager alloc] init];
|
UIView *containerView = _uiManager.viewRegistry[20];
|
||||||
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[uiManager]; } launchOptions:nil];
|
NSMutableArray *removedViews = [NSMutableArray array];
|
||||||
NS_VALID_UNTIL_END_OF_SCOPE RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"Test"];
|
|
||||||
|
|
||||||
__block BOOL done = NO;
|
NSArray *removeAtIndices = @[@0, @4, @8, @12, @16];
|
||||||
|
for (NSNumber *index in removeAtIndices) {
|
||||||
dispatch_queue_t shadowQueue = [uiManager valueForKey:@"shadowQueue"];
|
NSNumber *reactTag = @([index integerValue] + 2);
|
||||||
dispatch_async(shadowQueue, ^{
|
[removedViews addObject:_uiManager.viewRegistry[reactTag]];
|
||||||
// Make sure root view finishes loading.
|
}
|
||||||
dispatch_sync(dispatch_get_main_queue(), ^{});
|
for (NSInteger i = 2; i < 20; i++) {
|
||||||
|
UIView *view = _uiManager.viewRegistry[i];
|
||||||
/* */[uiManager createView:@2 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}];
|
[containerView addSubview:view];
|
||||||
/* */[uiManager createView:@3 viewName:@"RCTView" rootTag:@1 props:@{@"bottom":@0,@"left":@0,@"position":@"absolute",@"right":@0,@"top":@0}];
|
|
||||||
/* V */[uiManager createView:@4 viewName:@"RCTView" rootTag:@1 props:@{@"alignItems":@"center",@"backgroundColor":@"#F5FCFF",@"flex":@1,@"justifyContent":@"center"}];
|
|
||||||
/* */[uiManager createView:@5 viewName:@"RCTView" rootTag:@1 props:@{@"width":@250}];
|
|
||||||
/* V */[uiManager createView:@6 viewName:@"RCTView" rootTag:@1 props:@{@"borderWidth":@1}];
|
|
||||||
/* V */[uiManager createView:@7 viewName:@"RCTText" rootTag:@1 props:@{@"accessible":@1,@"fontSize":@20,@"isHighlighted":@0,@"margin":@10,@"textAlign":@"center"}];
|
|
||||||
/* */[uiManager createView:@8 viewName:@"RCTRawText" rootTag:@1 props:@{@"text":@"This tests removal of layout-only views."}];
|
|
||||||
/* */[uiManager manageChildren:@7 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@8] addAtIndices:@[@0] removeAtIndices:nil];
|
|
||||||
/* */[uiManager manageChildren:@6 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@7] addAtIndices:@[@0] removeAtIndices:nil];
|
|
||||||
/* */[uiManager manageChildren:@5 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@6] addAtIndices:@[@0] removeAtIndices:nil];
|
|
||||||
/* */[uiManager manageChildren:@4 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@5] addAtIndices:@[@0] removeAtIndices:nil];
|
|
||||||
/* */[uiManager manageChildren:@3 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@4] addAtIndices:@[@0] removeAtIndices:nil];
|
|
||||||
/* */[uiManager manageChildren:@2 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@3] addAtIndices:@[@0] removeAtIndices:nil];
|
|
||||||
/* */[uiManager manageChildren:@1 moveFromIndices:nil moveToIndices:nil addChildReactTags:@[@2] addAtIndices:@[@0] removeAtIndices:nil];
|
|
||||||
|
|
||||||
[uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) {
|
|
||||||
XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)8);
|
|
||||||
XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)4);
|
|
||||||
done = YES;
|
|
||||||
}];
|
|
||||||
|
|
||||||
[uiManager flushUIBlocks];
|
|
||||||
});
|
|
||||||
|
|
||||||
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:1.0];
|
|
||||||
while ([date timeIntervalSinceNow] > 0 && !done) {
|
|
||||||
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
|
||||||
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
done = NO;
|
// Remove views 1-5 from view 20
|
||||||
dispatch_async(shadowQueue, ^{
|
[_uiManager _manageChildren:@20
|
||||||
[uiManager updateView:@6 viewName:@"RCTView" props:@{@"borderWidth":[NSNull null]}];
|
moveFromIndices:nil
|
||||||
[uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) {
|
moveToIndices:nil
|
||||||
XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)8);
|
addChildReactTags:nil
|
||||||
XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)3);
|
addAtIndices:nil
|
||||||
done = YES;
|
removeAtIndices:removeAtIndices
|
||||||
}];
|
registry:_uiManager.viewRegistry];
|
||||||
|
|
||||||
[uiManager flushUIBlocks];
|
XCTAssertEqual(containerView.reactSubviews.count, (NSUInteger)13,
|
||||||
});
|
@"Expect to have 13 react subviews after calling manage children\
|
||||||
|
with 5 tags to remove and 18 prior children, instead have %zd",
|
||||||
|
containerView.reactSubviews.count);
|
||||||
|
for (UIView *view in removedViews) {
|
||||||
|
XCTAssertTrue([view superview] == nil,
|
||||||
|
@"Expected to have manage children successfully remove children");
|
||||||
|
// After removing views are unregistered - we need to reregister
|
||||||
|
_uiManager.viewRegistry[view.reactTag] = view;
|
||||||
|
}
|
||||||
|
for (NSInteger i = 2; i < 20; i++) {
|
||||||
|
UIView *view = _uiManager.viewRegistry[i];
|
||||||
|
if (![removedViews containsObject:view]) {
|
||||||
|
XCTAssertTrue([view superview] == containerView,
|
||||||
|
@"Should not have removed view with react tag %ld during delete but did", (long)i);
|
||||||
|
[view removeFromSuperview];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
date = [NSDate dateWithTimeIntervalSinceNow:1.0];
|
// We want to start with views 1-10 added at indices 0-9
|
||||||
while ([date timeIntervalSinceNow] > 0 && !done) {
|
// Then we'll remove indices 2, 3, 5 and 8
|
||||||
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
// Add views 11 and 12 to indices 0 and 6
|
||||||
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
// And move indices 4 and 9 to 1 and 7
|
||||||
|
// So in total it goes from:
|
||||||
|
// [1,2,3,4,5,6,7,8,9,10]
|
||||||
|
// to
|
||||||
|
// [11,5,1,2,7,8,12,10]
|
||||||
|
- (void)testManagingChildrenToAddRemoveAndMove
|
||||||
|
{
|
||||||
|
UIView *containerView = _uiManager.viewRegistry[20];
|
||||||
|
|
||||||
|
NSArray *removeAtIndices = @[@2, @3, @5, @8];
|
||||||
|
NSArray *addAtIndices = @[@0, @6];
|
||||||
|
NSArray *tagsToAdd = @[@11, @12];
|
||||||
|
NSArray *moveFromIndices = @[@4, @9];
|
||||||
|
NSArray *moveToIndices = @[@1, @7];
|
||||||
|
|
||||||
|
// We need to keep these in array to keep them around
|
||||||
|
NSMutableArray *viewsToRemove = [NSMutableArray array];
|
||||||
|
for (NSUInteger i = 0; i < removeAtIndices.count; i++) {
|
||||||
|
NSNumber *reactTagToRemove = @([removeAtIndices[i] integerValue] + 1);
|
||||||
|
UIView *viewToRemove = _uiManager.viewRegistry[reactTagToRemove];
|
||||||
|
[viewsToRemove addObject:viewToRemove];
|
||||||
}
|
}
|
||||||
|
|
||||||
done = NO;
|
for (NSInteger i = 1; i < 11; i++) {
|
||||||
dispatch_async(shadowQueue, ^{
|
UIView *view = _uiManager.viewRegistry[i];
|
||||||
[uiManager updateView:@6 viewName:@"RCTView" props:@{@"borderWidth":@1}];
|
[containerView addSubview:view];
|
||||||
[uiManager addUIBlock:^(RCTUIManager *uiManager_, __unused RCTSparseArray *viewRegistry) {
|
}
|
||||||
XCTAssertEqual(uiManager_.shadowViewRegistry.count, (NSUInteger)8);
|
|
||||||
XCTAssertEqual(uiManager_.viewRegistry.count, (NSUInteger)4);
|
|
||||||
done = YES;
|
|
||||||
}];
|
|
||||||
|
|
||||||
[uiManager flushUIBlocks];
|
[_uiManager _manageChildren:@20
|
||||||
});
|
moveFromIndices:moveFromIndices
|
||||||
|
moveToIndices:moveToIndices
|
||||||
|
addChildReactTags:tagsToAdd
|
||||||
|
addAtIndices:addAtIndices
|
||||||
|
removeAtIndices:removeAtIndices
|
||||||
|
registry:_uiManager.viewRegistry];
|
||||||
|
|
||||||
date = [NSDate dateWithTimeIntervalSinceNow:1.0];
|
XCTAssertTrue([[containerView reactSubviews] count] == 8,
|
||||||
while ([date timeIntervalSinceNow] > 0 && !done) {
|
@"Expect to have 8 react subviews after calling manage children,\
|
||||||
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
instead have the following subviews %@", [containerView reactSubviews]);
|
||||||
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
|
||||||
|
NSArray *expectedReactTags = @[@11, @5, @1, @2, @7, @8, @12, @10];
|
||||||
|
for (NSUInteger i = 0; i < containerView.subviews.count; i++) {
|
||||||
|
XCTAssertEqualObjects([[containerView reactSubviews][i] reactTag], expectedReactTags[i],
|
||||||
|
@"Expected subview at index %ld to have react tag #%@ but has tag #%@",
|
||||||
|
(long)i, expectedReactTags[i], [[containerView reactSubviews][i] reactTag]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up after ourselves
|
||||||
|
for (NSInteger i = 1; i < 13; i++) {
|
||||||
|
UIView *view = _uiManager.viewRegistry[i];
|
||||||
|
[view removeFromSuperview];
|
||||||
|
}
|
||||||
|
for (UIView *view in viewsToRemove) {
|
||||||
|
_uiManager.viewRegistry[view.reactTag] = view;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 97 KiB |
|
@ -40,6 +40,7 @@ var COMMON_COMPONENTS = [
|
||||||
require('./ImageExample'),
|
require('./ImageExample'),
|
||||||
require('./LayoutEventsExample'),
|
require('./LayoutEventsExample'),
|
||||||
require('./ListViewExample'),
|
require('./ListViewExample'),
|
||||||
|
require('./ListViewGridLayoutExample'),
|
||||||
require('./ListViewPagingExample'),
|
require('./ListViewPagingExample'),
|
||||||
require('./MapViewExample'),
|
require('./MapViewExample'),
|
||||||
require('./Navigator/NavigatorExample'),
|
require('./Navigator/NavigatorExample'),
|
||||||
|
|
|
@ -19,17 +19,15 @@
|
||||||
#import "RCTContextExecutor.h"
|
#import "RCTContextExecutor.h"
|
||||||
#import "RCTRootView.h"
|
#import "RCTRootView.h"
|
||||||
|
|
||||||
#define RUN_RUNLOOP_WHILE(CONDITION, TIMEOUT) \
|
#define RUN_RUNLOOP_WHILE(CONDITION) \
|
||||||
_Pragma("clang diagnostic push") \
|
_Pragma("clang diagnostic push") \
|
||||||
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
|
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
|
||||||
NSDate *timeout = [[NSDate date] dateByAddingTimeInterval:TIMEOUT]; \
|
NSDate *timeout = [[NSDate date] dateByAddingTimeInterval:0.1]; \
|
||||||
while ((CONDITION) && [timeout timeIntervalSinceNow] > 0) { \
|
while ((CONDITION) && [timeout timeIntervalSinceNow] > 0) { \
|
||||||
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeout]; \
|
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeout]; \
|
||||||
} \
|
} \
|
||||||
_Pragma("clang diagnostic pop")
|
_Pragma("clang diagnostic pop")
|
||||||
|
|
||||||
#define DEFAULT_TIMEOUT 2
|
|
||||||
|
|
||||||
@interface RCTBridge (RCTAllocationTests)
|
@interface RCTBridge (RCTAllocationTests)
|
||||||
|
|
||||||
@property (nonatomic, weak) RCTBridge *batchedBridge;
|
@property (nonatomic, weak) RCTBridge *batchedBridge;
|
||||||
|
@ -83,7 +81,6 @@ RCT_EXPORT_MODULE();
|
||||||
(void)view;
|
(void)view;
|
||||||
}
|
}
|
||||||
|
|
||||||
sleep(DEFAULT_TIMEOUT);
|
|
||||||
XCTAssertNil(weakBridge, @"RCTBridge should have been deallocated");
|
XCTAssertNil(weakBridge, @"RCTBridge should have been deallocated");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,8 +101,7 @@ RCT_EXPORT_MODULE();
|
||||||
* Sleep on the main thread to allow js thread deallocations then run the runloop
|
* Sleep on the main thread to allow js thread deallocations then run the runloop
|
||||||
* to allow the module to be deallocated on the main thread
|
* to allow the module to be deallocated on the main thread
|
||||||
*/
|
*/
|
||||||
sleep(1);
|
RUN_RUNLOOP_WHILE(module.isValid)
|
||||||
RUN_RUNLOOP_WHILE(module.isValid, 1)
|
|
||||||
XCTAssertFalse(module.isValid, @"AllocationTestModule should have been invalidated by the bridge");
|
XCTAssertFalse(module.isValid, @"AllocationTestModule should have been invalidated by the bridge");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,12 +120,7 @@ RCT_EXPORT_MODULE();
|
||||||
(void)bridge;
|
(void)bridge;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
RUN_RUNLOOP_WHILE(weakModule)
|
||||||
* Sleep on the main thread to allow js thread deallocations then run the runloop
|
|
||||||
* to allow the module to be deallocated on the main thread
|
|
||||||
*/
|
|
||||||
sleep(1);
|
|
||||||
RUN_RUNLOOP_WHILE(weakModule, 1)
|
|
||||||
XCTAssertNil(weakModule, @"AllocationTestModule should have been deallocated");
|
XCTAssertNil(weakModule, @"AllocationTestModule should have been deallocated");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,8 +136,7 @@ RCT_EXPORT_MODULE();
|
||||||
(void)bridge;
|
(void)bridge;
|
||||||
}
|
}
|
||||||
|
|
||||||
RUN_RUNLOOP_WHILE(weakExecutor, 1);
|
RUN_RUNLOOP_WHILE(weakExecutor);
|
||||||
sleep(1);
|
|
||||||
XCTAssertNil(weakExecutor, @"JavaScriptExecutor should have been released");
|
XCTAssertNil(weakExecutor, @"JavaScriptExecutor should have been released");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,13 +148,12 @@ RCT_EXPORT_MODULE();
|
||||||
moduleProvider:nil
|
moduleProvider:nil
|
||||||
launchOptions:nil];
|
launchOptions:nil];
|
||||||
id executor = [bridge.batchedBridge valueForKey:@"javaScriptExecutor"];
|
id executor = [bridge.batchedBridge valueForKey:@"javaScriptExecutor"];
|
||||||
RUN_RUNLOOP_WHILE(!(weakContext = [executor valueForKey:@"context"]), DEFAULT_TIMEOUT);
|
RUN_RUNLOOP_WHILE(!(weakContext = [executor valueForKey:@"context"]));
|
||||||
XCTAssertNotNil(weakContext, @"RCTJavaScriptContext should have been created");
|
XCTAssertNotNil(weakContext, @"RCTJavaScriptContext should have been created");
|
||||||
(void)bridge;
|
(void)bridge;
|
||||||
}
|
}
|
||||||
|
|
||||||
RUN_RUNLOOP_WHILE(weakContext, 1);
|
RUN_RUNLOOP_WHILE(weakContext);
|
||||||
sleep(1);
|
|
||||||
XCTAssertNil(weakContext, @"RCTJavaScriptContext should have been deallocated");
|
XCTAssertNil(weakContext, @"RCTJavaScriptContext should have been deallocated");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,12 +165,11 @@ RCT_EXPORT_MODULE();
|
||||||
__weak id rootContentView;
|
__weak id rootContentView;
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@""];
|
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@""];
|
||||||
RUN_RUNLOOP_WHILE(!(rootContentView = [rootView valueForKey:@"contentView"]), DEFAULT_TIMEOUT)
|
RUN_RUNLOOP_WHILE(!(rootContentView = [rootView valueForKey:@"contentView"]))
|
||||||
XCTAssertTrue([rootContentView isValid], @"RCTContentView should be valid");
|
XCTAssertTrue([rootContentView isValid], @"RCTContentView should be valid");
|
||||||
(void)rootView;
|
(void)rootView;
|
||||||
}
|
}
|
||||||
|
|
||||||
sleep(DEFAULT_TIMEOUT);
|
|
||||||
XCTAssertFalse([rootContentView isValid], @"RCTContentView should have been invalidated");
|
XCTAssertFalse([rootContentView isValid], @"RCTContentView should have been invalidated");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,8 +184,7 @@ RCT_EXPORT_MODULE();
|
||||||
[bridge reload];
|
[bridge reload];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use RUN_RUNLOOP_WHILE because `batchedBridge` deallocates on the main thread.
|
RUN_RUNLOOP_WHILE(batchedBridge != nil)
|
||||||
RUN_RUNLOOP_WHILE(batchedBridge != nil, DEFAULT_TIMEOUT)
|
|
||||||
|
|
||||||
XCTAssertNotNil(bridge, @"RCTBridge should not have been deallocated");
|
XCTAssertNotNil(bridge, @"RCTBridge should not have been deallocated");
|
||||||
XCTAssertNil(batchedBridge, @"RCTBatchedBridge should have been deallocated");
|
XCTAssertNil(batchedBridge, @"RCTBatchedBridge should have been deallocated");
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#import "RCTContextExecutor.h"
|
#import "RCTContextExecutor.h"
|
||||||
#import "RCTUtils.h"
|
#import "RCTUtils.h"
|
||||||
|
|
||||||
|
#define RUN_PERF_TESTS 0
|
||||||
|
|
||||||
@interface RCTContextExecutorTests : XCTestCase
|
@interface RCTContextExecutorTests : XCTestCase
|
||||||
|
|
||||||
|
@ -48,6 +49,8 @@
|
||||||
[_executor invalidate];
|
[_executor invalidate];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if RUN_PERF_TESTS
|
||||||
|
|
||||||
static uint64_t _get_time_nanoseconds(void)
|
static uint64_t _get_time_nanoseconds(void)
|
||||||
{
|
{
|
||||||
static struct mach_timebase_info tb_info = {0, 0};
|
static struct mach_timebase_info tb_info = {0, 0};
|
||||||
|
@ -91,7 +94,7 @@ static uint64_t _get_time_nanoseconds(void)
|
||||||
JSContextGroupRelease(group);
|
JSContextGroupRelease(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)MANUALLY_testJavaScriptCallSpeed
|
- (void)testJavaScriptCallSpeed
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Since we almost don't change the RCTContextExecutor logic, and this test is
|
* Since we almost don't change the RCTContextExecutor logic, and this test is
|
||||||
|
@ -200,4 +203,6 @@ static uint64_t _get_time_nanoseconds(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -14,8 +14,6 @@
|
||||||
|
|
||||||
#import <XCTest/XCTest.h>
|
#import <XCTest/XCTest.h>
|
||||||
|
|
||||||
#import "RCTRootView.h"
|
|
||||||
#import "RCTShadowView.h"
|
|
||||||
#import "RCTSparseArray.h"
|
#import "RCTSparseArray.h"
|
||||||
#import "RCTUIManager.h"
|
#import "RCTUIManager.h"
|
||||||
#import "UIView+React.h"
|
#import "UIView+React.h"
|
||||||
|
@ -23,13 +21,14 @@
|
||||||
@interface RCTUIManager (Testing)
|
@interface RCTUIManager (Testing)
|
||||||
|
|
||||||
- (void)_manageChildren:(NSNumber *)containerReactTag
|
- (void)_manageChildren:(NSNumber *)containerReactTag
|
||||||
|
moveFromIndices:(NSArray *)moveFromIndices
|
||||||
|
moveToIndices:(NSArray *)moveToIndices
|
||||||
addChildReactTags:(NSArray *)addChildReactTags
|
addChildReactTags:(NSArray *)addChildReactTags
|
||||||
addAtIndices:(NSArray *)addAtIndices
|
addAtIndices:(NSArray *)addAtIndices
|
||||||
removeAtIndices:(NSArray *)removeAtIndices
|
removeAtIndices:(NSArray *)removeAtIndices
|
||||||
registry:(RCTSparseArray *)registry;
|
registry:(RCTSparseArray *)registry;
|
||||||
|
|
||||||
@property (nonatomic, readonly) RCTSparseArray *viewRegistry;
|
@property (nonatomic, readonly) RCTSparseArray *viewRegistry;
|
||||||
@property (nonatomic, readonly) RCTSparseArray *shadowViewRegistry; // RCT thread only
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@ -52,11 +51,6 @@
|
||||||
UIView *registeredView = [[UIView alloc] init];
|
UIView *registeredView = [[UIView alloc] init];
|
||||||
[registeredView setReactTag:@(i)];
|
[registeredView setReactTag:@(i)];
|
||||||
_uiManager.viewRegistry[i] = registeredView;
|
_uiManager.viewRegistry[i] = registeredView;
|
||||||
|
|
||||||
RCTShadowView *registeredShadowView = [[RCTShadowView alloc] init];
|
|
||||||
registeredShadowView.viewName = @"RCTView";
|
|
||||||
[registeredShadowView setReactTag:@(i)];
|
|
||||||
_uiManager.shadowViewRegistry[i] = registeredShadowView;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +67,8 @@
|
||||||
|
|
||||||
// Add views 1-5 to view 20
|
// Add views 1-5 to view 20
|
||||||
[_uiManager _manageChildren:@20
|
[_uiManager _manageChildren:@20
|
||||||
|
moveFromIndices:nil
|
||||||
|
moveToIndices:nil
|
||||||
addChildReactTags:tagsToAdd
|
addChildReactTags:tagsToAdd
|
||||||
addAtIndices:addAtIndices
|
addAtIndices:addAtIndices
|
||||||
removeAtIndices:nil
|
removeAtIndices:nil
|
||||||
|
@ -105,6 +101,8 @@
|
||||||
|
|
||||||
// Remove views 1-5 from view 20
|
// Remove views 1-5 from view 20
|
||||||
[_uiManager _manageChildren:@20
|
[_uiManager _manageChildren:@20
|
||||||
|
moveFromIndices:nil
|
||||||
|
moveToIndices:nil
|
||||||
addChildReactTags:nil
|
addChildReactTags:nil
|
||||||
addAtIndices:nil
|
addAtIndices:nil
|
||||||
removeAtIndices:removeAtIndices
|
removeAtIndices:removeAtIndices
|
||||||
|
@ -142,9 +140,11 @@
|
||||||
{
|
{
|
||||||
UIView *containerView = _uiManager.viewRegistry[20];
|
UIView *containerView = _uiManager.viewRegistry[20];
|
||||||
|
|
||||||
NSArray *removeAtIndices = @[@2, @3, @5, @8, @4, @9];
|
NSArray *removeAtIndices = @[@2, @3, @5, @8];
|
||||||
NSArray *addAtIndices = @[@0, @6, @1, @7];
|
NSArray *addAtIndices = @[@0, @6];
|
||||||
NSArray *tagsToAdd = @[@11, @12, @5, @10];
|
NSArray *tagsToAdd = @[@11, @12];
|
||||||
|
NSArray *moveFromIndices = @[@4, @9];
|
||||||
|
NSArray *moveToIndices = @[@1, @7];
|
||||||
|
|
||||||
// We need to keep these in array to keep them around
|
// We need to keep these in array to keep them around
|
||||||
NSMutableArray *viewsToRemove = [NSMutableArray array];
|
NSMutableArray *viewsToRemove = [NSMutableArray array];
|
||||||
|
@ -160,6 +160,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
[_uiManager _manageChildren:@20
|
[_uiManager _manageChildren:@20
|
||||||
|
moveFromIndices:moveFromIndices
|
||||||
|
moveToIndices:moveToIndices
|
||||||
addChildReactTags:tagsToAdd
|
addChildReactTags:tagsToAdd
|
||||||
addAtIndices:addAtIndices
|
addAtIndices:addAtIndices
|
||||||
removeAtIndices:removeAtIndices
|
removeAtIndices:removeAtIndices
|
||||||
|
|
|
@ -60,6 +60,7 @@ var RCTNavigatorItem = createReactNativeComponentClass({
|
||||||
tintColor: true,
|
tintColor: true,
|
||||||
translucent: true,
|
translucent: true,
|
||||||
navigationBarHidden: true,
|
navigationBarHidden: true,
|
||||||
|
shadowHidden: true,
|
||||||
titleTextColor: true,
|
titleTextColor: true,
|
||||||
style: true,
|
style: true,
|
||||||
},
|
},
|
||||||
|
@ -281,6 +282,11 @@ var NavigatorIOS = React.createClass({
|
||||||
*/
|
*/
|
||||||
navigationBarHidden: PropTypes.bool,
|
navigationBarHidden: PropTypes.bool,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Boolean value that indicates whether to hide the 1px hairline shadow
|
||||||
|
*/
|
||||||
|
shadowHidden: PropTypes.bool,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default wrapper style for components in the navigator.
|
* The default wrapper style for components in the navigator.
|
||||||
* A common use case is to set the backgroundColor for every page
|
* A common use case is to set the backgroundColor for every page
|
||||||
|
@ -640,6 +646,7 @@ var NavigatorIOS = React.createClass({
|
||||||
rightButtonTitle={route.rightButtonTitle}
|
rightButtonTitle={route.rightButtonTitle}
|
||||||
onNavRightButtonTap={route.onRightButtonPress}
|
onNavRightButtonTap={route.onRightButtonPress}
|
||||||
navigationBarHidden={this.props.navigationBarHidden}
|
navigationBarHidden={this.props.navigationBarHidden}
|
||||||
|
shadowHidden={this.props.shadowHidden}
|
||||||
tintColor={this.props.tintColor}
|
tintColor={this.props.tintColor}
|
||||||
barTintColor={this.props.barTintColor}
|
barTintColor={this.props.barTintColor}
|
||||||
translucent={this.props.translucent !== false}
|
translucent={this.props.translucent !== false}
|
||||||
|
|
|
@ -17,7 +17,6 @@ var PointPropType = require('PointPropType');
|
||||||
var RCTScrollView = require('NativeModules').UIManager.RCTScrollView;
|
var RCTScrollView = require('NativeModules').UIManager.RCTScrollView;
|
||||||
var RCTScrollViewConsts = RCTScrollView.Constants;
|
var RCTScrollViewConsts = RCTScrollView.Constants;
|
||||||
var React = require('React');
|
var React = require('React');
|
||||||
var ReactChildren = require('ReactChildren');
|
|
||||||
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
||||||
var RCTUIManager = require('NativeModules').UIManager;
|
var RCTUIManager = require('NativeModules').UIManager;
|
||||||
var ScrollResponder = require('ScrollResponder');
|
var ScrollResponder = require('ScrollResponder');
|
||||||
|
@ -278,24 +277,12 @@ var ScrollView = React.createClass({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var children = this.props.children;
|
|
||||||
if (this.props.stickyHeaderIndices) {
|
|
||||||
children = ReactChildren.map(children, (child) => {
|
|
||||||
if (child) {
|
|
||||||
return <View collapsible={false}>{child}</View>;
|
|
||||||
} else {
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var contentContainer =
|
var contentContainer =
|
||||||
<View
|
<View
|
||||||
collapsible={false}
|
|
||||||
ref={INNERVIEW}
|
ref={INNERVIEW}
|
||||||
style={contentContainerStyle}
|
style={contentContainerStyle}
|
||||||
removeClippedSubviews={this.props.removeClippedSubviews}>
|
removeClippedSubviews={this.props.removeClippedSubviews}>
|
||||||
{children}
|
{this.props.children}
|
||||||
</View>;
|
</View>;
|
||||||
|
|
||||||
var alwaysBounceHorizontal =
|
var alwaysBounceHorizontal =
|
||||||
|
|
|
@ -537,7 +537,9 @@ var TextInput = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_onPress: function(event: Event) {
|
_onPress: function(event: Event) {
|
||||||
|
if (this.props.editable || this.props.editable === undefined) {
|
||||||
this.focus();
|
this.focus();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_onChange: function(event: Event) {
|
_onChange: function(event: Event) {
|
||||||
|
|
|
@ -83,12 +83,6 @@ var View = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
propTypes: {
|
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,
|
* When true, indicates that the view is an accessibility element. By default,
|
||||||
* all the touchable elements are accessible.
|
* all the touchable elements are accessible.
|
||||||
|
|
|
@ -209,7 +209,7 @@ var ListView = React.createClass({
|
||||||
*/
|
*/
|
||||||
getMetrics: function() {
|
getMetrics: function() {
|
||||||
return {
|
return {
|
||||||
contentHeight: this.scrollProperties.contentHeight,
|
contentLength: this.scrollProperties.contentLength,
|
||||||
totalRows: this.props.dataSource.getRowCount(),
|
totalRows: this.props.dataSource.getRowCount(),
|
||||||
renderedRows: this.state.curRenderedRowsCount,
|
renderedRows: this.state.curRenderedRowsCount,
|
||||||
visibleRows: Object.keys(this._visibleRows).length,
|
visibleRows: Object.keys(this._visibleRows).length,
|
||||||
|
@ -255,9 +255,9 @@ var ListView = React.createClass({
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
// this data should never trigger a render pass, so don't put in state
|
// this data should never trigger a render pass, so don't put in state
|
||||||
this.scrollProperties = {
|
this.scrollProperties = {
|
||||||
visibleHeight: null,
|
visibleLength: null,
|
||||||
contentHeight: null,
|
contentLength: null,
|
||||||
offsetY: 0
|
offset: 0
|
||||||
};
|
};
|
||||||
this._childFrames = [];
|
this._childFrames = [];
|
||||||
this._visibleRows = {};
|
this._visibleRows = {};
|
||||||
|
@ -409,12 +409,12 @@ var ListView = React.createClass({
|
||||||
scrollComponent.getInnerViewNode(),
|
scrollComponent.getInnerViewNode(),
|
||||||
React.findNodeHandle(scrollComponent),
|
React.findNodeHandle(scrollComponent),
|
||||||
logError,
|
logError,
|
||||||
this._setScrollContentHeight
|
this._setScrollContentLength
|
||||||
);
|
);
|
||||||
RCTUIManager.measureLayoutRelativeToParent(
|
RCTUIManager.measureLayoutRelativeToParent(
|
||||||
React.findNodeHandle(scrollComponent),
|
React.findNodeHandle(scrollComponent),
|
||||||
logError,
|
logError,
|
||||||
this._setScrollVisibleHeight
|
this._setScrollVisibleLength
|
||||||
);
|
);
|
||||||
|
|
||||||
// RCTScrollViewManager.calculateChildFrames is not available on
|
// RCTScrollViewManager.calculateChildFrames is not available on
|
||||||
|
@ -426,12 +426,14 @@ var ListView = React.createClass({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
_setScrollContentHeight: function(left, top, width, height) {
|
_setScrollContentLength: function(left, top, width, height) {
|
||||||
this.scrollProperties.contentHeight = height;
|
this.scrollProperties.contentLength = !this.props.horizontal ?
|
||||||
|
height : width;
|
||||||
},
|
},
|
||||||
|
|
||||||
_setScrollVisibleHeight: function(left, top, width, height) {
|
_setScrollVisibleLength: function(left, top, width, height) {
|
||||||
this.scrollProperties.visibleHeight = height;
|
this.scrollProperties.visibleLength = !this.props.horizontal ?
|
||||||
|
height : width;
|
||||||
this._updateVisibleRows();
|
this._updateVisibleRows();
|
||||||
this._renderMoreRowsIfNeeded();
|
this._renderMoreRowsIfNeeded();
|
||||||
},
|
},
|
||||||
|
@ -441,8 +443,8 @@ var ListView = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderMoreRowsIfNeeded: function() {
|
_renderMoreRowsIfNeeded: function() {
|
||||||
if (this.scrollProperties.contentHeight === null ||
|
if (this.scrollProperties.contentLength === null ||
|
||||||
this.scrollProperties.visibleHeight === null ||
|
this.scrollProperties.visibleLength === null ||
|
||||||
this.state.curRenderedRowsCount === this.props.dataSource.getRowCount()) {
|
this.state.curRenderedRowsCount === this.props.dataSource.getRowCount()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -472,9 +474,9 @@ var ListView = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_getDistanceFromEnd: function(scrollProperties) {
|
_getDistanceFromEnd: function(scrollProperties) {
|
||||||
return scrollProperties.contentHeight -
|
return scrollProperties.contentLength -
|
||||||
scrollProperties.visibleHeight -
|
scrollProperties.visibleLength -
|
||||||
scrollProperties.offsetY;
|
scrollProperties.offset;
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateVisibleRows: function(updatedFrames) {
|
_updateVisibleRows: function(updatedFrames) {
|
||||||
|
@ -486,9 +488,10 @@ var ListView = React.createClass({
|
||||||
this._childFrames[newFrame.index] = merge(newFrame);
|
this._childFrames[newFrame.index] = merge(newFrame);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
var isVertical = !this.props.horizontal;
|
||||||
var dataSource = this.props.dataSource;
|
var dataSource = this.props.dataSource;
|
||||||
var visibleTop = this.scrollProperties.offsetY;
|
var visibleMin = this.scrollProperties.offset;
|
||||||
var visibleBottom = visibleTop + this.scrollProperties.visibleHeight;
|
var visibleMax = visibleMin + this.scrollProperties.visibleLength;
|
||||||
var allRowIDs = dataSource.rowIdentities;
|
var allRowIDs = dataSource.rowIdentities;
|
||||||
|
|
||||||
var header = this.props.renderHeader && this.props.renderHeader();
|
var header = this.props.renderHeader && this.props.renderHeader();
|
||||||
|
@ -516,9 +519,9 @@ var ListView = React.createClass({
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
var rowVisible = visibleSection[rowID];
|
var rowVisible = visibleSection[rowID];
|
||||||
var top = frame.y;
|
var min = isVertical ? frame.y : frame.x;
|
||||||
var bottom = top + frame.height;
|
var max = min + (isVertical ? frame.height : frame.width);
|
||||||
if (top > visibleBottom || bottom < visibleTop) {
|
if (min > visibleMax || max < visibleMin) {
|
||||||
if (rowVisible) {
|
if (rowVisible) {
|
||||||
visibilityChanged = true;
|
visibilityChanged = true;
|
||||||
delete visibleSection[rowID];
|
delete visibleSection[rowID];
|
||||||
|
@ -546,16 +549,23 @@ var ListView = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_onScroll: function(e) {
|
_onScroll: function(e) {
|
||||||
this.scrollProperties.visibleHeight = e.nativeEvent.layoutMeasurement.height;
|
var isVertical = !this.props.horizontal;
|
||||||
this.scrollProperties.contentHeight = e.nativeEvent.contentSize.height;
|
this.scrollProperties.visibleLength = e.nativeEvent.layoutMeasurement[
|
||||||
this.scrollProperties.offsetY = e.nativeEvent.contentOffset.y;
|
isVertical ? 'height' : 'width'
|
||||||
|
];
|
||||||
|
this.scrollProperties.contentLength = e.nativeEvent.contentSize[
|
||||||
|
isVertical ? 'height' : 'width'
|
||||||
|
];
|
||||||
|
this.scrollProperties.offset = e.nativeEvent.contentOffset[
|
||||||
|
isVertical ? 'y' : 'x'
|
||||||
|
];
|
||||||
this._updateVisibleRows(e.nativeEvent.updatedChildFrames);
|
this._updateVisibleRows(e.nativeEvent.updatedChildFrames);
|
||||||
var nearEnd = this._getDistanceFromEnd(this.scrollProperties) < this.props.onEndReachedThreshold;
|
var nearEnd = this._getDistanceFromEnd(this.scrollProperties) < this.props.onEndReachedThreshold;
|
||||||
if (nearEnd &&
|
if (nearEnd &&
|
||||||
this.props.onEndReached &&
|
this.props.onEndReached &&
|
||||||
this.scrollProperties.contentHeight !== this._sentEndForContentHeight &&
|
this.scrollProperties.contentLength !== this._sentEndForContentLength &&
|
||||||
this.state.curRenderedRowsCount === this.props.dataSource.getRowCount()) {
|
this.state.curRenderedRowsCount === this.props.dataSource.getRowCount()) {
|
||||||
this._sentEndForContentHeight = this.scrollProperties.contentHeight;
|
this._sentEndForContentLength = this.scrollProperties.contentLength;
|
||||||
this.props.onEndReached(e);
|
this.props.onEndReached(e);
|
||||||
} else {
|
} else {
|
||||||
this._renderMoreRowsIfNeeded();
|
this._renderMoreRowsIfNeeded();
|
||||||
|
|
|
@ -1,20 +1,119 @@
|
||||||
/**
|
/**
|
||||||
* Copyright 2004-present Facebook. All Rights Reserved.
|
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Facebook, Inc. (“Facebook”) owns all right, title and interest, including
|
||||||
|
* all intellectual property and other proprietary rights, in and to the React
|
||||||
|
* Native CustomComponents software (the “Software”). Subject to your
|
||||||
|
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||||
|
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||||
|
* and (2) reproduce and distribute the Software as part of your own software
|
||||||
|
* (“Your Software”). Facebook reserves all rights not expressly granted to
|
||||||
|
* you in this license agreement.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||||
|
* OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
|
||||||
|
* IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
|
||||||
|
* EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||||
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*
|
*
|
||||||
* @providesModule NavigationEvent
|
* @providesModule NavigationEvent
|
||||||
* @flow
|
* @flow
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var invariant = require('invariant');
|
||||||
|
|
||||||
|
class NavigationEventPool {
|
||||||
|
_list: Array<any>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._list = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
get(type: String, target: Object, data: any): NavigationEvent {
|
||||||
|
var event;
|
||||||
|
if (this._list.length > 0) {
|
||||||
|
event = this._list.pop();
|
||||||
|
event.constructor.call(event, type, target, data);
|
||||||
|
} else {
|
||||||
|
event = new NavigationEvent(type, target, data);
|
||||||
|
}
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
put(event: NavigationEvent) {
|
||||||
|
this._list.push(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _navigationEventPool = new NavigationEventPool();
|
||||||
|
|
||||||
class NavigationEvent {
|
class NavigationEvent {
|
||||||
type: String;
|
_data: any;
|
||||||
target: Object;
|
_defaultPrevented: boolean;
|
||||||
data: any;
|
_disposed: boolean;
|
||||||
|
_target: ?Object;
|
||||||
|
_type: ?String;
|
||||||
|
|
||||||
|
static pool(type: String, target: Object, data: any): NavigationEvent {
|
||||||
|
return _navigationEventPool.get(type, target, data);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(type: String, target: Object, data: any) {
|
constructor(type: String, target: Object, data: any) {
|
||||||
this.type = type;
|
this._type = type;
|
||||||
this.target = target;
|
this._target = target;
|
||||||
this.data = data;
|
this._data = data;
|
||||||
|
this._defaultPrevented = false;
|
||||||
|
this._disposed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* $FlowFixMe - get/set properties not yet supported */
|
||||||
|
get type(): string {
|
||||||
|
return this._type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* $FlowFixMe - get/set properties not yet supported */
|
||||||
|
get target(): Object {
|
||||||
|
return this._target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* $FlowFixMe - get/set properties not yet supported */
|
||||||
|
get data(): any {
|
||||||
|
return this._data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* $FlowFixMe - get/set properties not yet supported */
|
||||||
|
get defaultPrevented(): boolean {
|
||||||
|
return this._defaultPrevented;
|
||||||
|
}
|
||||||
|
|
||||||
|
preventDefault(): void {
|
||||||
|
this._defaultPrevented = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispose the event.
|
||||||
|
* NavigationEvent shall be disposed after being emitted by
|
||||||
|
* `NavigationEventEmitter`.
|
||||||
|
*/
|
||||||
|
dispose(): void {
|
||||||
|
invariant(!this._disposed, 'NavigationEvent is already disposed');
|
||||||
|
this._disposed = true;
|
||||||
|
|
||||||
|
// Clean up.
|
||||||
|
this._type = null;
|
||||||
|
this._target = null;
|
||||||
|
this._data = null;
|
||||||
|
this._defaultPrevented = false;
|
||||||
|
|
||||||
|
// Put this back to the pool to reuse the instance.
|
||||||
|
_navigationEventPool.put(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Facebook, Inc. (“Facebook”) owns all right, title and interest, including
|
||||||
|
* all intellectual property and other proprietary rights, in and to the React
|
||||||
|
* Native CustomComponents software (the “Software”). Subject to your
|
||||||
|
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||||
|
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||||
|
* and (2) reproduce and distribute the Software as part of your own software
|
||||||
|
* (“Your Software”). Facebook reserves all rights not expressly granted to
|
||||||
|
* you in this license agreement.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||||
|
* OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
|
||||||
|
* IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
|
||||||
|
* EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||||
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
jest
|
||||||
|
.dontMock('NavigationEvent')
|
||||||
|
.dontMock('invariant');
|
||||||
|
|
||||||
|
var NavigationEvent = require('NavigationEvent');
|
||||||
|
|
||||||
|
describe('NavigationEvent', () => {
|
||||||
|
it('constructs', () => {
|
||||||
|
var target = {};
|
||||||
|
var event = new NavigationEvent('foo', target, 123);
|
||||||
|
expect(event.type).toBe('foo');
|
||||||
|
expect(event.target).toBe(target);
|
||||||
|
expect(event.data).toBe(123);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('constructs from pool', () => {
|
||||||
|
var target = {};
|
||||||
|
var event = NavigationEvent.pool('foo', target, 123);
|
||||||
|
expect(event.type).toBe('foo');
|
||||||
|
expect(event.target).toBe(target);
|
||||||
|
expect(event.data).toBe(123);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prevents default', () => {
|
||||||
|
var event = new NavigationEvent('foo', {}, 123);
|
||||||
|
expect(event.defaultPrevented).toBe(false);
|
||||||
|
event.preventDefault();
|
||||||
|
expect(event.defaultPrevented).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recycles', () => {
|
||||||
|
var event1 = NavigationEvent.pool('foo', {}, 123);
|
||||||
|
event1.dispose();
|
||||||
|
expect(event1.type).toBe(null);
|
||||||
|
expect(event1.data).toBe(null);
|
||||||
|
expect(event1.target).toBe(null);
|
||||||
|
|
||||||
|
var event2 = NavigationEvent.pool('bar', {}, 456);
|
||||||
|
expect(event2.type).toBe('bar');
|
||||||
|
expect(event2).toBe(event1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
|
@ -220,6 +220,11 @@ var Navigator = React.createClass({
|
||||||
*/
|
*/
|
||||||
onDidFocus: PropTypes.func,
|
onDidFocus: PropTypes.func,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will be called with (ref, indexInStack, route) when the scene ref changes
|
||||||
|
*/
|
||||||
|
onItemRef: PropTypes.func,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optionally provide a navigation bar that persists across scene
|
* Optionally provide a navigation bar that persists across scene
|
||||||
* transitions
|
* transitions
|
||||||
|
@ -313,6 +318,7 @@ var Navigator = React.createClass({
|
||||||
onPanResponderMove: this._handlePanResponderMove,
|
onPanResponderMove: this._handlePanResponderMove,
|
||||||
onPanResponderTerminate: this._handlePanResponderTerminate,
|
onPanResponderTerminate: this._handlePanResponderTerminate,
|
||||||
});
|
});
|
||||||
|
this._itemRefs = {};
|
||||||
this._interactionHandle = null;
|
this._interactionHandle = null;
|
||||||
this._emitWillFocus(this.state.routeStack[this.state.presentedIndex]);
|
this._emitWillFocus(this.state.routeStack[this.state.presentedIndex]);
|
||||||
},
|
},
|
||||||
|
@ -1000,10 +1006,22 @@ var Navigator = React.createClass({
|
||||||
return this.state.routeStack.slice();
|
return this.state.routeStack.slice();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_handleItemRef: function(itemId, route, ref) {
|
||||||
|
this._itemRefs[itemId] = ref;
|
||||||
|
var itemIndex = this.state.idStack.indexOf(itemId);
|
||||||
|
if (itemIndex === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.props.onItemRef && this.props.onItemRef(ref, itemIndex, route);
|
||||||
|
},
|
||||||
|
|
||||||
_cleanScenesPastIndex: function(index) {
|
_cleanScenesPastIndex: function(index) {
|
||||||
var newStackLength = index + 1;
|
var newStackLength = index + 1;
|
||||||
// Remove any unneeded rendered routes.
|
// Remove any unneeded rendered routes.
|
||||||
if (newStackLength < this.state.routeStack.length) {
|
if (newStackLength < this.state.routeStack.length) {
|
||||||
|
this.state.idStack.slice(newStackLength).map((removingId) => {
|
||||||
|
this._itemRefs[removingId] = null;
|
||||||
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
sceneConfigStack: this.state.sceneConfigStack.slice(0, newStackLength),
|
sceneConfigStack: this.state.sceneConfigStack.slice(0, newStackLength),
|
||||||
idStack: this.state.idStack.slice(0, newStackLength),
|
idStack: this.state.idStack.slice(0, newStackLength),
|
||||||
|
@ -1013,22 +1031,38 @@ var Navigator = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderScene: function(route, i) {
|
_renderScene: function(route, i) {
|
||||||
|
var child = this.props.renderScene(
|
||||||
|
route,
|
||||||
|
this
|
||||||
|
);
|
||||||
var disabledSceneStyle = null;
|
var disabledSceneStyle = null;
|
||||||
if (i !== this.state.presentedIndex) {
|
if (i !== this.state.presentedIndex) {
|
||||||
disabledSceneStyle = styles.disabledScene;
|
disabledSceneStyle = styles.disabledScene;
|
||||||
}
|
}
|
||||||
|
var originalRef = child.ref;
|
||||||
|
if (originalRef != null && typeof originalRef !== 'function') {
|
||||||
|
console.warn(
|
||||||
|
'String refs are not supported for navigator scenes. Use a callback ' +
|
||||||
|
'ref instead. Ignoring ref: ' + originalRef
|
||||||
|
);
|
||||||
|
originalRef = null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
key={'scene_' + i}
|
key={this.state.idStack[i]}
|
||||||
ref={'scene_' + i}
|
ref={'scene_' + i}
|
||||||
onStartShouldSetResponderCapture={() => {
|
onStartShouldSetResponderCapture={() => {
|
||||||
return (this.state.transitionFromIndex != null) || (this.state.transitionFromIndex != null);
|
return (this.state.transitionFromIndex != null) || (this.state.transitionFromIndex != null);
|
||||||
}}
|
}}
|
||||||
style={[styles.baseScene, this.props.sceneStyle, disabledSceneStyle]}>
|
style={[styles.baseScene, this.props.sceneStyle, disabledSceneStyle]}>
|
||||||
{this.props.renderScene(
|
{React.cloneElement(child, {
|
||||||
route,
|
ref: component => {
|
||||||
this
|
this._handleItemRef(this.state.idStack[i], route, component);
|
||||||
)}
|
if (originalRef) {
|
||||||
|
originalRef(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -249,8 +249,15 @@ RCT_EXPORT_MODULE()
|
||||||
request.HTTPBody = result[@"body"];
|
request.HTTPBody = result[@"body"];
|
||||||
NSString *contentType = result[@"contentType"];
|
NSString *contentType = result[@"contentType"];
|
||||||
if (contentType) {
|
if (contentType) {
|
||||||
[request setValue:contentType forHTTPHeaderField:@"content-type"];
|
[request setValue:contentType forHTTPHeaderField:@"Content-Type"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gzip the request body
|
||||||
|
if ([request.allHTTPHeaderFields[@"Content-Encoding"] isEqualToString:@"gzip"]) {
|
||||||
|
request.HTTPBody = RCTGzipData(request.HTTPBody, -1 /* default */);
|
||||||
|
[request setValue:[@(request.HTTPBody.length) description] forHTTPHeaderField:@"Content-Length"];
|
||||||
|
}
|
||||||
|
|
||||||
[self sendRequest:request
|
[self sendRequest:request
|
||||||
incrementalUpdates:incrementalUpdates
|
incrementalUpdates:incrementalUpdates
|
||||||
responseSender:responseSender];
|
responseSender:responseSender];
|
||||||
|
|
|
@ -24,22 +24,6 @@ ReactNativeViewAttributes.UIView = {
|
||||||
onLayout: true,
|
onLayout: true,
|
||||||
onAccessibilityTap: true,
|
onAccessibilityTap: true,
|
||||||
onMagicTap: true,
|
onMagicTap: true,
|
||||||
collapsible: true,
|
|
||||||
|
|
||||||
// If editing layout-only view attributes, make sure
|
|
||||||
// -[RCTShadowView isLayoutOnly] in RCTShadowView.m
|
|
||||||
// is up-to-date! If any property below is set, the
|
|
||||||
// view should not be collapsible, but this is done
|
|
||||||
// on the native side.
|
|
||||||
onMoveShouldSetResponder: true,
|
|
||||||
onResponderGrant: true,
|
|
||||||
onResponderMove: true,
|
|
||||||
onResponderReject: true,
|
|
||||||
onResponderRelease: true,
|
|
||||||
onResponderTerminate: true,
|
|
||||||
onResponderTerminationRequest: true,
|
|
||||||
onStartShouldSetResponder: true,
|
|
||||||
onStartShouldSetResponderCapture: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ReactNativeViewAttributes.RCTView = merge(
|
ReactNativeViewAttributes.RCTView = merge(
|
||||||
|
|
|
@ -20,11 +20,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isLayoutOnly
|
|
||||||
{
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)description
|
- (NSString *)description
|
||||||
{
|
{
|
||||||
NSString *superDescription = super.description;
|
NSString *superDescription = super.description;
|
||||||
|
|
|
@ -31,6 +31,24 @@ var MatrixMath = {
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
createFrustum: function(left, right, bottom, top, near, far) {
|
||||||
|
var r_width = 1 / (right - left);
|
||||||
|
var r_height = 1 / (top - bottom);
|
||||||
|
var r_depth = 1 / (near - far);
|
||||||
|
var x = 2 * (near * r_width);
|
||||||
|
var y = 2 * (near * r_height);
|
||||||
|
var A = (right + left) * r_width;
|
||||||
|
var B = (top + bottom) * r_height;
|
||||||
|
var C = (far + near) * r_depth;
|
||||||
|
var D = 2 * (far * near * r_depth);
|
||||||
|
return [
|
||||||
|
x, 0, 0, 0,
|
||||||
|
0, y, 0, 0,
|
||||||
|
A, B, C,-1,
|
||||||
|
0, 0, D, 0,
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
createTranslate2d: function(x, y) {
|
createTranslate2d: function(x, y) {
|
||||||
var mat = MatrixMath.createIdentityMatrix();
|
var mat = MatrixMath.createIdentityMatrix();
|
||||||
MatrixMath.reuseTranslate2dCommand(mat, x, y);
|
MatrixMath.reuseTranslate2dCommand(mat, x, y);
|
||||||
|
|
|
@ -24,7 +24,8 @@ var PerformanceLogger = {
|
||||||
if (timespans[key]) {
|
if (timespans[key]) {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
console.log(
|
console.log(
|
||||||
'PerformanceLogger: Attempting to add a timespan that already exists'
|
'PerformanceLogger: Attempting to add a timespan that already exists ',
|
||||||
|
key
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -40,7 +41,8 @@ var PerformanceLogger = {
|
||||||
if (timespans[key]) {
|
if (timespans[key]) {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
console.log(
|
console.log(
|
||||||
'PerformanceLogger: Attempting to start a timespan that already exists'
|
'PerformanceLogger: Attempting to start a timespan that already exists ',
|
||||||
|
key,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -56,7 +58,8 @@ var PerformanceLogger = {
|
||||||
if (!timespans[key] || !timespans[key].startTime) {
|
if (!timespans[key] || !timespans[key].startTime) {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
console.log(
|
console.log(
|
||||||
'PerformanceLogger: Attempting to end a timespan that has not started'
|
'PerformanceLogger: Attempting to end a timespan that has not started ',
|
||||||
|
key,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -75,6 +78,10 @@ var PerformanceLogger = {
|
||||||
return timespans;
|
return timespans;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
hasTimespan(key) {
|
||||||
|
return !!timespans[key];
|
||||||
|
},
|
||||||
|
|
||||||
logTimespans() {
|
logTimespans() {
|
||||||
for (var key in timespans) {
|
for (var key in timespans) {
|
||||||
console.log(key + ': ' + timespans[key].totalTime + 'ms');
|
console.log(key + ': ' + timespans[key].totalTime + 'ms');
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
#import "RCTAssert.h"
|
#import "RCTAssert.h"
|
||||||
#import "RCTDefines.h"
|
#import "RCTDefines.h"
|
||||||
|
|
||||||
// Utility functions for JSON object <-> string serialization/deserialization
|
// JSON serialization/deserialization
|
||||||
RCT_EXTERN NSString *RCTJSONStringify(id jsonObject, NSError **error);
|
RCT_EXTERN NSString *RCTJSONStringify(id jsonObject, NSError **error);
|
||||||
RCT_EXTERN id RCTJSONParse(NSString *jsonString, NSError **error);
|
RCT_EXTERN id RCTJSONParse(NSString *jsonString, NSError **error);
|
||||||
RCT_EXTERN id RCTJSONParseMutable(NSString *jsonString, NSError **error);
|
RCT_EXTERN id RCTJSONParseMutable(NSString *jsonString, NSError **error);
|
||||||
|
@ -24,7 +24,7 @@ RCT_EXTERN id RCTJSONParseWithOptions(NSString *jsonString, NSError **error, NSJ
|
||||||
// Strip non JSON-safe values from an object graph
|
// Strip non JSON-safe values from an object graph
|
||||||
RCT_EXTERN id RCTJSONClean(id object);
|
RCT_EXTERN id RCTJSONClean(id object);
|
||||||
|
|
||||||
// Get MD5 hash of a string (TODO: currently unused. Remove?)
|
// Get MD5 hash of a string
|
||||||
RCT_EXTERN NSString *RCTMD5Hash(NSString *string);
|
RCT_EXTERN NSString *RCTMD5Hash(NSString *string);
|
||||||
|
|
||||||
// Get screen metrics in a thread-safe way
|
// Get screen metrics in a thread-safe way
|
||||||
|
@ -64,3 +64,6 @@ RCT_EXTERN id RCTNullIfNil(id value);
|
||||||
|
|
||||||
// Convert data to a Base64-encoded data URL
|
// Convert data to a Base64-encoded data URL
|
||||||
RCT_EXTERN NSURL *RCTDataURL(NSString *mimeType, NSData *data);
|
RCT_EXTERN NSURL *RCTDataURL(NSString *mimeType, NSData *data);
|
||||||
|
|
||||||
|
// Gzip functionality - compression level in range 0 - 1 (-1 for default)
|
||||||
|
RCT_EXTERN NSData *RCTGzipData(NSData *data, float level);
|
||||||
|
|
|
@ -10,12 +10,15 @@
|
||||||
#import "RCTUtils.h"
|
#import "RCTUtils.h"
|
||||||
|
|
||||||
#import <mach/mach_time.h>
|
#import <mach/mach_time.h>
|
||||||
#import <objc/runtime.h>
|
#import <objc/message.h>
|
||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
#import <CommonCrypto/CommonCrypto.h>
|
#import <CommonCrypto/CommonCrypto.h>
|
||||||
|
|
||||||
|
#import <zlib.h>
|
||||||
|
#import <dlfcn.h>
|
||||||
|
|
||||||
#import "RCTLog.h"
|
#import "RCTLog.h"
|
||||||
|
|
||||||
NSString *RCTJSONStringify(id jsonObject, NSError **error)
|
NSString *RCTJSONStringify(id jsonObject, NSError **error)
|
||||||
|
@ -305,3 +308,51 @@ NSURL *RCTDataURL(NSString *mimeType, NSData *data)
|
||||||
[data base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0]]];
|
[data base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0]]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static BOOL RCTIsGzippedData(NSData *data)
|
||||||
|
{
|
||||||
|
UInt8 *bytes = (UInt8 *)data.bytes;
|
||||||
|
return (data.length >= 2 && bytes[0] == 0x1f && bytes[1] == 0x8b);
|
||||||
|
}
|
||||||
|
|
||||||
|
NSData *RCTGzipData(NSData *input, float level)
|
||||||
|
{
|
||||||
|
if (input.length == 0 || RCTIsGzippedData(input)) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *libz = dlopen("libz.dylib", RTLD_NOW);
|
||||||
|
int (*deflateInit2_)(z_streamp, int, int, int, int, int, const char *, int) = dlsym(libz, "deflateInit2_");
|
||||||
|
int (*deflate)(z_streamp, int) = dlsym(libz, "deflate");
|
||||||
|
int (*deflateEnd)(z_streamp) = dlsym(libz, "deflateEnd");
|
||||||
|
|
||||||
|
z_stream stream;
|
||||||
|
stream.zalloc = Z_NULL;
|
||||||
|
stream.zfree = Z_NULL;
|
||||||
|
stream.opaque = Z_NULL;
|
||||||
|
stream.avail_in = (uint)input.length;
|
||||||
|
stream.next_in = (Bytef *)input.bytes;
|
||||||
|
stream.total_out = 0;
|
||||||
|
stream.avail_out = 0;
|
||||||
|
|
||||||
|
static const NSUInteger RCTGZipChunkSize = 16384;
|
||||||
|
|
||||||
|
NSMutableData *output = nil;
|
||||||
|
int compression = (level < 0.0f)? Z_DEFAULT_COMPRESSION: (int)(roundf(level * 9));
|
||||||
|
if (deflateInit2(&stream, compression, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY) == Z_OK) {
|
||||||
|
output = [NSMutableData dataWithLength:RCTGZipChunkSize];
|
||||||
|
while (stream.avail_out == 0) {
|
||||||
|
if (stream.total_out >= output.length) {
|
||||||
|
output.length += RCTGZipChunkSize;
|
||||||
|
}
|
||||||
|
stream.next_out = (uint8_t *)output.mutableBytes + stream.total_out;
|
||||||
|
stream.avail_out = (uInt)(output.length - stream.total_out);
|
||||||
|
deflate(&stream, Z_FINISH);
|
||||||
|
}
|
||||||
|
deflateEnd(&stream);
|
||||||
|
output.length = stream.total_out;
|
||||||
|
}
|
||||||
|
|
||||||
|
dlclose(libz);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
|
@ -32,8 +32,15 @@
|
||||||
#import "RCTViewNodeProtocol.h"
|
#import "RCTViewNodeProtocol.h"
|
||||||
#import "UIView+React.h"
|
#import "UIView+React.h"
|
||||||
|
|
||||||
static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, void (^block)(id<RCTViewNodeProtocol>));
|
typedef void (^react_view_node_block_t)(id<RCTViewNodeProtocol>);
|
||||||
static NSDictionary *RCTPropsMerge(NSDictionary *beforeProps, NSDictionary *newProps);
|
|
||||||
|
static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, react_view_node_block_t block)
|
||||||
|
{
|
||||||
|
if (view.reactTag) block(view);
|
||||||
|
for (id<RCTViewNodeProtocol> subview in view.reactSubviews) {
|
||||||
|
RCTTraverseViewNodes(subview, block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@interface RCTAnimation : NSObject
|
@interface RCTAnimation : NSObject
|
||||||
|
|
||||||
|
@ -460,24 +467,6 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass)
|
||||||
[rootShadowView collectRootUpdatedFrames:viewsWithNewFrames
|
[rootShadowView collectRootUpdatedFrames:viewsWithNewFrames
|
||||||
parentConstraint:(CGSize){CSS_UNDEFINED, CSS_UNDEFINED}];
|
parentConstraint:(CGSize){CSS_UNDEFINED, CSS_UNDEFINED}];
|
||||||
|
|
||||||
NSSet *originalViewsWithNewFrames = [viewsWithNewFrames copy];
|
|
||||||
NSMutableArray *viewsToCheck = [viewsWithNewFrames.allObjects mutableCopy];
|
|
||||||
while (viewsToCheck.count > 0) {
|
|
||||||
// Better to remove from the front and append to the end
|
|
||||||
// because of how NSMutableArray is implemented.
|
|
||||||
// (It's a "round" buffer with stored size and offset.)
|
|
||||||
|
|
||||||
RCTShadowView *viewToCheck = viewsToCheck.firstObject;
|
|
||||||
[viewsToCheck removeObjectAtIndex:0];
|
|
||||||
|
|
||||||
if (viewToCheck.layoutOnly) {
|
|
||||||
[viewsWithNewFrames removeObject:viewToCheck];
|
|
||||||
[viewsToCheck addObjectsFromArray:[viewToCheck reactSubviews]];
|
|
||||||
} else {
|
|
||||||
[viewsWithNewFrames addObject:viewToCheck];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parallel arrays are built and then handed off to main thread
|
// Parallel arrays are built and then handed off to main thread
|
||||||
NSMutableArray *frameReactTags = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
|
NSMutableArray *frameReactTags = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
|
||||||
NSMutableArray *frames = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
|
NSMutableArray *frames = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
|
||||||
|
@ -486,30 +475,26 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass)
|
||||||
NSMutableArray *onLayoutEvents = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
|
NSMutableArray *onLayoutEvents = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
|
||||||
|
|
||||||
for (RCTShadowView *shadowView in viewsWithNewFrames) {
|
for (RCTShadowView *shadowView in viewsWithNewFrames) {
|
||||||
CGRect frame = shadowView.adjustedFrame;
|
[frameReactTags addObject:shadowView.reactTag];
|
||||||
NSNumber *reactTag = shadowView.reactTag;
|
[frames addObject:[NSValue valueWithCGRect:shadowView.frame]];
|
||||||
[frameReactTags addObject:reactTag];
|
|
||||||
[frames addObject:[NSValue valueWithCGRect:frame]];
|
|
||||||
[areNew addObject:@(shadowView.isNewView)];
|
[areNew addObject:@(shadowView.isNewView)];
|
||||||
|
[parentsAreNew addObject:@(shadowView.superview.isNewView)];
|
||||||
RCTShadowView *superview = shadowView;
|
id event = (id)kCFNull;
|
||||||
BOOL parentIsNew = NO;
|
if (shadowView.hasOnLayout) {
|
||||||
while (YES) {
|
event = @{
|
||||||
superview = superview.superview;
|
@"target": shadowView.reactTag,
|
||||||
parentIsNew = superview.isNewView;
|
@"layout": @{
|
||||||
if (!superview.layoutOnly) {
|
@"x": @(shadowView.frame.origin.x),
|
||||||
break;
|
@"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];
|
[onLayoutEvents addObject:event];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (RCTShadowView *shadowView in originalViewsWithNewFrames) {
|
for (RCTShadowView *shadowView in viewsWithNewFrames) {
|
||||||
// We have to do this after we build the parentsAreNew array.
|
// We have to do this after we build the parentsAreNew array.
|
||||||
shadowView.newView = NO;
|
shadowView.newView = NO;
|
||||||
}
|
}
|
||||||
|
@ -526,28 +511,24 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform layout (possibly animated)
|
// Perform layout (possibly animated)
|
||||||
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
return ^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||||
RCTResponseSenderBlock callback = uiManager->_layoutAnimation.callback;
|
RCTResponseSenderBlock callback = self->_layoutAnimation.callback;
|
||||||
__block NSUInteger completionsCalled = 0;
|
__block NSUInteger completionsCalled = 0;
|
||||||
for (NSUInteger ii = 0; ii < frames.count; ii++) {
|
for (NSUInteger ii = 0; ii < frames.count; ii++) {
|
||||||
NSNumber *reactTag = frameReactTags[ii];
|
NSNumber *reactTag = frameReactTags[ii];
|
||||||
UIView *view = viewRegistry[reactTag];
|
UIView *view = viewRegistry[reactTag];
|
||||||
if (!view) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
CGRect frame = [frames[ii] CGRectValue];
|
CGRect frame = [frames[ii] CGRectValue];
|
||||||
id event = onLayoutEvents[ii];
|
id event = onLayoutEvents[ii];
|
||||||
|
|
||||||
BOOL isNew = [areNew[ii] boolValue];
|
BOOL isNew = [areNew[ii] boolValue];
|
||||||
RCTAnimation *updateAnimation = isNew ? nil : uiManager->_layoutAnimation.updateAnimation;
|
RCTAnimation *updateAnimation = isNew ? nil : _layoutAnimation.updateAnimation;
|
||||||
BOOL shouldAnimateCreation = isNew && ![parentsAreNew[ii] boolValue];
|
BOOL shouldAnimateCreation = isNew && ![parentsAreNew[ii] boolValue];
|
||||||
RCTAnimation *createAnimation = shouldAnimateCreation ? uiManager->_layoutAnimation.createAnimation : nil;
|
RCTAnimation *createAnimation = shouldAnimateCreation ? _layoutAnimation.createAnimation : nil;
|
||||||
|
|
||||||
void (^completion)(BOOL) = ^(BOOL finished) {
|
void (^completion)(BOOL) = ^(BOOL finished) {
|
||||||
completionsCalled++;
|
completionsCalled++;
|
||||||
if (event != (id)kCFNull) {
|
if (event != (id)kCFNull) {
|
||||||
[uiManager.bridge.eventDispatcher sendInputEventWithName:@"topLayout" body:event];
|
[self.bridge.eventDispatcher sendInputEventWithName:@"topLayout" body:event];
|
||||||
}
|
}
|
||||||
if (callback && completionsCalled == frames.count - 1) {
|
if (callback && completionsCalled == frames.count - 1) {
|
||||||
callback(@[@(finished)]);
|
callback(@[@(finished)]);
|
||||||
|
@ -559,13 +540,13 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass)
|
||||||
[updateAnimation performAnimations:^{
|
[updateAnimation performAnimations:^{
|
||||||
[view reactSetFrame:frame];
|
[view reactSetFrame:frame];
|
||||||
for (RCTViewManagerUIBlock block in updateBlocks) {
|
for (RCTViewManagerUIBlock block in updateBlocks) {
|
||||||
block(uiManager, viewRegistry);
|
block(self, _viewRegistry);
|
||||||
}
|
}
|
||||||
} withCompletionBlock:completion];
|
} withCompletionBlock:completion];
|
||||||
} else {
|
} else {
|
||||||
[view reactSetFrame:frame];
|
[view reactSetFrame:frame];
|
||||||
for (RCTViewManagerUIBlock block in updateBlocks) {
|
for (RCTViewManagerUIBlock block in updateBlocks) {
|
||||||
block(uiManager, viewRegistry);
|
block(self, _viewRegistry);
|
||||||
}
|
}
|
||||||
completion(YES);
|
completion(YES);
|
||||||
}
|
}
|
||||||
|
@ -587,7 +568,7 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass)
|
||||||
createAnimation.property);
|
createAnimation.property);
|
||||||
}
|
}
|
||||||
for (RCTViewManagerUIBlock block in updateBlocks) {
|
for (RCTViewManagerUIBlock block in updateBlocks) {
|
||||||
block(uiManager, viewRegistry);
|
block(self, _viewRegistry);
|
||||||
}
|
}
|
||||||
} withCompletionBlock:nil];
|
} withCompletionBlock:nil];
|
||||||
}
|
}
|
||||||
|
@ -710,135 +691,6 @@ RCT_EXPORT_METHOD(replaceExistingNonRootView:(NSNumber *)reactTag withView:(NSNu
|
||||||
removeAtIndices:removeAtIndices];
|
removeAtIndices:removeAtIndices];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This method modifies the indices received in manageChildren() to take into
|
|
||||||
* account views that are layout only. For example, if JS tells native to insert
|
|
||||||
* view with tag 12 at index 4, but view 12 is layout only, we would want to
|
|
||||||
* insert its children's tags, tags 13 and 14, at indices 4 and 5 instead. This
|
|
||||||
* causes us to have to shift the remaining indices to account for the new
|
|
||||||
* views.
|
|
||||||
*/
|
|
||||||
- (void)modifyManageChildren:(NSNumber *)containerReactTag
|
|
||||||
addChildReactTags:(NSMutableArray *)mutableAddChildReactTags
|
|
||||||
addAtIndices:(NSMutableArray *)mutableAddAtIndices
|
|
||||||
removeAtIndices:(NSMutableArray *)mutableRemoveAtIndices
|
|
||||||
{
|
|
||||||
NSUInteger i;
|
|
||||||
NSMutableArray *containerSubviews = [[_shadowViewRegistry[containerReactTag] reactSubviews] mutableCopy];
|
|
||||||
|
|
||||||
i = 0;
|
|
||||||
while (i < containerSubviews.count) {
|
|
||||||
RCTShadowView *shadowView = containerSubviews[i];
|
|
||||||
if (!shadowView.layoutOnly) {
|
|
||||||
i++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
[containerSubviews removeObjectAtIndex:i];
|
|
||||||
|
|
||||||
NSArray *subviews = [shadowView reactSubviews];
|
|
||||||
NSUInteger subviewsCount = subviews.count;
|
|
||||||
NSIndexSet *insertionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(i, subviewsCount)];
|
|
||||||
[containerSubviews insertObjects:subviews atIndexes:insertionIndexes];
|
|
||||||
|
|
||||||
NSUInteger removalIndex = [mutableRemoveAtIndices indexOfObject:@(i)];
|
|
||||||
if (removalIndex != NSNotFound) {
|
|
||||||
[mutableRemoveAtIndices removeObjectAtIndex:removalIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subviewsCount != 1) {
|
|
||||||
for (NSUInteger j = 0, count = mutableRemoveAtIndices.count; j < count; j++) {
|
|
||||||
NSUInteger atIndex = [mutableRemoveAtIndices[j] unsignedIntegerValue];
|
|
||||||
if (atIndex > i) {
|
|
||||||
mutableRemoveAtIndices[j] = @(atIndex + subviewsCount - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (removalIndex != NSNotFound) {
|
|
||||||
for (NSUInteger j = 0; j < subviewsCount; j++) {
|
|
||||||
[mutableRemoveAtIndices insertObject:@(i + j) atIndex:removalIndex + j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (removalIndex == NSNotFound && subviewsCount != 1) {
|
|
||||||
for (NSUInteger j = 0, count = mutableAddAtIndices.count; j < count; j++) {
|
|
||||||
NSUInteger atIndex = [mutableAddAtIndices[j] unsignedIntegerValue];
|
|
||||||
if (atIndex > i) {
|
|
||||||
mutableAddAtIndices[j] = @(atIndex + subviewsCount - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
i = 0;
|
|
||||||
while (i < mutableAddChildReactTags.count) {
|
|
||||||
NSNumber *tag = mutableAddChildReactTags[i];
|
|
||||||
NSNumber *index = mutableAddAtIndices[i];
|
|
||||||
|
|
||||||
RCTShadowView *shadowView = _shadowViewRegistry[tag];
|
|
||||||
if (!shadowView.layoutOnly) {
|
|
||||||
i++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSArray *subviews = [shadowView reactSubviews];
|
|
||||||
NSUInteger subviewsCount = subviews.count;
|
|
||||||
[mutableAddAtIndices removeObjectAtIndex:i];
|
|
||||||
[mutableAddChildReactTags removeObjectAtIndex:i];
|
|
||||||
|
|
||||||
for (NSUInteger j = 0; j < subviewsCount; j++) {
|
|
||||||
[mutableAddChildReactTags insertObject:[subviews[j] reactTag] atIndex:i + j];
|
|
||||||
[mutableAddAtIndices insertObject:@(index.unsignedIntegerValue + j) atIndex:i + j];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (NSUInteger j = i + subviewsCount, count = mutableAddAtIndices.count; j < count; j++) {
|
|
||||||
NSUInteger atIndex = [mutableAddAtIndices[j] unsignedIntegerValue];
|
|
||||||
mutableAddAtIndices[j] = @(atIndex + subviewsCount - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSNumber *)containerReactTag:(NSNumber *)containerReactTag offset:(inout NSUInteger *)offset
|
|
||||||
{
|
|
||||||
RCTShadowView *container = _shadowViewRegistry[containerReactTag];
|
|
||||||
NSNumber *containerSuperviewReactTag = containerReactTag;
|
|
||||||
RCTShadowView *superview = container;
|
|
||||||
|
|
||||||
while (superview.layoutOnly) {
|
|
||||||
RCTShadowView *superviewSuperview = superview.superview;
|
|
||||||
containerSuperviewReactTag = superviewSuperview.reactTag;
|
|
||||||
NSMutableArray *reactSubviews = [[superviewSuperview reactSubviews] mutableCopy];
|
|
||||||
NSUInteger superviewIndex = [reactSubviews indexOfObject:superview];
|
|
||||||
|
|
||||||
NSUInteger i = 0;
|
|
||||||
while (i < superviewIndex) {
|
|
||||||
RCTShadowView *child = reactSubviews[i];
|
|
||||||
if (!child.layoutOnly) {
|
|
||||||
if (offset) {
|
|
||||||
(*offset)++;
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
[reactSubviews removeObjectAtIndex:i];
|
|
||||||
|
|
||||||
NSArray *subviews = [child reactSubviews];
|
|
||||||
NSUInteger subviewsCount = subviews.count;
|
|
||||||
NSIndexSet *insertionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(i, subviewsCount)];
|
|
||||||
[reactSubviews insertObjects:subviews atIndexes:insertionIndexes];
|
|
||||||
|
|
||||||
superviewIndex += subviewsCount - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
superview = superviewSuperview;
|
|
||||||
}
|
|
||||||
|
|
||||||
return containerSuperviewReactTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
RCT_EXPORT_METHOD(manageChildren:(NSNumber *)containerReactTag
|
RCT_EXPORT_METHOD(manageChildren:(NSNumber *)containerReactTag
|
||||||
moveFromIndices:(NSArray *)moveFromIndices
|
moveFromIndices:(NSArray *)moveFromIndices
|
||||||
moveToIndices:(NSArray *)moveToIndices
|
moveToIndices:(NSArray *)moveToIndices
|
||||||
|
@ -846,109 +698,62 @@ RCT_EXPORT_METHOD(manageChildren:(NSNumber *)containerReactTag
|
||||||
addAtIndices:(NSArray *)addAtIndices
|
addAtIndices:(NSArray *)addAtIndices
|
||||||
removeAtIndices:(NSArray *)removeAtIndices)
|
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
|
[self _manageChildren:containerReactTag
|
||||||
|
moveFromIndices:moveFromIndices
|
||||||
|
moveToIndices:moveToIndices
|
||||||
addChildReactTags:addChildReactTags
|
addChildReactTags:addChildReactTags
|
||||||
addAtIndices:addAtIndices
|
addAtIndices:addAtIndices
|
||||||
removeAtIndices:removeAtIndices
|
removeAtIndices:removeAtIndices
|
||||||
registry:_shadowViewRegistry];
|
registry:_shadowViewRegistry];
|
||||||
|
|
||||||
if (containerSuperviewReactTag) {
|
|
||||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
|
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
|
||||||
(void)(id []){containerReactTag, @(offset), addChildReactTags, addAtIndices, removeAtIndices};
|
[uiManager _manageChildren:containerReactTag
|
||||||
[uiManager _manageChildren:containerSuperviewReactTag
|
moveFromIndices:moveFromIndices
|
||||||
addChildReactTags:mutableAddChildReactTags
|
moveToIndices:moveToIndices
|
||||||
addAtIndices:mutableAddAtIndices
|
addChildReactTags:addChildReactTags
|
||||||
removeAtIndices:mutableRemoveAtIndices
|
addAtIndices:addAtIndices
|
||||||
|
removeAtIndices:removeAtIndices
|
||||||
registry:viewRegistry];
|
registry:viewRegistry];
|
||||||
}];
|
}];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)_manageChildren:(NSNumber *)containerReactTag
|
- (void)_manageChildren:(NSNumber *)containerReactTag
|
||||||
|
moveFromIndices:(NSArray *)moveFromIndices
|
||||||
|
moveToIndices:(NSArray *)moveToIndices
|
||||||
addChildReactTags:(NSArray *)addChildReactTags
|
addChildReactTags:(NSArray *)addChildReactTags
|
||||||
addAtIndices:(NSArray *)addAtIndices
|
addAtIndices:(NSArray *)addAtIndices
|
||||||
removeAtIndices:(NSArray *)removeAtIndices
|
removeAtIndices:(NSArray *)removeAtIndices
|
||||||
registry:(RCTSparseArray *)registry
|
registry:(RCTSparseArray *)registry
|
||||||
{
|
{
|
||||||
id<RCTViewNodeProtocol> container = registry[containerReactTag];
|
id<RCTViewNodeProtocol> 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
|
// Removes (both permanent and temporary moves) are using "before" indices
|
||||||
NSArray *removedChildren = [self _childrenToRemoveFromContainer:container atIndices:removeAtIndices];
|
NSArray *permanentlyRemovedChildren = [self _childrenToRemoveFromContainer:container atIndices:removeAtIndices];
|
||||||
[self _removeChildren:removedChildren fromContainer:container];
|
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];
|
[self _purgeChildren:permanentlyRemovedChildren fromRegistry:registry];
|
||||||
|
|
||||||
// TODO (#5906496): optimize all these loops - constantly calling array.count is not efficient
|
// TODO (#5906496): optimize all these loops - constantly calling array.count is not efficient
|
||||||
|
|
||||||
// Figure out what to insert
|
// Figure out what to insert - merge temporary inserts and adds
|
||||||
NSMutableDictionary *childrenToAdd = [NSMutableDictionary dictionary];
|
NSMutableDictionary *destinationsToChildrenToAdd = [NSMutableDictionary dictionary];
|
||||||
for (NSInteger index = 0, count = addAtIndices.count; index < count; index++) {
|
for (NSInteger index = 0, length = temporarilyRemovedChildren.count; index < length; index++) {
|
||||||
id<RCTViewNodeProtocol> view = registry[addChildReactTags[index]];
|
destinationsToChildrenToAdd[moveToIndices[index]] = temporarilyRemovedChildren[index];
|
||||||
|
}
|
||||||
|
for (NSInteger index = 0, length = addAtIndices.count; index < length; index++) {
|
||||||
|
id view = registry[addChildReactTags[index]];
|
||||||
if (view) {
|
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) {
|
for (NSNumber *reactIndex in sortedIndices) {
|
||||||
[container insertReactSubview:childrenToAdd[reactIndex] atIndex:reactIndex.integerValue];
|
[container insertReactSubview:destinationsToChildrenToAdd[reactIndex] atIndex:reactIndex.integerValue];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1031,36 +836,27 @@ RCT_EXPORT_METHOD(createView:(NSNumber *)reactTag
|
||||||
// Set properties
|
// Set properties
|
||||||
shadowView.viewName = viewName;
|
shadowView.viewName = viewName;
|
||||||
shadowView.reactTag = reactTag;
|
shadowView.reactTag = reactTag;
|
||||||
shadowView.allProps = props;
|
|
||||||
RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[viewName], manager);
|
RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[viewName], manager);
|
||||||
}
|
}
|
||||||
_shadowViewRegistry[reactTag] = shadowView;
|
_shadowViewRegistry[reactTag] = shadowView;
|
||||||
|
|
||||||
if (!shadowView.layoutOnly) {
|
|
||||||
// Shadow view is the source of truth for background color this is a little
|
// 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
|
// 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
|
// the view, but it's the only way that makes sense given our threading model
|
||||||
UIColor *backgroundColor = shadowView.backgroundColor;
|
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
|
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
|
||||||
{
|
|
||||||
RCTAssertMainThread();
|
RCTAssertMainThread();
|
||||||
|
|
||||||
UIView *view = [manager view];
|
UIView *view = [manager view];
|
||||||
if (!view) {
|
if (view) {
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate default view, used for resetting default props
|
// Generate default view, used for resetting default props
|
||||||
if (!_defaultViews[viewName]) {
|
if (!uiManager->_defaultViews[viewName]) {
|
||||||
// Note the default is setup after the props are read for the first time
|
// 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
|
// ever for this className - this is ok because we only use the default
|
||||||
// for restoring defaults, which never happens on first creation.
|
// for restoring defaults, which never happens on first creation.
|
||||||
_defaultViews[viewName] = [manager view];
|
uiManager->_defaultViews[viewName] = [manager view];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set properties
|
// Set properties
|
||||||
|
@ -1071,32 +867,14 @@ RCT_EXPORT_METHOD(createView:(NSNumber *)reactTag
|
||||||
view.userInteractionEnabled = YES; // required for touch handling
|
view.userInteractionEnabled = YES; // required for touch handling
|
||||||
view.layer.allowsGroupOpacity = YES; // required for touch handling
|
view.layer.allowsGroupOpacity = YES; // required for touch handling
|
||||||
}
|
}
|
||||||
RCTSetViewProps(props, view, _defaultViews[viewName], manager);
|
RCTSetViewProps(props, view, uiManager->_defaultViews[viewName], manager);
|
||||||
|
|
||||||
if ([view respondsToSelector:@selector(reactBridgeDidFinishTransaction)]) {
|
if ([view respondsToSelector:@selector(reactBridgeDidFinishTransaction)]) {
|
||||||
[_bridgeTransactionListeners addObject:view];
|
[uiManager->_bridgeTransactionListeners addObject:view];
|
||||||
}
|
}
|
||||||
_viewRegistry[reactTag] = 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
|
// TODO: remove viewName param as it isn't needed
|
||||||
|
@ -1110,100 +888,10 @@ RCT_EXPORT_METHOD(updateView:(NSNumber *)reactTag
|
||||||
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
|
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
|
||||||
RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[viewName], viewManager);
|
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<RCTInvalidating>)container invalidate];
|
|
||||||
}
|
|
||||||
|
|
||||||
viewRegistry[reactTag] = nil;
|
|
||||||
}];
|
|
||||||
} else {
|
|
||||||
NSMutableArray *mutableAddChildReactTags = [[[shadowView reactSubviews] valueForKey:@"reactTag"] mutableCopy];
|
|
||||||
NSMutableArray *mutableAddAtIndices = [NSMutableArray arrayWithCapacity:mutableAddChildReactTags.count];
|
|
||||||
for (NSUInteger i = 0, count = mutableAddChildReactTags.count; i < count; i++) {
|
|
||||||
[mutableAddAtIndices addObject:@(i)];
|
|
||||||
}
|
|
||||||
|
|
||||||
[self modifyManageChildren:reactTag
|
|
||||||
addChildReactTags:mutableAddChildReactTags
|
|
||||||
addAtIndices:mutableAddAtIndices
|
|
||||||
removeAtIndices:nil];
|
|
||||||
|
|
||||||
NSUInteger offset = [shadowView.superview.reactSubviews indexOfObject:shadowView];
|
|
||||||
NSNumber *containerSuperviewReactTag = [self containerReactTag:shadowView.superview.reactTag offset:&offset];
|
|
||||||
UIColor *backgroundColor = shadowView.backgroundColor;
|
|
||||||
|
|
||||||
CGRect shadowViewFrame = shadowView.adjustedFrame;
|
|
||||||
NSMutableDictionary *newFrames = [NSMutableDictionary dictionaryWithCapacity:mutableAddChildReactTags.count];
|
|
||||||
for (NSNumber *childTag in mutableAddChildReactTags) {
|
|
||||||
RCTShadowView *child = _shadowViewRegistry[childTag];
|
|
||||||
newFrames[childTag] = [NSValue valueWithCGRect:child.adjustedFrame];
|
|
||||||
}
|
|
||||||
|
|
||||||
[self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
|
||||||
RCTAssertMainThread();
|
|
||||||
|
|
||||||
UIView *containerSuperview = viewRegistry[containerSuperviewReactTag];
|
|
||||||
UIView *container = [uiManager createView:reactTag viewName:viewName props:newProps withManager:viewManager backgroundColor:backgroundColor];
|
|
||||||
|
|
||||||
[containerSuperview insertReactSubview:container atIndex:offset];
|
|
||||||
if (RCTRectIsDefined(shadowViewFrame)) {
|
|
||||||
container.frame = shadowViewFrame;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (NSUInteger i = 0, count = mutableAddAtIndices.count; i < count; i++) {
|
|
||||||
NSNumber *tag = mutableAddChildReactTags[i];
|
|
||||||
UIView *subview = viewRegistry[tag];
|
|
||||||
[containerSuperview removeReactSubview:subview];
|
|
||||||
|
|
||||||
NSUInteger atIndex = [mutableAddAtIndices[i] unsignedIntegerValue];
|
|
||||||
[container insertReactSubview:subview atIndex:atIndex];
|
|
||||||
|
|
||||||
CGRect subviewFrame = [newFrames[tag] CGRectValue];
|
|
||||||
if (RCTRectIsDefined(subviewFrame)) {
|
|
||||||
subview.frame = subviewFrame;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
} else if (!isLayoutOnly) {
|
|
||||||
// Update node
|
|
||||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||||
UIView *view = viewRegistry[reactTag];
|
UIView *view = viewRegistry[reactTag];
|
||||||
RCTSetViewProps(props, view, uiManager->_defaultViews[viewName], viewManager);
|
RCTSetViewProps(props, view, uiManager->_defaultViews[viewName], viewManager);
|
||||||
}];
|
}];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_METHOD(focus:(NSNumber *)reactTag)
|
RCT_EXPORT_METHOD(focus:(NSNumber *)reactTag)
|
||||||
|
@ -1541,16 +1229,12 @@ RCT_EXPORT_METHOD(zoomToRect:(NSNumber *)reactTag
|
||||||
RCT_EXPORT_METHOD(setJSResponder:(NSNumber *)reactTag
|
RCT_EXPORT_METHOD(setJSResponder:(NSNumber *)reactTag
|
||||||
blockNativeResponder:(__unused BOOL)blockNativeResponder)
|
blockNativeResponder:(__unused BOOL)blockNativeResponder)
|
||||||
{
|
{
|
||||||
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
|
|
||||||
if (!shadowView) {
|
|
||||||
RCTLogError(@"Invalid view set to be the JS responder - tag %@", reactTag);
|
|
||||||
} else if (shadowView.layoutOnly) {
|
|
||||||
RCTLogError(@"Cannot set JS responder to layout-only view with tag %@ - %@, %@", reactTag, shadowView, shadowView.allProps);
|
|
||||||
} else {
|
|
||||||
[self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||||
_jsResponder = viewRegistry[reactTag];
|
_jsResponder = viewRegistry[reactTag];
|
||||||
}];
|
if (!_jsResponder) {
|
||||||
|
RCTLogError(@"Invalid view set to be the JS responder - tag %zd", reactTag);
|
||||||
}
|
}
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_METHOD(clearJSResponder)
|
RCT_EXPORT_METHOD(clearJSResponder)
|
||||||
|
@ -1806,27 +1490,3 @@ static UIView *_jsResponder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, void (^block)(id<RCTViewNodeProtocol>))
|
|
||||||
{
|
|
||||||
if (view.reactTag) block(view);
|
|
||||||
for (id<RCTViewNodeProtocol> 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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
@property (nonatomic, strong) UIImage *backButtonIcon;
|
@property (nonatomic, strong) UIImage *backButtonIcon;
|
||||||
@property (nonatomic, copy) NSString *backButtonTitle;
|
@property (nonatomic, copy) NSString *backButtonTitle;
|
||||||
@property (nonatomic, assign) BOOL navigationBarHidden;
|
@property (nonatomic, assign) BOOL navigationBarHidden;
|
||||||
|
@property (nonatomic, assign) BOOL shadowHidden;
|
||||||
@property (nonatomic, strong) UIColor *tintColor;
|
@property (nonatomic, strong) UIColor *tintColor;
|
||||||
@property (nonatomic, strong) UIColor *barTintColor;
|
@property (nonatomic, strong) UIColor *barTintColor;
|
||||||
@property (nonatomic, strong) UIColor *titleTextColor;
|
@property (nonatomic, strong) UIColor *titleTextColor;
|
||||||
|
|
|
@ -22,6 +22,7 @@ RCT_EXPORT_MODULE()
|
||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_VIEW_PROPERTY(navigationBarHidden, BOOL)
|
RCT_EXPORT_VIEW_PROPERTY(navigationBarHidden, BOOL)
|
||||||
|
RCT_EXPORT_VIEW_PROPERTY(shadowHidden, BOOL)
|
||||||
RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
|
RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
|
||||||
RCT_EXPORT_VIEW_PROPERTY(barTintColor, UIColor)
|
RCT_EXPORT_VIEW_PROPERTY(barTintColor, UIColor)
|
||||||
RCT_EXPORT_VIEW_PROPERTY(translucent, BOOL)
|
RCT_EXPORT_VIEW_PROPERTY(translucent, BOOL)
|
||||||
|
|
|
@ -41,12 +41,6 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *viewRegistry);
|
||||||
@property (nonatomic, assign) RCTUpdateLifecycle layoutLifecycle;
|
@property (nonatomic, assign) RCTUpdateLifecycle layoutLifecycle;
|
||||||
@property (nonatomic, assign) BOOL hasOnLayout;
|
@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
|
* 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
|
* set to NO in RCTUIManager after the layout pass is done and all frames have been extracted to be applied to the
|
||||||
|
|
|
@ -367,10 +367,8 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
|
||||||
- (NSString *)description
|
- (NSString *)description
|
||||||
{
|
{
|
||||||
NSString *description = super.description;
|
NSString *description = super.description;
|
||||||
if (self.layoutOnly) {
|
description = [[description substringToIndex:description.length - 1] stringByAppendingFormat:@"; viewName: %@; reactTag: %@; frame: %@>", self.viewName, self.reactTag, NSStringFromCGRect(self.frame)];
|
||||||
description = [@"* " stringByAppendingString:description];
|
return 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
|
- (void)addRecursiveDescriptionToString:(NSMutableString *)string atLevel:(NSUInteger)level
|
||||||
|
@ -394,94 +392,6 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isLayoutOnly
|
|
||||||
{
|
|
||||||
if (![self.viewName isEqualToString:@"RCTView"]) {
|
|
||||||
// For now, only `RCTView`s can be layout-only.
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
// dispatch_once is unnecessary because this property SHOULD only be accessed
|
|
||||||
// on the shadow queue
|
|
||||||
static NSSet *layoutKeys;
|
|
||||||
static NSSet *specialKeys;
|
|
||||||
if (!layoutKeys || !specialKeys) {
|
|
||||||
// Taken from LayoutPropTypes.js with the exception that borderWidth,
|
|
||||||
// borderTopWidth, borderBottomWidth, borderLeftWidth, and borderRightWidth
|
|
||||||
// were removed because black color is assumed
|
|
||||||
static NSString *const layoutKeyStrings[] = {
|
|
||||||
@"width",
|
|
||||||
@"height",
|
|
||||||
@"top",
|
|
||||||
@"left",
|
|
||||||
@"right",
|
|
||||||
@"bottom",
|
|
||||||
@"margin",
|
|
||||||
@"marginVertical",
|
|
||||||
@"marginHorizontal",
|
|
||||||
@"marginTop",
|
|
||||||
@"marginBottom",
|
|
||||||
@"marginLeft",
|
|
||||||
@"marginRight",
|
|
||||||
@"padding",
|
|
||||||
@"paddingVertical",
|
|
||||||
@"paddingHorizontal",
|
|
||||||
@"paddingTop",
|
|
||||||
@"paddingBottom",
|
|
||||||
@"paddingLeft",
|
|
||||||
@"paddingRight",
|
|
||||||
@"position",
|
|
||||||
@"flexDirection",
|
|
||||||
@"flexWrap",
|
|
||||||
@"justifyContent",
|
|
||||||
@"alignItems",
|
|
||||||
@"alignSelf",
|
|
||||||
@"flex",
|
|
||||||
};
|
|
||||||
layoutKeys = [NSSet setWithObjects:layoutKeyStrings count:sizeof(layoutKeyStrings)/sizeof(*layoutKeyStrings)];
|
|
||||||
// layoutKeys are the only keys whose presence does not reject layout-only status.
|
|
||||||
|
|
||||||
static NSString *const specialKeyStrings[] = {
|
|
||||||
@"accessible",
|
|
||||||
@"collapsible",
|
|
||||||
};
|
|
||||||
specialKeys = [NSSet setWithObjects:specialKeyStrings count:sizeof(specialKeyStrings)/sizeof(*specialKeyStrings)];
|
|
||||||
// specialKeys are keys whose presence does not indicate whether layout-only or not
|
|
||||||
// their values must be tested below
|
|
||||||
}
|
|
||||||
|
|
||||||
NSNumber *collapsible = self.allProps[@"collapsible"];
|
|
||||||
if (collapsible && !collapsible.boolValue) {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSNumber *accessible = self.allProps[@"accessible"];
|
|
||||||
if (accessible && accessible.boolValue) {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (NSString *key in self.allProps) {
|
|
||||||
if (![specialKeys containsObject:key] && ![layoutKeys containsObject:key]) {
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (CGRect)adjustedFrame
|
|
||||||
{
|
|
||||||
CGRect frame = self.frame;
|
|
||||||
RCTShadowView *superview = self;
|
|
||||||
while ((superview = superview.superview) && superview.layoutOnly) {
|
|
||||||
const CGRect superviewFrame = superview.frame;
|
|
||||||
frame.origin.x += superviewFrame.origin.x;
|
|
||||||
frame.origin.y += superviewFrame.origin.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
return frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Margin
|
// Margin
|
||||||
|
|
||||||
#define RCT_MARGIN_PROPERTY(prop, metaProp) \
|
#define RCT_MARGIN_PROPERTY(prop, metaProp) \
|
||||||
|
|
|
@ -102,8 +102,13 @@ RCT_REMAP_VIEW_PROPERTY(shadowColor, layer.shadowColor, CGColor);
|
||||||
RCT_REMAP_VIEW_PROPERTY(shadowOffset, layer.shadowOffset, CGSize);
|
RCT_REMAP_VIEW_PROPERTY(shadowOffset, layer.shadowOffset, CGSize);
|
||||||
RCT_REMAP_VIEW_PROPERTY(shadowOpacity, layer.shadowOpacity, float)
|
RCT_REMAP_VIEW_PROPERTY(shadowOpacity, layer.shadowOpacity, float)
|
||||||
RCT_REMAP_VIEW_PROPERTY(shadowRadius, layer.shadowRadius, CGFloat)
|
RCT_REMAP_VIEW_PROPERTY(shadowRadius, layer.shadowRadius, CGFloat)
|
||||||
RCT_REMAP_VIEW_PROPERTY(transformMatrix, layer.transform, CATransform3D)
|
|
||||||
RCT_REMAP_VIEW_PROPERTY(overflow, clipsToBounds, css_clip_t)
|
RCT_REMAP_VIEW_PROPERTY(overflow, clipsToBounds, css_clip_t)
|
||||||
|
RCT_CUSTOM_VIEW_PROPERTY(transformMatrix, CATransform3D, RCTView)
|
||||||
|
{
|
||||||
|
view.layer.transform = json ? [RCTConvert CATransform3D:json] : defaultView.layer.transform;
|
||||||
|
// TODO: Improve this by enabling edge antialiasing only for transforms with rotation or skewing
|
||||||
|
view.layer.allowsEdgeAntialiasing = !CATransform3DIsIdentity(view.layer.transform);
|
||||||
|
}
|
||||||
RCT_CUSTOM_VIEW_PROPERTY(pointerEvents, RCTPointerEvents, RCTView)
|
RCT_CUSTOM_VIEW_PROPERTY(pointerEvents, RCTPointerEvents, RCTView)
|
||||||
{
|
{
|
||||||
if ([view respondsToSelector:@selector(setPointerEvents:)]) {
|
if ([view respondsToSelector:@selector(setPointerEvents:)]) {
|
||||||
|
|
|
@ -15,11 +15,10 @@
|
||||||
@protocol RCTViewNodeProtocol <NSObject>
|
@protocol RCTViewNodeProtocol <NSObject>
|
||||||
|
|
||||||
@property (nonatomic, copy) NSNumber *reactTag;
|
@property (nonatomic, copy) NSNumber *reactTag;
|
||||||
@property (nonatomic, assign) CGRect frame;
|
|
||||||
|
|
||||||
- (void)insertReactSubview:(id<RCTViewNodeProtocol>)subview atIndex:(NSInteger)atIndex;
|
- (void)insertReactSubview:(id<RCTViewNodeProtocol>)subview atIndex:(NSInteger)atIndex;
|
||||||
- (void)removeReactSubview:(id<RCTViewNodeProtocol>)subview;
|
- (void)removeReactSubview:(id<RCTViewNodeProtocol>)subview;
|
||||||
- (NSArray *)reactSubviews;
|
- (NSMutableArray *)reactSubviews;
|
||||||
- (id<RCTViewNodeProtocol>)reactSuperview;
|
- (id<RCTViewNodeProtocol>)reactSuperview;
|
||||||
- (NSNumber *)reactTagAtPoint:(CGPoint)point;
|
- (NSNumber *)reactTagAtPoint:(CGPoint)point;
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,20 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
|
||||||
_currentBottomLayoutGuide = self.bottomLayoutGuide;
|
_currentBottomLayoutGuide = self.bottomLayoutGuide;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static UIView *RCTFindNavBarShadowViewInView(UIView *view)
|
||||||
|
{
|
||||||
|
if ([view isKindOfClass:[UIImageView class]] && view.bounds.size.height <= 1) {
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
for (UIView *subview in view.subviews) {
|
||||||
|
UIView *shadowView = RCTFindNavBarShadowViewInView(subview);
|
||||||
|
if (shadowView) {
|
||||||
|
return shadowView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
- (void)viewWillAppear:(BOOL)animated
|
- (void)viewWillAppear:(BOOL)animated
|
||||||
{
|
{
|
||||||
[super viewWillAppear:animated];
|
[super viewWillAppear:animated];
|
||||||
|
@ -74,17 +88,15 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
|
||||||
setNavigationBarHidden:_navItem.navigationBarHidden
|
setNavigationBarHidden:_navItem.navigationBarHidden
|
||||||
animated:animated];
|
animated:animated];
|
||||||
|
|
||||||
if (!_navItem) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UINavigationBar *bar = self.navigationController.navigationBar;
|
UINavigationBar *bar = self.navigationController.navigationBar;
|
||||||
bar.barTintColor = _navItem.barTintColor;
|
bar.barTintColor = _navItem.barTintColor;
|
||||||
bar.tintColor = _navItem.tintColor;
|
bar.tintColor = _navItem.tintColor;
|
||||||
bar.translucent = _navItem.translucent;
|
bar.translucent = _navItem.translucent;
|
||||||
if (_navItem.titleTextColor) {
|
bar.titleTextAttributes = _navItem.titleTextColor ? @{
|
||||||
[bar setTitleTextAttributes:@{NSForegroundColorAttributeName : _navItem.titleTextColor}];
|
NSForegroundColorAttributeName: _navItem.titleTextColor
|
||||||
}
|
} : nil;
|
||||||
|
|
||||||
|
RCTFindNavBarShadowViewInView(bar).hidden = _navItem.shadowHidden;
|
||||||
|
|
||||||
UINavigationItem *item = self.navigationItem;
|
UINavigationItem *item = self.navigationItem;
|
||||||
item.title = _navItem.title;
|
item.title = _navItem.title;
|
||||||
|
@ -129,7 +141,8 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
|
||||||
// finishes, be it a swipe to go back or a standard tap on the back button
|
// finishes, be it a swipe to go back or a standard tap on the back button
|
||||||
[super didMoveToParentViewController:parent];
|
[super didMoveToParentViewController:parent];
|
||||||
if (parent == nil || [parent isKindOfClass:[UINavigationController class]]) {
|
if (parent == nil || [parent isKindOfClass:[UINavigationController class]]) {
|
||||||
[self.navigationListener wrapperViewController:self didMoveToNavigationController:(UINavigationController *)parent];
|
[self.navigationListener wrapperViewController:self
|
||||||
|
didMoveToNavigationController:(UINavigationController *)parent];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,8 @@
|
||||||
"init.sh",
|
"init.sh",
|
||||||
"LICENSE",
|
"LICENSE",
|
||||||
"PATENTS",
|
"PATENTS",
|
||||||
"README.md"
|
"README.md",
|
||||||
|
"jestSupport"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
|
|
|
@ -27,6 +27,11 @@ window.onbeforeunload = function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Alias native implementations needed by the debugger before platform-specific
|
||||||
|
// implementations are loaded into the global namespace
|
||||||
|
var debuggerSetTimeout = window.setTimeout;
|
||||||
|
var DebuggerWebSocket = window.WebSocket;
|
||||||
|
|
||||||
function setStatus(status) {
|
function setStatus(status) {
|
||||||
document.getElementById('status').innerHTML = status;
|
document.getElementById('status').innerHTML = status;
|
||||||
}
|
}
|
||||||
|
@ -58,35 +63,43 @@ var messageHandlers = {
|
||||||
sendReply(JSON.stringify(returnValue));
|
sendReply(JSON.stringify(returnValue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
var ws = new WebSocket('ws://' + window.location.host + '/debugger-proxy');
|
function connectToDebuggerProxy() {
|
||||||
|
var ws = new DebuggerWebSocket('ws://' + window.location.host + '/debugger-proxy');
|
||||||
|
|
||||||
ws.onopen = function() {
|
ws.onopen = function() {
|
||||||
if (sessionID) {
|
if (sessionID) {
|
||||||
setStatus('Debugger session #' + sessionID + ' active');
|
setStatus('Debugger session #' + sessionID + ' active');
|
||||||
ws.send(JSON.stringify({replyID: parseInt(sessionID, 10)}));
|
ws.send(JSON.stringify({replyID: parseInt(sessionID, 10)}));
|
||||||
} else {
|
} else {
|
||||||
setStatus('Waiting, press ⌘R in simulator to reload and connect');
|
setStatus('Waiting, press ⌘R in simulator to reload and connect');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
ws.onmessage = function(message) {
|
ws.onmessage = function(message) {
|
||||||
var object = JSON.parse(message.data);
|
var object = JSON.parse(message.data);
|
||||||
var sendReply = function(result) {
|
var sendReply = function(result) {
|
||||||
ws.send(JSON.stringify({replyID: object.id, result: result}));
|
ws.send(JSON.stringify({replyID: object.id, result: result}));
|
||||||
}
|
};
|
||||||
var handler = messageHandlers[object.method];
|
var handler = messageHandlers[object.method];
|
||||||
if (handler) {
|
if (handler) {
|
||||||
handler(object, sendReply);
|
handler(object, sendReply);
|
||||||
} else {
|
} else {
|
||||||
console.warn('Unknown method: ' + object.method);
|
console.warn('Unknown method: ' + object.method);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = function() {
|
||||||
|
setStatus('Disconnected from proxy. Attempting reconnection. Is node server running?');
|
||||||
|
|
||||||
|
sessionID = null;
|
||||||
|
window.localStorage.removeItem('sessionID');
|
||||||
|
debuggerSetTimeout(connectToDebuggerProxy, 100);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
ws.onclose = function() {
|
connectToDebuggerProxy();
|
||||||
setStatus('Disconnected from proxy. Is node server running?');
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadScript(src, callback) {
|
function loadScript(src, callback) {
|
||||||
var script = document.createElement('script');
|
var script = document.createElement('script');
|
||||||
|
|
Loading…
Reference in New Issue