Updates from Wed 6 May

This commit is contained in:
Alex Kotliarskyi 2015-05-06 16:08:10 -07:00
commit cad5cdef42
19 changed files with 273 additions and 148 deletions

View File

@ -17,6 +17,7 @@
00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; };
00E356F31AD99517003FC87E /* SampleAppTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* SampleAppTests.m */; };
133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; };
139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; };
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
@ -82,6 +83,13 @@
remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
remoteInfo = SampleApp;
};
139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 134814201AA4EA6300B7C361;
remoteInfo = RCTSettings;
};
146834031AC3E56700842450 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */;
@ -117,6 +125,7 @@
00E356EE1AD99517003FC87E /* SampleAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SampleAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
00E356F21AD99517003FC87E /* SampleAppTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SampleAppTests.m; sourceTree = "<group>"; };
139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../../Libraries/Settings/RCTSettings.xcodeproj; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* SampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = iOS/AppDelegate.h; sourceTree = "<group>"; };
13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = iOS/AppDelegate.m; sourceTree = "<group>"; };
@ -142,6 +151,7 @@
buildActionMask = 2147483647;
files = (
146834051AC3E58100842450 /* libReact.a in Frameworks */,
139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */,
00481BE81AC0C86700671115 /* libRCTWebSocketDebugger.a in Frameworks */,
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */,
00C302E61ABCBA2D00DB3ED1 /* libRCTAdSupport.a in Frameworks */,
@ -230,6 +240,14 @@
name = "Supporting Files";
sourceTree = "<group>";
};
139105B71AF99BAD00B5F7CC /* Products */ = {
isa = PBXGroup;
children = (
139105C11AF99BAD00B5F7CC /* libRCTSettings.a */,
);
name = Products;
sourceTree = "<group>";
};
13B07FAE1A68108700A75B9A /* SampleApp */ = {
isa = PBXGroup;
children = (
@ -263,14 +281,15 @@
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup;
children = (
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */,
146833FF1AC3E56700842450 /* React.xcodeproj */,
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */,
00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */,
00C302AF1ABCB8E700DB3ED1 /* RCTAdSupport.xcodeproj */,
00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */,
00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */,
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */,
00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */,
139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */,
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */,
00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */,
00481BDB1AC0C7FA00671115 /* RCTWebSocketDebugger.xcodeproj */,
);
@ -395,6 +414,10 @@
ProductGroup = 00C302D41ABCB9D200DB3ED1 /* Products */;
ProjectRef = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */;
},
{
ProductGroup = 139105B71AF99BAD00B5F7CC /* Products */;
ProjectRef = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */;
},
{
ProductGroup = 832341B11AAA6A8300B99B32 /* Products */;
ProjectRef = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */;
@ -470,6 +493,13 @@
remoteRef = 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
139105C11AF99BAD00B5F7CC /* libRCTSettings.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTSettings.a;
remoteRef = 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
146834041AC3E56700842450 /* libReact.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;

View File

@ -24,7 +24,7 @@ var SampleApp = React.createClass({
</Text>
<Text style={styles.instructions}>
Press Cmd+R to reload,{'\n'}
Cmd+Control+Z for dev menu
Cmd+D or shake for dev menu
</Text>
</View>
);

View File

@ -13,12 +13,14 @@
var LayoutPropTypes = require('LayoutPropTypes');
var ReactPropTypes = require('ReactPropTypes');
var TransformPropTypes = require('TransformPropTypes');
/**
* Warning: Some of these properties may not be supported in all releases.
*/
var ViewStylePropTypes = {
...LayoutPropTypes,
...TransformPropTypes,
backgroundColor: ReactPropTypes.string,
borderColor: ReactPropTypes.string,
borderTopColor: ReactPropTypes.string,
@ -34,15 +36,6 @@ var ViewStylePropTypes = {
),
shadowOpacity: ReactPropTypes.number,
shadowRadius: ReactPropTypes.number,
transform: ReactPropTypes.arrayOf(ReactPropTypes.object),
transformMatrix: ReactPropTypes.arrayOf(ReactPropTypes.number),
// DEPRECATED
rotation: ReactPropTypes.number,
scaleX: ReactPropTypes.number,
scaleY: ReactPropTypes.number,
translateX: ReactPropTypes.number,
translateY: ReactPropTypes.number,
};
module.exports = ViewStylePropTypes;

View File

@ -227,15 +227,17 @@ var Navigator = React.createClass({
renderScene: PropTypes.func.isRequired,
/**
* Provide a single "route" to start on. A route is an arbitrary object
* that the navigator will use to identify each scene before rendering.
* Either initialRoute or initialRouteStack is required.
* Specify a route to start on. A route is an object that the navigator
* will use to identify each scene to render. `initialRoute` must be
* a route in the `initialRouteStack` if both props are provided. The
* `initialRoute` will default to the last item in the `initialRouteStack`.
*/
initialRoute: PropTypes.object,
/**
* Provide a set of routes to initially mount the scenes for. Required if no
* initialRoute is provided
* Provide a set of routes to initially mount. Required if no initialRoute
* is provided. Otherwise, it will default to an array containing only the
* `initialRoute`
*/
initialRouteStack: PropTypes.arrayOf(PropTypes.object),
@ -295,21 +297,18 @@ var Navigator = React.createClass({
},
getInitialState: function() {
var routeStack = this.props.initialRouteStack || [];
var initialRouteIndex = 0;
if (this.props.initialRoute && routeStack.length) {
var routeStack = this.props.initialRouteStack || [this.props.initialRoute];
invariant(
routeStack.length >= 1,
'Navigator requires props.initialRoute or props.initialRouteStack.'
);
var initialRouteIndex = routeStack.length - 1;
if (this.props.initialRoute) {
initialRouteIndex = routeStack.indexOf(this.props.initialRoute);
invariant(
initialRouteIndex !== -1,
'initialRoute is not in initialRouteStack.'
);
} else if (this.props.initialRoute) {
routeStack = [this.props.initialRoute];
} else {
invariant(
routeStack.length >= 1,
'Navigator requires props.initialRoute or props.initialRouteStack.'
);
}
return {
sceneConfigStack: routeStack.map(
@ -689,7 +688,8 @@ var Navigator = React.createClass({
var enabledSceneNativeProps = {
left: sceneStyle.left,
};
if (sceneIndex !== this.state.transitionFromIndex) {
if (sceneIndex !== this.state.transitionFromIndex &&
sceneIndex !== this.state.presentedIndex) {
// If we are not in a transition from this index, make sure opacity is 0
// to prevent the enabled scene from flashing over the presented scene
enabledSceneNativeProps.opacity = 0;

View File

@ -14,9 +14,11 @@
var ImageResizeMode = require('ImageResizeMode');
var LayoutPropTypes = require('LayoutPropTypes');
var ReactPropTypes = require('ReactPropTypes');
var TransformPropTypes = require('TransformPropTypes');
var ImageStylePropTypes = {
...LayoutPropTypes,
...TransformPropTypes,
resizeMode: ReactPropTypes.oneOf(Object.keys(ImageResizeMode)),
backgroundColor: ReactPropTypes.string,
borderColor: ReactPropTypes.string,

View File

@ -66,7 +66,7 @@ class XMLHttpRequestBase {
getResponseHeader(header: string): ?string {
if (this.responseHeaders) {
var value = this.responseHeaders[header];
var value = this.responseHeaders[header.toLowerCase()];
return value !== undefined ? value : null;
}
return null;
@ -132,7 +132,12 @@ class XMLHttpRequestBase {
return;
}
this.status = status;
this.responseHeaders = responseHeaders || {};
// Headers should be case-insensitive
var lcResponseHeaders = {};
for (var header in responseHeaders) {
lcResponseHeaders[header.toLowerCase()] = responseHeaders[header];
}
this.responseHeaders = lcResponseHeaders;
this.responseText = responseText;
this._setReadyState(this.DONE);
this._sendLoad();

View File

@ -12,6 +12,7 @@
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTUtils.h"
@implementation RCTSettingsManager
{
@ -53,13 +54,15 @@ RCT_EXPORT_MODULE()
return;
}
[_bridge.eventDispatcher sendDeviceEventWithName:@"settingsUpdated" body:[_defaults dictionaryRepresentation]];
[_bridge.eventDispatcher
sendDeviceEventWithName:@"settingsUpdated"
body:RCTJSONClean([_defaults dictionaryRepresentation])];
}
- (NSDictionary *)constantsToExport
{
return @{
@"settings": [_defaults dictionaryRepresentation]
@"settings": RCTJSONClean([_defaults dictionaryRepresentation])
};
}

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule TransformPropTypes
* @flow
*/
'use strict';
var ReactPropTypes = require('ReactPropTypes');
var TransformPropTypes = {
transform: ReactPropTypes.arrayOf(ReactPropTypes.object),
transformMatrix: ReactPropTypes.arrayOf(ReactPropTypes.number),
// DEPRECATED
rotation: ReactPropTypes.number,
scaleX: ReactPropTypes.number,
scaleY: ReactPropTypes.number,
translateX: ReactPropTypes.number,
translateY: ReactPropTypes.number,
};
module.exports = TransformPropTypes;

View File

@ -39,6 +39,7 @@
+ (NSString *)NSString:(id)json;
+ (NSNumber *)NSNumber:(id)json;
+ (NSData *)NSData:(id)json;
+ (NSIndexSet *)NSIndexSet:(id)json;
+ (NSURL *)NSURL:(id)json;
+ (NSURLRequest *)NSURLRequest:(id)json;
@ -76,6 +77,7 @@
+ (UIImage *)UIImage:(id)json;
+ (CGImageRef)CGImage:(id)json;
+ (UIFont *)UIFont:(id)json;
+ (UIFont *)UIFont:(UIFont *)font withSize:(id)json;
+ (UIFont *)UIFont:(UIFont *)font withWeight:(id)json;
+ (UIFont *)UIFont:(UIFont *)font withStyle:(id)json;

View File

@ -64,6 +64,20 @@ RCT_CONVERTER(NSString *, NSString, description)
return [[self NSString:json] dataUsingEncoding:NSUTF8StringEncoding];
}
+ (NSIndexSet *)NSIndexSet:(id)json
{
json = [self NSNumberArray:json];
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init];
for (NSNumber *number in json) {
NSInteger index = number.integerValue;
if (RCT_DEBUG && index < 0) {
RCTLogError(@"Invalid index value %zd. Indices must be positive.", index);
}
[indexSet addIndex:index];
}
return indexSet;
}
+ (NSURL *)NSURL:(id)json
{
NSString *path = [self NSString:json];
@ -679,6 +693,16 @@ static BOOL RCTFontIsCondensed(UIFont *font)
return (symbolicTraits & UIFontDescriptorTraitCondensed) != 0;
}
+ (UIFont *)UIFont:(id)json
{
json = [self NSDictionary:json];
return [self UIFont:nil
withFamily:json[@"fontFamily"]
size:json[@"fontSize"]
weight:json[@"fontWeight"]
style:json[@"fontStyle"]];
}
+ (UIFont *)UIFont:(UIFont *)font withSize:(id)json
{
return [self UIFont:font withFamily:nil size:json weight:nil style:nil];
@ -728,11 +752,6 @@ static BOOL RCTFontIsCondensed(UIFont *font)
// Get font family
familyName = [self NSString:family] ?: familyName;
// Get font style
if (style) {
isItalic = [self RCTFontStyle:style];
}
// Gracefully handle being given a font name rather than font family, for
// example: "Helvetica Light Oblique" rather than just "Helvetica".
if ([UIFont fontNamesForFamilyName:familyName].count == 0) {
@ -751,6 +770,11 @@ static BOOL RCTFontIsCondensed(UIFont *font)
}
}
// Get font style
if (style) {
isItalic = [self RCTFontStyle:style];
}
// Get font weight
if (weight) {
fontWeight = [self RCTFontWeight:weight];
@ -758,13 +782,7 @@ static BOOL RCTFontIsCondensed(UIFont *font)
// Get the closest font that matches the given weight for the fontFamily
UIFont *bestMatch = [UIFont fontWithName:font.fontName size: fontSize];
CGFloat closestWeight;
if (font && [font.familyName isEqualToString: familyName]) {
closestWeight = RCTWeightOfFont(font);
} else {
closestWeight = INFINITY;
}
CGFloat closestWeight = INFINITY;
for (NSString *name in [UIFont fontNamesForFamilyName:familyName]) {
UIFont *match = [UIFont fontWithName:name size:fontSize];
@ -834,7 +852,9 @@ static id RCTConvertPropertyListValue(id json)
{
if (!json || json == (id)kCFNull) {
return nil;
} else if ([json isKindOfClass:[NSDictionary class]]) {
}
if ([json isKindOfClass:[NSDictionary class]]) {
__block BOOL copy = NO;
NSMutableDictionary *values = [[NSMutableDictionary alloc] initWithCapacity:[json count]];
[json enumerateKeysAndObjectsUsingBlock:^(NSString *key, id jsonValue, BOOL *stop) {
@ -845,7 +865,9 @@ static id RCTConvertPropertyListValue(id json)
copy |= value != jsonValue;
}];
return copy ? values : json;
} else if ([json isKindOfClass:[NSArray class]]) {
}
if ([json isKindOfClass:[NSArray class]]) {
__block BOOL copy = NO;
__block NSArray *values = json;
[json enumerateObjectsUsingBlock:^(id jsonValue, NSUInteger idx, BOOL *stop) {
@ -860,15 +882,17 @@ static id RCTConvertPropertyListValue(id json)
for (NSInteger i = 0; i < idx; i++) {
[(NSMutableArray *)values addObject:json[i]];
}
[(NSMutableArray *)values addObject:value];
if (value) {
[(NSMutableArray *)values addObject:value];
}
copy = YES;
}
}];
return values;
} else {
// All other JSON types are supported by property lists
return json;
}
// All other JSON types are supported by property lists
return json;
}
+ (NSPropertyList)NSPropertyList:(id)json

View File

@ -66,9 +66,7 @@ RCT_EXPORT_MODULE()
// We're swizzling here because it's poor form to override methods in a category,
// however UIWindow doesn't actually implement motionEnded:withEvent:, so there's
// no need to call the original implementation.
#if RCT_DEV
RCTSwapInstanceMethods([UIWindow class], @selector(motionEnded:withEvent:), @selector(RCT_motionEnded:withEvent:));
#endif
}
- (instancetype)init
@ -121,8 +119,6 @@ RCT_EXPORT_MODULE()
- (void)updateSettings
{
_settings = [NSMutableDictionary dictionaryWithDictionary:[_defaults objectForKey:RCTDevMenuSettingsKey]];
__weak RCTDevMenu *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
RCTDevMenu *strongSelf = weakSelf;
@ -130,6 +126,8 @@ RCT_EXPORT_MODULE()
return;
}
strongSelf->_settings = [NSMutableDictionary dictionaryWithDictionary:[strongSelf->_defaults objectForKey:RCTDevMenuSettingsKey]];
strongSelf.shakeToShow = [strongSelf->_settings[@"shakeToShow"] ?: @YES boolValue];
strongSelf.profilingEnabled = [strongSelf->_settings[@"profilingEnabled"] ?: @NO boolValue];
strongSelf.liveReloadEnabled = [strongSelf->_settings[@"liveReloadEnabled"] ?: @NO boolValue];

View File

@ -19,6 +19,9 @@
RCT_EXTERN NSString *RCTJSONStringify(id jsonObject, NSError **error);
RCT_EXTERN id RCTJSONParse(NSString *jsonString, NSError **error);
// Strip non JSON-safe values from an object graph
RCT_EXTERN id RCTJSONClean(id object);
// Get MD5 hash of a string (TODO: currently unused. Remove?)
RCT_EXTERN NSString *RCTMD5Hash(NSString *string);

View File

@ -20,7 +20,7 @@
NSString *RCTJSONStringify(id jsonObject, NSError **error)
{
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonObject options:0 error:error];
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonObject options:(NSJSONWritingOptions)NSJSONReadingAllowFragments error:error];
return jsonData ? [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] : nil;
}
@ -42,6 +42,57 @@ id RCTJSONParse(NSString *jsonString, NSError **error)
return [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:error];
}
id RCTJSONClean(id object)
{
static dispatch_once_t onceToken;
static NSSet *validLeafTypes;
dispatch_once(&onceToken, ^{
validLeafTypes = [[NSSet alloc] initWithArray:@[
[NSString class],
[NSMutableString class],
[NSNumber class],
[NSNull class],
]];
});
if ([validLeafTypes containsObject:[object classForCoder]]) {
return object;
}
if ([object isKindOfClass:[NSDictionary class]]) {
__block BOOL copy = NO;
NSMutableDictionary *values = [[NSMutableDictionary alloc] initWithCapacity:[object count]];
[object enumerateKeysAndObjectsUsingBlock:^(NSString *key, id item, BOOL *stop) {
id value = RCTJSONClean(item);
values[key] = value;
copy |= value != item;
}];
return copy ? values : object;
}
if ([object isKindOfClass:[NSArray class]]) {
__block BOOL copy = NO;
__block NSArray *values = object;
[object enumerateObjectsUsingBlock:^(id item, NSUInteger idx, BOOL *stop) {
id value = RCTJSONClean(item);
if (copy) {
[(NSMutableArray *)values addObject:value];
} else if (value != item) {
// Converted value is different, so we'll need to copy the array
values = [[NSMutableArray alloc] initWithCapacity:values.count];
for (NSInteger i = 0; i < idx; i++) {
[(NSMutableArray *)values addObject:object[i]];
}
[(NSMutableArray *)values addObject:value];
copy = YES;
}
}];
return values;
}
return (id)kCFNull;
}
NSString *RCTMD5Hash(NSString *string)
{
const char *str = [string UTF8String];

View File

@ -45,6 +45,6 @@
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
@property (nonatomic, assign) NSTimeInterval scrollEventThrottle;
@property (nonatomic, assign) BOOL centerContent;
@property (nonatomic, copy) NSArray *stickyHeaderIndices;
@property (nonatomic, copy) NSIndexSet *stickyHeaderIndices;
@end

View File

@ -28,8 +28,8 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
*/
@interface RCTCustomScrollView : UIScrollView<UIGestureRecognizerDelegate>
@property (nonatomic, copy, readwrite) NSArray *stickyHeaderIndices;
@property (nonatomic, readwrite, assign) BOOL centerContent;
@property (nonatomic, copy) NSIndexSet *stickyHeaderIndices;
@property (nonatomic, assign) BOOL centerContent;
@end
@ -155,97 +155,72 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
[super setContentOffset:contentOffset];
}
- (void)setBounds:(CGRect)bounds
{
[super setBounds:bounds];
[self dockClosestSectionHeader];
}
- (void)dockClosestSectionHeader
{
UIView *contentView = [self contentView];
if (_stickyHeaderIndices.count == 0 || !contentView) {
CGFloat scrollTop = self.bounds.origin.y + self.contentInset.top;
// Find the section headers that need to be docked
__block UIView *previousHeader = nil;
__block UIView *currentHeader = nil;
__block UIView *nextHeader = nil;
NSInteger subviewCount = contentView.reactSubviews.count;
[_stickyHeaderIndices enumerateIndexesWithOptions:0 usingBlock:^(NSUInteger idx, BOOL *stop) {
if (idx >= subviewCount) {
RCTLogError(@"Sticky header index %zd was outside the range {0, %zd}", idx, subviewCount);
return;
}
UIView *header = contentView.reactSubviews[idx];
// If nextHeader not yet found, search for docked headers
if (!nextHeader) {
CGFloat height = header.bounds.size.height;
CGFloat top = header.center.y - height * header.layer.anchorPoint.y;
if (top > scrollTop) {
nextHeader = header;
} else {
previousHeader = currentHeader;
currentHeader = header;
}
}
// Reset transforms for header views
header.transform = CGAffineTransformIdentity;
header.layer.zPosition = ZINDEX_DEFAULT;
}];
// If no docked header, bail out
if (!currentHeader) {
return;
}
// find the section header that needs to be docked
NSInteger firstIndexInView = [[_stickyHeaderIndices firstObject] integerValue] + 1;
CGRect scrollBounds = self.bounds;
scrollBounds.origin.x += self.contentInset.left;
scrollBounds.origin.y += self.contentInset.top;
NSInteger i = 0;
for (UIView *subview in contentView.reactSubviews) {
CGRect rowFrame = [RCTCustomScrollView _calculateUntransformedFrame:subview];
if (CGRectIntersectsRect(scrollBounds, rowFrame)) {
firstIndexInView = i;
break;
}
i++;
// Adjust current header to hug the top of the screen
CGFloat currentFrameHeight = currentHeader.bounds.size.height;
CGFloat currentFrameTop = currentHeader.center.y - currentFrameHeight * currentHeader.layer.anchorPoint.y;
CGFloat yOffset = scrollTop - currentFrameTop;
if (nextHeader) {
// The next header nudges the current header out of the way when it reaches
// the top of the screen
CGFloat nextFrameHeight = nextHeader.bounds.size.height;
CGFloat nextFrameTop = nextHeader.center.y - nextFrameHeight * nextHeader.layer.anchorPoint.y;
CGFloat overlap = currentFrameHeight - (nextFrameTop - scrollTop);
yOffset -= MAX(0, overlap);
}
NSInteger stickyHeaderii = 0;
for (NSNumber *stickyHeaderI in _stickyHeaderIndices) {
if ([stickyHeaderI integerValue] > firstIndexInView) {
break;
}
stickyHeaderii++;
}
stickyHeaderii = MAX(0, stickyHeaderii - 1);
// Set up transforms for the various section headers
NSInteger currentlyDockedIndex = [_stickyHeaderIndices[stickyHeaderii] integerValue];
NSInteger previouslyDockedIndex = stickyHeaderii > 0 ? [_stickyHeaderIndices[stickyHeaderii-1] integerValue] : -1;
NSInteger nextDockedIndex = (stickyHeaderii < _stickyHeaderIndices.count - 1) ?
[_stickyHeaderIndices[stickyHeaderii + 1] integerValue] : -1;
UIView *currentHeader = contentView.reactSubviews[currentlyDockedIndex];
UIView *previousHeader = previouslyDockedIndex >= 0 ? contentView.reactSubviews[previouslyDockedIndex] : nil;
CGRect curFrame = [RCTCustomScrollView _calculateUntransformedFrame:currentHeader];
currentHeader.transform = CGAffineTransformMakeTranslation(0, yOffset);
currentHeader.layer.zPosition = ZINDEX_STICKY_HEADER;
if (previousHeader) {
// the previous header is offset to sit right above the currentlyDockedHeader's initial position
// (so it scrolls away nicely once the currentHeader locks into position)
CGRect previousFrame = [RCTCustomScrollView _calculateUntransformedFrame:previousHeader];
CGFloat yOffset = curFrame.origin.y - previousFrame.origin.y - previousFrame.size.height;
// The previous header sits right above the currentHeader's initial position
// so it scrolls away nicely once the currentHeader has locked into place
CGFloat previousFrameHeight = previousHeader.bounds.size.height;
CGFloat targetCenter = currentFrameTop - previousFrameHeight * (1.0 - previousHeader.layer.anchorPoint.y);
yOffset = targetCenter - previousHeader.center.y;
previousHeader.transform = CGAffineTransformMakeTranslation(0, yOffset);
previousHeader.layer.zPosition = ZINDEX_STICKY_HEADER;
}
UIView *nextHeader = nextDockedIndex >= 0 ? contentView.reactSubviews[nextDockedIndex] : nil;
CGRect nextFrame = [RCTCustomScrollView _calculateUntransformedFrame:nextHeader];
if (curFrame.origin.y < scrollBounds.origin.y) {
// scrolled off (or being scrolled off) the top of the screen
CGFloat yOffset = 0;
if (nextHeader && nextFrame.origin.y < scrollBounds.origin.y + curFrame.size.height) {
// next frame is bumping me off if scrolling down (or i'm bumping the next one off if scrolling up)
yOffset = nextFrame.origin.y - curFrame.origin.y - curFrame.size.height;
} else {
// standard sticky header position
yOffset = scrollBounds.origin.y - curFrame.origin.y;
}
currentHeader.transform = CGAffineTransformMakeTranslation(0, yOffset);
currentHeader.layer.zPosition = ZINDEX_STICKY_HEADER;
} else {
// i'm the current header but in the viewport, so just scroll in normal position
currentHeader.transform = CGAffineTransformIdentity;
currentHeader.layer.zPosition = ZINDEX_DEFAULT;
}
// in our setup, 'next header' will always just scroll with the page
if (nextHeader) {
nextHeader.transform = CGAffineTransformIdentity;
nextHeader.layer.zPosition = ZINDEX_DEFAULT;
}
}
+ (CGRect)_calculateUntransformedFrame:(UIView *)view
{
CGRect frame = CGRectNull;
if (view) {
frame.size = view.bounds.size;
frame.origin = CGPointMake(view.layer.position.x - view.bounds.size.width * view.layer.anchorPoint.x, view.layer.position.y - view.bounds.size.height * view.layer.anchorPoint.y);
}
return frame;
}
@end
@ -312,7 +287,7 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
_scrollView.centerContent = centerContent;
}
- (void)setStickyHeaderIndices:(NSArray *)headerIndices
- (void)setStickyHeaderIndices:(NSIndexSet *)headerIndices
{
RCTAssert(_scrollView.contentSize.width <= self.frame.size.width,
@"sticky headers are not supported with horizontal scrolled views");
@ -340,14 +315,8 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
- (void)setContentInset:(UIEdgeInsets)contentInset
{
CGPoint contentOffset = _scrollView.contentOffset;
_contentInset = contentInset;
[RCTView autoAdjustInsetsForView:self
withScrollView:_scrollView
updateOffset:NO];
_scrollView.contentOffset = contentOffset;
[self setNeedsLayout];
}
- (void)scrollToOffset:(CGPoint)offset
@ -390,6 +359,7 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove)
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[_scrollView dockClosestSectionHeader];
[self updateClippedSubviews];
NSTimeInterval now = CACurrentMediaTime();

View File

@ -41,7 +41,7 @@ RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(scrollsToTop, BOOL)
RCT_EXPORT_VIEW_PROPERTY(showsHorizontalScrollIndicator, BOOL)
RCT_EXPORT_VIEW_PROPERTY(showsVerticalScrollIndicator, BOOL)
RCT_EXPORT_VIEW_PROPERTY(stickyHeaderIndices, NSNumberArray)
RCT_EXPORT_VIEW_PROPERTY(stickyHeaderIndices, NSIndexSet)
RCT_EXPORT_VIEW_PROPERTY(scrollEventThrottle, NSTimeInterval)
RCT_EXPORT_VIEW_PROPERTY(zoomScale, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)

View File

@ -48,6 +48,7 @@
"chalk": "^1.0.0",
"connect": "2.8.3",
"debug": "~2.1.0",
"graceful-fs": "^3.0.6",
"image-size": "0.3.5",
"joi": "~5.1.0",
"jstransform": "10.1.0",

View File

@ -10,7 +10,7 @@
on run argv
set theURL to item 1 of argv
tell application "Google Chrome"
tell application "Chrome"
activate
if (count every window) = 0 then

View File

@ -8,6 +8,8 @@
*/
'use strict';
useGracefulFs();
var Activity = require('./src/Activity');
var Server = require('./src/Server');
@ -45,3 +47,16 @@ exports.getDependencies = function(options, main) {
return r.dependencies;
});
};
function useGracefulFs() {
var fs = require('fs');
var gracefulFs = require('graceful-fs');
// A bit sneaky but it's not straightforward to update all the
// modules we depend on.
Object.keys(fs).forEach(function(method) {
if (typeof fs[method] === 'function' && gracefulFs[method]) {
fs[method] = gracefulFs[method];
}
});
}