Skip to content

Commit

Permalink
"Merge pull request facebook#1532 from rxb/master\n\nDefinable distan…
Browse files Browse the repository at this point in the history
…ce pagination for ScrollView"
  • Loading branch information
miracle2k committed Sep 16, 2015
2 parents 42eb546 + e50059e commit 58e6997
Show file tree
Hide file tree
Showing 14 changed files with 150 additions and 57 deletions.
1 change: 1 addition & 0 deletions Examples/SampleApp/iOS/SampleApp.xcodeproj/project.pbxproj
Expand Up @@ -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,
Expand Down
21 changes: 21 additions & 0 deletions Libraries/Components/ScrollView/ScrollView.js
Expand Up @@ -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.
Expand Down Expand Up @@ -430,6 +449,8 @@ var validAttributes = {
scrollsToTop: true,
showsHorizontalScrollIndicator: true,
showsVerticalScrollIndicator: true,
snapToInterval: true,
snapToAlignment: true,
stickyHeaderIndices: {diff: deepDiffer},
scrollEventThrottle: true,
zoomScale: true,
Expand Down
12 changes: 6 additions & 6 deletions React/Executors/RCTContextExecutor.m
Expand Up @@ -33,6 +33,8 @@
#if RCT_JSC_PROFILER
#include <dlfcn.h>

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
Expand Down Expand Up @@ -224,20 +226,18 @@ 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
options:NSDataReadingMappedIfSafe
error:NULL];

RCTProfileSendResult(bridge, @"cpu-profile", profileData);
} else {
nativeProfilerStart(context, "profile");
}
isProfiling = !isProfiling;
}]];
}
}
Expand Down
2 changes: 2 additions & 0 deletions React/Views/RCTScrollView.h
Expand Up @@ -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
Expand Down
45 changes: 45 additions & 0 deletions React/Views/RCTScrollView.m
Expand Up @@ -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),
Expand All @@ -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);
}

Expand Down
2 changes: 2 additions & 0 deletions React/Views/RCTScrollViewManager.m
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions docs/EmbeddedApp.md
Expand Up @@ -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

Expand Down
11 changes: 3 additions & 8 deletions docs/GettingStarted.md
Expand Up @@ -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).
Expand Down Expand Up @@ -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)._
Expand Down
91 changes: 54 additions & 37 deletions docs/KnownIssues.md
Expand Up @@ -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.

```
<View style={{flex: 1}}>
<TouchableOpacity onPress={() => alert('hi!')}>
<Text>HELLO!</Text>
</TouchableOpacity>
<View style={{
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
opacity: 0}} />
</View>
<View style={{flex: 1}}>
<TouchableOpacity onPress={() => alert('hi!')}>
<Text>HELLO!</Text>
</TouchableOpacity>
<View style={{
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
opacity: 0}} />
</View>
```

###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:
```
<View collapsable={false}>
...
</View>
<View collapsable={false}>
...
</View>
```

### 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.
8 changes: 8 additions & 0 deletions docs/NativeComponentsIOS.md
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion docs/Testing.md
Expand Up @@ -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)

Expand Down
2 changes: 2 additions & 0 deletions docs/Videos.md
Expand Up @@ -13,6 +13,8 @@ next: style

<iframe width="650" height="315" src="//www.youtube.com/embed/X6YbAKiLCLU" frameborder="0" allowfullscreen></iframe>

<iframe width="650" height="315" src="//www.youtube.com/embed/oWPoW0gIzvs" frameborder="0" allowfullscreen></iframe>

### [The Changelog #149](https://thechangelog.com/149/)
With Christopher "vjeux" Chedeau and Spencer Ahrens

Expand Down
2 changes: 1 addition & 1 deletion 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": {
Expand Down
4 changes: 2 additions & 2 deletions website/src/react-native/index.js
Expand Up @@ -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"));
}
}
`}
Expand Down Expand Up @@ -341,7 +341,7 @@ public class MyCustomViewManager extends SimpleViewManager<MyCustomView> {
@Override
protected MyCustomView createViewInstance(ThemedReactContext reactContext) {
return new MyCustomView(reactContext)
return new MyCustomView(reactContext);
}
@Override
Expand Down

0 comments on commit 58e6997

Please sign in to comment.