diff --git a/Examples/SampleApp/iOS/SampleApp.xcodeproj/project.pbxproj b/Examples/SampleApp/iOS/SampleApp.xcodeproj/project.pbxproj index e0225c7c700840..7123b2d3b2cdac 100644 --- a/Examples/SampleApp/iOS/SampleApp.xcodeproj/project.pbxproj +++ b/Examples/SampleApp/iOS/SampleApp.xcodeproj/project.pbxproj @@ -604,6 +604,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEAD_CODE_STRIPPING = NO; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index 1ee37a84f56d58..a2decc36998b42 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -246,6 +246,25 @@ var ScrollView = React.createClass({ */ stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number), style: StyleSheetPropType(ViewStylePropTypes), + /** + * When set, causes the scroll view to stop at multiples of the value of + * `snapToInterval`. This can be used for paginating through children + * that have lengths smaller than the scroll view. Used in combination + * with `snapToAlignment`. + */ + snapToInterval: PropTypes.number, + /** + * When `snapToInterval` is set, `snapToAlignment` will define the relationship + * of the the snapping to the scroll view. + * - `start` (the default) will align the snap at the left (horizontal) or top (vertical) + * - `center` will align the snap in the center + * - `end` will align the snap at the right (horizontal) or bottom (vertical) + */ + snapToAlignment: PropTypes.oneOf([ + 'start', // default + 'center', + 'end', + ]), /** * Experimental: When true, offscreen child views (whose `overflow` value is * `hidden`) are removed from their native backing superview when offscreen. @@ -430,6 +449,8 @@ var validAttributes = { scrollsToTop: true, showsHorizontalScrollIndicator: true, showsVerticalScrollIndicator: true, + snapToInterval: true, + snapToAlignment: true, stickyHeaderIndices: {diff: deepDiffer}, scrollEventThrottle: true, zoomScale: true, diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 267213b202899c..c3654d198c89d4 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -33,6 +33,8 @@ #if RCT_JSC_PROFILER #include +static NSString * const RCTJSCProfilerEnabledDefaultsKey = @"RCTJSCProfilerEnabled"; + #ifndef RCT_JSC_PROFILER_DYLIB #define RCT_JSC_PROFILER_DYLIB [[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"RCTJSCProfiler.ios%zd", [[[UIDevice currentDevice] systemVersion] integerValue]] ofType:@"dylib" inDirectory:@"RCTJSCProfiler"] UTF8String] #endif @@ -224,9 +226,10 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context) nativeProfilerEnableByteCode(); } - __block BOOL isProfiling = NO; - [bridge.devMenu addItem:[RCTDevMenuItem buttonItemWithTitle:@"Profile" handler:^{ - if (isProfiling) { + [bridge.devMenu addItem:[RCTDevMenuItem toggleItemWithKey:RCTJSCProfilerEnabledDefaultsKey title:@"Start Profiling" selectedTitle:@"Stop Profiling" handler:^(BOOL shouldStart) { + if (shouldStart) { + nativeProfilerStart(context, "profile"); + } else { NSString *outputFile = [NSTemporaryDirectory() stringByAppendingPathComponent:@"cpu_profile.json"]; nativeProfilerEnd(context, "profile", outputFile.UTF8String); NSData *profileData = [NSData dataWithContentsOfFile:outputFile @@ -234,10 +237,7 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context) error:NULL]; RCTProfileSendResult(bridge, @"cpu-profile", profileData); - } else { - nativeProfilerStart(context, "profile"); } - isProfiling = !isProfiling; }]]; } } diff --git a/React/Views/RCTScrollView.h b/React/Views/RCTScrollView.h index e5c1d550af8fa4..d44be6fafae14c 100644 --- a/React/Views/RCTScrollView.h +++ b/React/Views/RCTScrollView.h @@ -44,6 +44,8 @@ @property (nonatomic, assign) BOOL automaticallyAdjustContentInsets; @property (nonatomic, assign) NSTimeInterval scrollEventThrottle; @property (nonatomic, assign) BOOL centerContent; +@property (nonatomic, assign) int snapToInterval; +@property (nonatomic, copy) NSString *snapToAlignment; @property (nonatomic, copy) NSIndexSet *stickyHeaderIndices; @end diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m index b502203f02cde8..62cc1b27102483 100644 --- a/React/Views/RCTScrollView.m +++ b/React/Views/RCTScrollView.m @@ -620,6 +620,50 @@ - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { + + + // snapToInterval + // An alternative to enablePaging which allows setting custom stopping intervals, + // smaller than a full page size. Often seen in apps which feature horizonally + // scrolling items. snapToInterval does not enforce scrolling one interval at a time + // but guarantees that the scroll will stop at an interval point. + if(self.snapToInterval){ + + CGFloat snapToIntervalF = (CGFloat)self.snapToInterval; + + // Find which axis to snap + BOOL isHorizontal = (scrollView.contentSize.width > self.frame.size.width); + + // What is the current offset? + CGFloat targetContentOffsetAlongAxis = isHorizontal ? targetContentOffset->x : targetContentOffset->y; + + // Which direction is the scroll travelling? + CGPoint translation = [scrollView.panGestureRecognizer translationInView:scrollView]; + CGFloat translationAlongAxis = isHorizontal ? translation.x : translation.y; + + // Offset based on desired alignment + CGFloat frameLength = isHorizontal ? self.frame.size.width : self.frame.size.height; + CGFloat alignmentOffset = 0.0f; + if([self.snapToAlignment isEqualToString: @"center"]){ + alignmentOffset = (frameLength * 0.5f) + (snapToIntervalF * 0.5f); + } else if ([self.snapToAlignment isEqualToString: @"end"]) { + alignmentOffset = frameLength; + } + + // Pick snap point based on direction and proximity + NSInteger snapIndex = floor((targetContentOffsetAlongAxis + alignmentOffset) / snapToIntervalF); + snapIndex = (translationAlongAxis < 0) ? snapIndex + 1 : snapIndex; + CGFloat newTargetContentOffset = ( snapIndex * snapToIntervalF ) - alignmentOffset; + + // Set new targetContentOffset + if(isHorizontal) { + targetContentOffset->x = newTargetContentOffset; + } else { + targetContentOffset->y = newTargetContentOffset; + } + + } + NSDictionary *userData = @{ @"velocity": @{ @"x": @(velocity.x), @@ -631,6 +675,7 @@ - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoi } }; [_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeEnd reactTag:self.reactTag scrollView:scrollView userData:userData]; + RCT_FORWARD_SCROLL_EVENT(scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset); } diff --git a/React/Views/RCTScrollViewManager.m b/React/Views/RCTScrollViewManager.m index 71736a344dec8b..18f5eaf1db5376 100644 --- a/React/Views/RCTScrollViewManager.m +++ b/React/Views/RCTScrollViewManager.m @@ -63,6 +63,8 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(zoomScale, CGFloat) RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets) RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets, UIEdgeInsets) +RCT_EXPORT_VIEW_PROPERTY(snapToInterval, int) +RCT_EXPORT_VIEW_PROPERTY(snapToAlignment, NSString) RCT_REMAP_VIEW_PROPERTY(contentOffset, scrollView.contentOffset, CGPoint) - (NSDictionary *)constantsToExport diff --git a/docs/EmbeddedApp.md b/docs/EmbeddedApp.md index c99fe7e956e87d..f48a148a9d6f84 100644 --- a/docs/EmbeddedApp.md +++ b/docs/EmbeddedApp.md @@ -12,8 +12,8 @@ Since React makes no assumptions about the rest of your technology stack – it ## Requirements - [CocoaPods](http://cocoapods.org/) – `gem install cocoapods` -- [io.js](http://iojs.org) - - Install **nvm** with [its setup instructions here](https://github.com/creationix/nvm#installation). Then run `nvm install iojs-v2 && nvm alias default iojs-v2`, which installs the latest compatible version of io.js and sets up your terminal so that typing `node` runs io.js. With nvm you can install multiple versions of Node and io.js and easily switch between them. +- [Node.js](http://nodejs.org) + - Install **nvm** with [its setup instructions here](https://github.com/creationix/nvm#installation). Then run `nvm install node && nvm alias default node`, which installs the latest version of Node.js and sets up your terminal so you can run it by typing `node`. With nvm you can install multiple versions of Node.js and easily switch between them. ## Install React Native Using CocoaPods diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md index 6183b21812535d..4c20287f3880a3 100644 --- a/docs/GettingStarted.md +++ b/docs/GettingStarted.md @@ -10,9 +10,9 @@ next: android-setup ## Requirements 1. OS X - Only OS X is currently supported -2. [Homebrew](http://brew.sh/) is the recommended way to install io.js, watchman, and flow. -3. Install [io.js](https://iojs.org/) 1.0 or newer. io.js is the modern version of Node. - - Install **nvm** with [its setup instructions here](https://github.com/creationix/nvm#installation). Then run `nvm install iojs-v2 && nvm alias default iojs-v2`, which installs the latest compatible version of io.js and sets up your terminal so that typing `node` runs io.js. With nvm you can install multiple versions of Node and io.js and easily switch between them. +2. [Homebrew](http://brew.sh/) is the recommended way to install nvm, watchman, and flow. +3. Install [Node.js](https://nodejs.org/) 4.0 or newer. + - Install **nvm** with Homebrew or [its setup instructions here](https://github.com/creationix/nvm#installation). Then run `nvm install node && nvm alias default node`, which installs the latest version of Node.js and sets up your terminal so you can run it by typing `node`. With nvm you can install multiple versions of Node.js and easily switch between them. - New to [npm](https://docs.npmjs.com/)? 4. `brew install watchman`. We recommend installing [watchman](https://facebook.github.io/watchman/docs/install.html), otherwise you might hit a node file watching bug. 5. `brew install flow`. If you want to use [flow](http://www.flowtype.org). @@ -46,11 +46,6 @@ To write React Native apps for Android, you will need to install the Android SDK * Press the menu button (F2 by default, or ⌘-M in Genymotion) and select *Reload JS* to see your change! * Run `adb logcat *:S ReactNative:V ReactNativeJS:V` in a terminal to see your app's logs -* `$ react-native run-android` -* Open `index.android.js` in your text editor of choice and edit some lines. -* Press the menu button (F2 by default, or ⌘-M in Genymotion) and select *Reload JS* to see your change! -* Run `adb logcat *:S ReactNative:V ReactNativeJS:V` in a terminal to see your app's logs - Congratulations! You've successfully run and modified your first React Native app. _If you run into any issues getting started, see the [troubleshooting page](/react-native/docs/troubleshooting.html#content)._ diff --git a/docs/KnownIssues.md b/docs/KnownIssues.md index c0a5a4e441d512..6ad12a54d43e91 100644 --- a/docs/KnownIssues.md +++ b/docs/KnownIssues.md @@ -7,58 +7,75 @@ permalink: docs/known-issues.html next: activityindicatorios --- -###Missing Modules and Native Views -This is an initial release of React Native Android and therefore not all of the views present on iOS are released on Android. We are very much interested in the communities' feedback on the next set of modules and views for Open Source. Not all native views between iOS and Android have a 100% equivalent representation, here it will be necessary to use a counterpart eg using ProgressBar on Android in place of ActivityIndicator on iOS. +### Missing Modules and Native Views +This is an initial release of React Native Android and therefore not all of the views present on iOS are released on Android. We are very much interested in the communities' feedback on the next set of modules and views for Open Source. Not all native views between iOS and Android have a 100% equivalent representation, here it will be necessary to use a counterpart eg using ProgressBar on Android in place of ActivityIndicator on iOS. Our provisional plan for common views and modules includes: -Views +#### Views + ``` - View Pager - Swipe Refresh - Spinner - ART - Maps - Webview +View Pager +Swipe Refresh +Spinner +ART +Maps +Webview ``` -Modules + +#### Modules + ``` - Geo Location - Net Info - Camera Roll - App State - Dialog - Intent - Media - Pasteboard - Alert +Geo Location +Net Info +Camera Roll +App State +Dialog +Intent +Media +Pasteboard +Alert ``` -###Publishing modules on Android + +### Publishing modules on Android + There is currently no easy way of publishing custom native modules on Android. Smooth work flow for contributors is important and this will be looked at very closely after the initial Open Source release. Of course the aim will be to streamline and optimize the process between iOS and Android as much as possible. -###Overlay view with opacity of 0 cannot be clicked through + +### Overlay view with opacity of 0 cannot be clicked through + There is a noted difference in the handling of Views with an opacity of 0 between iOS and Android. While iOS will allow these views to be clicked through and the View below will receive the touch input, for Android the touch will be blocked. This can be demonstrated in this example where it will only be possible to click the touchable on iOS. ``` - - alert('hi!')}> - HELLO! - - - + + alert('hi!')}> + HELLO! + + + + ``` -###Layout-only nodes on android +The behavior on Android is what you would expect from the web as well. If you want to be able to click through an overlaying transparent view, you can set `pointerEvents='none'` on it. + +### The `overflow` style property defaults to `hidden` and cannot be changed + +This is a result of how Android rendering works. This feature is not being worked on as it would be a significant undertaking and there are many more important tasks. + +### Layout-only nodes on Android + An optimization feature of the Android version of React Native is for views which only contribute to the layout to not have a native view, only their layout properties are propagated to their children views. This optimization is to provide stability in deep view hierarchies for React Native and is therefore enabled by default. Should you depend on a view being present or internal tests incorrectly detect a view is layout only it will be necessary to turn off this behavior. To do this, set `collapsable` to false as in this example: ``` - - ... - + + ... + ``` +### Memory issues with PNG images +React Native Android depends on [Fresco](https://github.com/facebook/fresco) for loading and displaying images. Currently we have disabled downsampling because it is experimental, so you may run into memory issues when loading large PNG images. diff --git a/docs/NativeComponentsIOS.md b/docs/NativeComponentsIOS.md index 483a565a2aba8b..0b369a974a0adc 100644 --- a/docs/NativeComponentsIOS.md +++ b/docs/NativeComponentsIOS.md @@ -212,6 +212,14 @@ MapView.propTypes = { Here you can see that the shape of the region is explicit in the JS documentation - ideally we could codegen some of this stuff, but that's not happening yet. +Sometimes you'll have some special properties that you need to expose for the native component, but don't actually want them as part of the API for the associated React component. For example, `Switch` has a custom `onChange` handler for the raw native event, and exposes an `onValueChange` handler property that is invoked with just the boolean value rather than the raw event. Since you don't want these native only properties to be part of the API, you don't want to put them in `propTypes`, but if you don't you'll get an error. The solution is simply to call them out via the `nativeOnly` option, e.g. + +```javascript +var RCTSwitch = requireNativeComponent('RCTSwitch', Switch, { + nativeOnly: { onChange: true } +}); +``` + ## Events So now we have a native map component that we can control easily from JS, but how do we deal with events from the user, like pinch-zooms or panning to change the visible region? The key is to make the `RCTMapManager` a delegate for all the views it vends, and forward the events to JS via the event dispatcher. This looks like so (simplified from the full implementation): diff --git a/docs/Testing.md b/docs/Testing.md index 5325f031f615e2..4ce54a151b7c6e 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -52,7 +52,7 @@ Note: In order to run your own tests, you will have to first follow the Getting ... ``` -Note: you may have to install/upgrade/link io.js and other parts of your environment in order for the tests to run correctly. Check out the latest setup in [.travis.yml](https://github.com/facebook/react-native/blob/master/.travis.yml#L11-24) +Note: you may have to install/upgrade/link Node.js and other parts of your environment in order for the tests to run correctly. Check out the latest setup in [.travis.yml](https://github.com/facebook/react-native/blob/master/.travis.yml#L11-24) ## Integration Tests (iOS only) diff --git a/docs/Videos.md b/docs/Videos.md index 9ff325570900b7..b065fcacf51164 100644 --- a/docs/Videos.md +++ b/docs/Videos.md @@ -13,6 +13,8 @@ next: style + + ### [The Changelog #149](https://thechangelog.com/149/) With Christopher "vjeux" Chedeau and Spencer Ahrens diff --git a/package.json b/package.json index 00cfd6afba1635..56bcefa8ed181f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native", - "version": "0.11.0-rc", + "version": "0.11.0", "description": "A framework for building native apps using React", "license": "BSD-3-Clause", "repository": { diff --git a/website/src/react-native/index.js b/website/src/react-native/index.js index 7a3a94a557eca8..769a5f0866f8c6 100644 --- a/website/src/react-native/index.js +++ b/website/src/react-native/index.js @@ -291,7 +291,7 @@ public class MyCustomModule extends ReactContextBaseJavaModule { // Available as NativeModules.MyCustomModule.processString @ReactMethod public void processString(String input, Callback callback) { - callback.invoke(input.replace("Goodbye", "Hello"); + callback.invoke(input.replace("Goodbye", "Hello")); } } `} @@ -341,7 +341,7 @@ public class MyCustomViewManager extends SimpleViewManager { @Override protected MyCustomView createViewInstance(ThemedReactContext reactContext) { - return new MyCustomView(reactContext) + return new MyCustomView(reactContext); } @Override