diff --git a/Examples/Movies/Movies/main.m b/Examples/Movies/Movies/main.m index 8954f343ce315c..9c58a39a483c81 100644 --- a/Examples/Movies/Movies/main.m +++ b/Examples/Movies/Movies/main.m @@ -17,6 +17,6 @@ int main(int argc, char * argv[]) { @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } diff --git a/Examples/UIExplorer/CameraRollView.ios.js b/Examples/UIExplorer/CameraRollView.ios.js index 87dd23e6decf13..74507aa2c1b50e 100644 --- a/Examples/UIExplorer/CameraRollView.ios.js +++ b/Examples/UIExplorer/CameraRollView.ios.js @@ -59,6 +59,16 @@ var propTypes = { * imagesPerRow: Number of images to be shown in each row. */ imagesPerRow: React.PropTypes.number, + + /** + * The asset type, one of 'Photos', 'Videos' or 'All' + */ + assetType: React.PropTypes.oneOf([ + 'Photos', + 'Videos', + 'All', + ]), + }; var CameraRollView = React.createClass({ @@ -69,6 +79,7 @@ var CameraRollView = React.createClass({ groupTypes: 'SavedPhotos', batchSize: 5, imagesPerRow: 1, + assetType: 'Photos', renderImage: function(asset) { var imageSize = 150; var imageStyle = [styles.image, {width: imageSize, height: imageSize}]; @@ -89,6 +100,7 @@ var CameraRollView = React.createClass({ assets: ([]: Array), groupTypes: this.props.groupTypes, lastCursor: (null : ?string), + assetType: this.props.assetType, noMore: false, loadingMore: false, dataSource: ds, @@ -124,7 +136,8 @@ var CameraRollView = React.createClass({ var fetchParams: Object = { first: this.props.batchSize, - groupTypes: this.props.groupTypes + groupTypes: this.props.groupTypes, + assetType: this.props.assetType, }; if (this.state.lastCursor) { fetchParams.after = this.state.lastCursor; diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js index fdbda4dc8d098e..a030220cabb1af 100644 --- a/Examples/UIExplorer/UIExplorerList.js +++ b/Examples/UIExplorer/UIExplorerList.js @@ -30,7 +30,7 @@ var { var { TestModule } = React.addons; var Settings = require('Settings'); -import type { Example, ExampleModule } from 'ExampleTypes'; +import type { ExampleModule } from 'ExampleTypes'; var createExamplePage = require('./createExamplePage'); @@ -154,7 +154,9 @@ class UIExplorerList extends React.Component { dataSource={this.state.dataSource} renderRow={this._renderRow.bind(this)} renderSectionHeader={this._renderSectionHeader} + keyboardShouldPersistTaps={true} automaticallyAdjustContentInsets={false} + keyboardDismissMode="onDrag" /> ); diff --git a/Libraries/CameraRoll/CameraRoll.js b/Libraries/CameraRoll/CameraRoll.js index 0d008ae75a7abe..67fa5083048dfd 100644 --- a/Libraries/CameraRoll/CameraRoll.js +++ b/Libraries/CameraRoll/CameraRoll.js @@ -29,8 +29,16 @@ var GROUP_TYPES_OPTIONS = [ 'SavedPhotos', // default ]; +var ASSET_TYPE_OPTIONS = [ + 'All', + 'Videos', + 'Photos', // default +]; + + // Flow treats Object and Array as disjoint types, currently. deepFreezeAndThrowOnMutationInDev((GROUP_TYPES_OPTIONS: any)); +deepFreezeAndThrowOnMutationInDev((ASSET_TYPE_OPTIONS: any)); /** * Shape of the param arg for the `getPhotos` function. @@ -58,6 +66,11 @@ var getPhotosParamChecker = createStrictShapeTypeChecker({ * titles. */ groupName: ReactPropTypes.string, + + /** + * Specifies filter on asset type + */ + assetType: ReactPropTypes.oneOf(ASSET_TYPE_OPTIONS), }); /** @@ -94,6 +107,7 @@ var getPhotosReturnChecker = createStrictShapeTypeChecker({ class CameraRoll { static GroupTypesOptions: Array; + static AssetTypeOptions: Array; /** * Saves the image with tag `tag` to the camera roll. * @@ -154,5 +168,6 @@ class CameraRoll { } CameraRoll.GroupTypesOptions = GROUP_TYPES_OPTIONS; +CameraRoll.AssetTypeOptions = ASSET_TYPE_OPTIONS; module.exports = CameraRoll; diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js index 17f1e477dddb75..717f03847bc94c 100644 --- a/Libraries/CustomComponents/ListView/ListView.js +++ b/Libraries/CustomComponents/ListView/ListView.js @@ -45,7 +45,6 @@ var DEFAULT_INITIAL_ROWS = 10; var DEFAULT_SCROLL_RENDER_AHEAD = 1000; var DEFAULT_END_REACHED_THRESHOLD = 1000; var DEFAULT_SCROLL_CALLBACK_THROTTLE = 50; -var RENDER_INTERVAL = 20; var SCROLLVIEW_REF = 'listviewscroll'; @@ -258,7 +257,6 @@ var ListView = React.createClass({ // the component is laid out this.requestAnimationFrame(() => { this._measureAndUpdateScrollProps(); - this.setInterval(this._renderMoreRowsIfNeeded, RENDER_INTERVAL); }); }, @@ -329,7 +327,7 @@ var ListView = React.createClass({ totalIndex++; if (this.props.renderSeparator && - (rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length -1)) { + (rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length - 1)) { var adjacentRowHighlighted = this.state.highlightedRow.sectionID === sectionID && ( this.state.highlightedRow.rowID === rowID || @@ -397,6 +395,7 @@ var ListView = React.createClass({ _setScrollVisibleHeight: function(left, top, width, height) { this.scrollProperties.visibleHeight = height; this._updateVisibleRows(); + this._renderMoreRowsIfNeeded(); }, _renderMoreRowsIfNeeded: function() { @@ -443,8 +442,8 @@ var ListView = React.createClass({ } var updatedFrames = e && e.nativeEvent.updatedChildFrames; if (updatedFrames) { - updatedFrames.forEach((frame) => { - this._childFrames[frame.index] = merge(frame); + updatedFrames.forEach((newFrame) => { + this._childFrames[newFrame.index] = merge(newFrame); }); } var dataSource = this.props.dataSource; diff --git a/Libraries/CustomComponents/Navigator/Navigator.js b/Libraries/CustomComponents/Navigator/Navigator.js index f75430b7790ecd..56bd98c73365a0 100644 --- a/Libraries/CustomComponents/Navigator/Navigator.js +++ b/Libraries/CustomComponents/Navigator/Navigator.js @@ -1290,7 +1290,7 @@ var Navigator = React.createClass({ key={this.state.idStack[i]} ref={'scene_' + i} onStartShouldSetResponderCapture={() => { - return i !== this.state.presentedIndex; + return !!this.state.transitionFromIndex || !!this.state.activeGesture; }} style={[styles.baseScene, this.props.sceneStyle, disabledSceneStyle]}> {React.cloneElement(child, { diff --git a/Libraries/Image/RCTCameraRollManager.m b/Libraries/Image/RCTCameraRollManager.m index 8e6c8a532b87bb..d7b42f88560aba 100644 --- a/Libraries/Image/RCTCameraRollManager.m +++ b/Libraries/Image/RCTCameraRollManager.m @@ -69,7 +69,9 @@ - (void)callCallback:(RCTResponseSenderBlock)callback withAssets:(NSArray *)asse NSString *afterCursor = params[@"after"]; NSString *groupTypesStr = params[@"groupTypes"]; NSString *groupName = params[@"groupName"]; + NSString *assetType = params[@"assetType"]; ALAssetsGroupType groupTypes; + if ([groupTypesStr isEqualToString:@"Album"]) { groupTypes = ALAssetsGroupAlbum; } else if ([groupTypesStr isEqualToString:@"All"]) { @@ -93,7 +95,15 @@ - (void)callCallback:(RCTResponseSenderBlock)callback withAssets:(NSArray *)asse [[RCTImageLoader assetsLibrary] enumerateGroupsWithTypes:groupTypes usingBlock:^(ALAssetsGroup *group, BOOL *stopGroups) { if (group && (groupName == nil || [groupName isEqualToString:[group valueForProperty:ALAssetsGroupPropertyName]])) { - [group setAssetsFilter:ALAssetsFilter.allPhotos]; + + if (assetType == nil || [assetType isEqualToString:@"Photos"]) { + [group setAssetsFilter:ALAssetsFilter.allPhotos]; + } else if ([assetType isEqualToString:@"Videos"]) { + [group setAssetsFilter:ALAssetsFilter.allVideos]; + } else if ([assetType isEqualToString:@"All"]) { + [group setAssetsFilter:ALAssetsFilter.allAssets]; + } + [group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stopAssets) { if (result) { NSString *uri = [(NSURL *)[result valueForProperty:ALAssetPropertyAssetURL] absoluteString]; diff --git a/Libraries/Image/__tests__/resolveAssetSource-test.js b/Libraries/Image/__tests__/resolveAssetSource-test.js index 866cf036880a19..5854dae0bd1263 100644 --- a/Libraries/Image/__tests__/resolveAssetSource-test.js +++ b/Libraries/Image/__tests__/resolveAssetSource-test.js @@ -175,7 +175,7 @@ describe('resolveAssetSource', () => { isStatic: true, width: 100, height: 200, - uri: 'assets_awesomemodule_subdir_logo1_', + uri: 'awesomemodule_subdir_logo1_', }); }); }); diff --git a/Libraries/Image/resolveAssetSource.js b/Libraries/Image/resolveAssetSource.js index 26592195d0a4a8..301d70dd9ac53a 100644 --- a/Libraries/Image/resolveAssetSource.js +++ b/Libraries/Image/resolveAssetSource.js @@ -50,7 +50,8 @@ function getPathInArchive(asset) { return (assetDir + '/' + asset.name) .toLowerCase() .replace(/\//g, '_') // Encode folder structure in file name - .replace(/([^a-z0-9_])/g, ''); // Remove illegal chars + .replace(/([^a-z0-9_])/g, '') // Remove illegal chars + .replace(/^assets_/, ''); // Remove "assets_" prefix } else { // E.g. 'assets/AwesomeModule/icon@2x.png' return getScaledAssetPath(asset); diff --git a/Libraries/Network/RCTDataManager.m b/Libraries/Network/RCTDataManager.m index 1d0a793de8e454..6a2e39b2ddb86b 100644 --- a/Libraries/Network/RCTDataManager.m +++ b/Libraries/Network/RCTDataManager.m @@ -39,34 +39,35 @@ @implementation RCTDataManager // Build data task NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *connectionError) { + NSHTTPURLResponse *httpResponse = nil; + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { + // Might be a local file request + httpResponse = (NSHTTPURLResponse *)response; + } + // Build response - NSDictionary *responseJSON; + NSArray *responseJSON; if (connectionError == nil) { NSStringEncoding encoding = NSUTF8StringEncoding; if (response.textEncodingName) { CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName); encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding); } - NSHTTPURLResponse *httpResponse = nil; - if ([response isKindOfClass:[NSHTTPURLResponse class]]) { - // Might be a local file request - httpResponse = (NSHTTPURLResponse *)response; - } - responseJSON = @{ - @"status": @([httpResponse statusCode] ?: 200), - @"responseHeaders": [httpResponse allHeaderFields] ?: @{}, - @"responseText": [[NSString alloc] initWithData:data encoding:encoding] ?: @"" - }; + responseJSON = @[ + @(httpResponse.statusCode ?: 200), + httpResponse.allHeaderFields ?: @{}, + [[NSString alloc] initWithData:data encoding:encoding] ?: @"", + ]; } else { - responseJSON = @{ - @"status": @0, - @"responseHeaders": @{}, - @"responseText": [connectionError localizedDescription] ?: [NSNull null] - }; + responseJSON = @[ + @(httpResponse.statusCode), + httpResponse.allHeaderFields ?: @{}, + connectionError.localizedDescription ?: [NSNull null], + ]; } // Send response (won't be sent on same thread as caller) - responseSender(@[RCTJSONStringify(responseJSON, NULL)]); + responseSender(responseJSON); }]; diff --git a/Libraries/Network/XMLHttpRequest.ios.js b/Libraries/Network/XMLHttpRequest.ios.js index 6c7367c18db725..64b5ea526c2435 100644 --- a/Libraries/Network/XMLHttpRequest.ios.js +++ b/Libraries/Network/XMLHttpRequest.ios.js @@ -30,10 +30,7 @@ class XMLHttpRequest extends XMLHttpRequestBase { }, // TODO: Do we need this? is it used anywhere? 'h' + crc32(method + '|' + url + '|' + data), - (result) => { - result = JSON.parse(result); - this.callback(result.status, result.responseHeaders, result.responseText); - } + this.callback.bind(this) ); } diff --git a/Libraries/ReactIOS/InspectorOverlay.js b/Libraries/ReactIOS/InspectorOverlay.js index 8b5c6c0cb2ab8e..eeb6e7965728f9 100644 --- a/Libraries/ReactIOS/InspectorOverlay.js +++ b/Libraries/ReactIOS/InspectorOverlay.js @@ -59,7 +59,7 @@ var InspectorOverlay = React.createClass({ ? 'flex-start' : 'flex-end'; - content.push(); + content.push(); content.push(); } return ( diff --git a/Libraries/Text/RCTShadowText.h b/Libraries/Text/RCTShadowText.h index 189bff79e4c109..d156bb4d60a197 100644 --- a/Libraries/Text/RCTShadowText.h +++ b/Libraries/Text/RCTShadowText.h @@ -28,7 +28,6 @@ extern NSString *const RCTReactTagAttributeName; @property (nonatomic, strong) UIColor *textBackgroundColor; @property (nonatomic, assign) NSWritingDirection writingDirection; -- (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width; - (void)recomputeText; @end diff --git a/Libraries/Text/RCTShadowText.m b/Libraries/Text/RCTShadowText.m index 7e1daf90855279..511697f8992e80 100644 --- a/Libraries/Text/RCTShadowText.m +++ b/Libraries/Text/RCTShadowText.m @@ -12,6 +12,8 @@ #import "RCTConvert.h" #import "RCTLog.h" #import "RCTShadowRawText.h" +#import "RCTSparseArray.h" +#import "RCTText.h" #import "RCTUtils.h" NSString *const RCTIsHighlightedAttributeName = @"IsHighlightedAttributeName"; @@ -19,6 +21,8 @@ @implementation RCTShadowText { + NSTextStorage *_cachedTextStorage; + CGFloat _cachedTextStorageWidth; NSAttributedString *_cachedAttributedString; CGFloat _effectiveLetterSpacing; } @@ -50,8 +54,35 @@ - (instancetype)init return self; } +- (NSDictionary *)processUpdatedProperties:(NSMutableSet *)applierBlocks + parentProperties:(NSDictionary *)parentProperties +{ + parentProperties = [super processUpdatedProperties:applierBlocks + parentProperties:parentProperties]; + + NSTextStorage *textStorage = [self buildTextStorageForWidth:self.frame.size.width]; + [applierBlocks addObject:^(RCTSparseArray *viewRegistry) { + RCTText *view = viewRegistry[self.reactTag]; + view.textStorage = textStorage; + }]; + + return parentProperties; +} + +- (void)applyLayoutNode:(css_node_t *)node + viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame + absolutePosition:(CGPoint)absolutePosition +{ + [super applyLayoutNode:node viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition]; + [self dirtyPropagation]; +} + - (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width { + if (_cachedTextStorage && width == _cachedTextStorageWidth) { + return _cachedTextStorage; + } + NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:self.attributedString]; @@ -69,13 +100,23 @@ - (NSTextStorage *)buildTextStorageForWidth:(CGFloat)width [layoutManager addTextContainer:textContainer]; [layoutManager ensureLayoutForTextContainer:textContainer]; + _cachedTextStorage = textStorage; + _cachedTextStorageWidth = width; + return textStorage; } +- (void)dirtyText +{ + [super dirtyText]; + _cachedTextStorage = nil; +} + - (void)recomputeText { [self attributedString]; [self setTextComputed]; + [self dirtyPropagation]; } - (NSAttributedString *)attributedString diff --git a/Libraries/Text/RCTTextManager.m b/Libraries/Text/RCTTextManager.m index d9e547c773156c..26c6329e2313ec 100644 --- a/Libraries/Text/RCTTextManager.m +++ b/Libraries/Text/RCTTextManager.m @@ -96,12 +96,10 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowText *)shadowVie { NSNumber *reactTag = shadowView.reactTag; UIEdgeInsets padding = shadowView.paddingAsInsets; - NSTextStorage *textStorage = [shadowView buildTextStorageForWidth:shadowView.frame.size.width]; return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { RCTText *text = viewRegistry[reactTag]; text.contentInset = padding; - text.textStorage = textStorage; }; } diff --git a/Libraries/Utilities/Dimensions.js b/Libraries/Utilities/Dimensions.js index b93000a33a8f50..31fa0d4dc4f3a6 100644 --- a/Libraries/Utilities/Dimensions.js +++ b/Libraries/Utilities/Dimensions.js @@ -29,6 +29,7 @@ if (dimensions && dimensions.windowPhysicalPixels) { width: windowPhysicalPixels.width / windowPhysicalPixels.scale, height: windowPhysicalPixels.height / windowPhysicalPixels.scale, scale: windowPhysicalPixels.scale, + fontScale: windowPhysicalPixels.fontScale, }; // delete so no callers rely on this existing diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index df34dde0629f31..9f07cf4c0b6b94 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -472,8 +472,10 @@ var MessageQueueMixin = { }, _flushedQueueUnguarded: function() { - // Call the functions registred via setImmediate - JSTimersExecution.callImmediates(); + ReactUpdates.batchedUpdates(() => { + // Call the functions registered via setImmediate + JSTimersExecution.callImmediates(); + }); var currentOutgoingItems = this._outgoingItems; this._swapAndReinitializeBuffer(); diff --git a/Libraries/Utilities/PixelRatio.js b/Libraries/Utilities/PixelRatio.js index a3e4d9e7703aaf..7660fad3055409 100644 --- a/Libraries/Utilities/PixelRatio.js +++ b/Libraries/Utilities/PixelRatio.js @@ -59,6 +59,20 @@ class PixelRatio { return Dimensions.get('window').scale; } + /** + * Returns the scaling factor for font sizes. This is the ratio that is used to calculate the + * absolute font size, so any elements that heavily depend on that should use this to do + * calculations. + * + * If a font scale is not set, this returns the device pixel ratio. + * + * Currently this is only implemented on Android and reflects the user preference set in + * Settings > Display > Font size, on iOS it will always return the default pixel ratio. + */ + static getFontScale(): number { + return Dimensions.get('window').fontScale || PixelRatio.get(); + } + /** * Converts a layout size (dp) to pixel size (px). * diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index 47c7f59420c91d..f122611cdc4d9d 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -1555,7 +1555,9 @@ - (void)_jsThreadUpdate:(CADisplayLink *)displayLink RCTProfileEndEvent(@"DispatchFrameUpdate", @"objc_call", nil); - [self.perfStats.jsGraph tick:displayLink.timestamp]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self.perfStats.jsGraph tick:displayLink.timestamp]; + }); } - (void)_mainThreadUpdate:(CADisplayLink *)displayLink diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 127cbd9fc5ee03..76e253a766a456 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -481,6 +481,10 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo shadowView.newView = NO; } + // These are blocks to be executed on each view, immediately after + // reactSetFrame: has been called. Note that if reactSetFrame: is not called, + // these won't be called either, so this is not a suitable place to update + // properties that aren't related to layout. NSMutableArray *updateBlocks = [[NSMutableArray alloc] init]; for (RCTShadowView *shadowView in viewsWithNewFrames) { RCTViewManager *manager = _viewManagerRegistry[shadowView.reactTag]; diff --git a/React/Views/RCTShadowView.h b/React/Views/RCTShadowView.h index c2e10750d3a5a8..1c44033f6df59f 100644 --- a/React/Views/RCTShadowView.h +++ b/React/Views/RCTShadowView.h @@ -20,8 +20,7 @@ typedef NS_ENUM(NSUInteger, RCTUpdateLifecycle) { RCTUpdateLifecycleDirtied, }; -// TODO: is this redundact now? -typedef void (^RCTApplierBlock)(RCTSparseArray *); +typedef void (^RCTApplierBlock)(RCTSparseArray *viewRegistry); /** * ShadowView tree mirrors RCT view tree. Every node is highly stateful. @@ -117,34 +116,48 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *); * The applierBlocks set contains RCTApplierBlock functions that must be applied * on the main thread in order to update the view. */ -- (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties; +- (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks + parentProperties:(NSDictionary *)parentProperties; + +/** + * Process the updated properties and apply them to view. Shadow view classes + * that add additional propagating properties should override this method. + */ +- (NSDictionary *)processUpdatedProperties:(NSMutableSet *)applierBlocks + parentProperties:(NSDictionary *)parentProperties NS_REQUIRES_SUPER; /** * Calculate all views whose frame needs updating after layout has been calculated. * The viewsWithNewFrame set contains the reactTags of the views that need updating. */ -- (void)collectRootUpdatedFrames:(NSMutableSet *)viewsWithNewFrame parentConstraint:(CGSize)parentConstraint; +- (void)collectRootUpdatedFrames:(NSMutableSet *)viewsWithNewFrame + parentConstraint:(CGSize)parentConstraint; + +/** + * Recursively apply layout to children. + */ +- (void)applyLayoutNode:(css_node_t *)node + viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame + absolutePosition:(CGPoint)absolutePosition NS_REQUIRES_SUPER; /** * The following are implementation details exposed to subclasses. Do not call them directly */ -- (void)fillCSSNode:(css_node_t *)node; -- (void)dirtyLayout; +- (void)fillCSSNode:(css_node_t *)node NS_REQUIRES_SUPER; +- (void)dirtyLayout NS_REQUIRES_SUPER; - (BOOL)isLayoutDirty; -// TODO: is this still needed? -- (void)dirtyPropagation; +- (void)dirtyPropagation NS_REQUIRES_SUPER; - (BOOL)isPropagationDirty; -// TODO: move this to text node? -- (void)dirtyText; +- (void)dirtyText NS_REQUIRES_SUPER; +- (void)setTextComputed NS_REQUIRES_SUPER; - (BOOL)isTextDirty; -- (void)setTextComputed; /** * Triggers a recalculation of the shadow view's layout. */ -- (void)updateLayout; +- (void)updateLayout NS_REQUIRES_SUPER; /** * Computes the recursive offset, meaning the sum of all descendant offsets - diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m index b26193f24ca946..ba70ca34f6558f 100644 --- a/React/Views/RCTShadowView.m +++ b/React/Views/RCTShadowView.m @@ -120,7 +120,9 @@ - (void)fillCSSNode:(css_node_t *)node // width = 213.5 - 106.5 = 107 // You'll notice that this is the same width we calculated for the parent view because we've taken its position into account. -- (void)applyLayoutNode:(css_node_t *)node viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame absolutePosition:(CGPoint)absolutePosition +- (void)applyLayoutNode:(css_node_t *)node + viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame + absolutePosition:(CGPoint)absolutePosition { if (!node->layout.should_update) { return; @@ -161,12 +163,19 @@ - (void)applyLayoutNode:(css_node_t *)node viewsWithNewFrame:(NSMutableSet *)vie for (int i = 0; i < node->children_count; ++i) { RCTShadowView *child = (RCTShadowView *)_reactSubviews[i]; - [child applyLayoutNode:node->get_child(node->context, i) viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition]; + [child applyLayoutNode:node->get_child(node->context, i) + viewsWithNewFrame:viewsWithNewFrame + absolutePosition:absolutePosition]; } } -- (NSDictionary *)processBackgroundColor:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties +- (NSDictionary *)processUpdatedProperties:(NSMutableSet *)applierBlocks + parentProperties:(NSDictionary *)parentProperties { + // TODO: we always refresh all propagated properties when propagation is + // dirtied, but really we should track which properties have changed and + // only update those. + if (!_backgroundColor) { UIColor *parentBackgroundColor = parentProperties[RCTBackgroundColorProp]; if (parentBackgroundColor) { @@ -190,14 +199,15 @@ - (NSDictionary *)processBackgroundColor:(NSMutableSet *)applierBlocks parentPro return parentProperties; } -- (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties +- (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks + parentProperties:(NSDictionary *)parentProperties { if (_propagationLifecycle == RCTUpdateLifecycleComputed && [parentProperties isEqualToDictionary:_lastParentProperties]) { return; } _propagationLifecycle = RCTUpdateLifecycleComputed; _lastParentProperties = parentProperties; - NSDictionary *nextProps = [self processBackgroundColor:applierBlocks parentProperties:parentProperties]; + NSDictionary *nextProps = [self processUpdatedProperties:applierBlocks parentProperties:parentProperties]; for (RCTShadowView *child in _reactSubviews) { [child collectUpdatedProperties:applierBlocks parentProperties:nextProps]; } @@ -212,21 +222,19 @@ - (void)collectRootUpdatedFrames:(NSMutableSet *)viewsWithNewFrame parentConstra - (CGRect)measureLayoutRelativeToAncestor:(RCTShadowView *)ancestor { - CGFloat totalOffsetTop = 0.0; - CGFloat totalOffsetLeft = 0.0; - CGSize size = self.frame.size; + CGPoint offset = CGPointZero; NSInteger depth = 30; // max depth to search RCTShadowView *shadowView = self; while (depth && shadowView && shadowView != ancestor) { - totalOffsetTop += shadowView.frame.origin.y; - totalOffsetLeft += shadowView.frame.origin.x; + offset.x += shadowView.frame.origin.x; + offset.y += shadowView.frame.origin.y; shadowView = shadowView->_superview; depth--; } if (ancestor != shadowView) { return CGRectNull; } - return (CGRect){{totalOffsetLeft, totalOffsetTop}, size}; + return (CGRect){offset, self.frame.size}; } - (instancetype)init diff --git a/package.json b/package.json index 535d59a57300e8..19226bc0851bc6 100644 --- a/package.json +++ b/package.json @@ -60,9 +60,9 @@ "react-timer-mixin": "^0.13.1", "react-tools": "0.13.2", "rebound": "^0.0.12", - "sane": "git://github.com/tadeuzagallo/sane.git#a029f8b04a", + "sane": "tadeuzagallo/sane#a029f8b04a", "source-map": "0.1.31", - "stacktrace-parser": "git://github.com/frantic/stacktrace-parser.git#493c5e5638", + "stacktrace-parser": "frantic/stacktrace-parser#493c5e5638", "uglify-js": "~2.4.16", "underscore": "1.7.0", "worker-farm": "^1.3.1",