Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Libraries/Components/ScrollView/ScrollView.js
Original file line number Diff line number Diff line change
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
2 changes: 2 additions & 0 deletions React/Views/RCTScrollView.h
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a safe way to determine whether or not it is horizontal? Could it ever occur that our primary scroll axis is vertical but the contentSize is both greater than the width and height of the scrollView?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was a little unsure about that one... I borrowed it from line 411

- (void)setStickyHeaderIndices:(NSIndexSet *)headerIndices
{
  RCTAssert(_scrollView.contentSize.width <= self.frame.size.width,
           @"sticky headers are not supported with horizontal scrolled views");
  _scrollView.stickyHeaderIndices = headerIndices;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fairly safe, although things might get weird in a 2-axis scrolling situation...the JS component has a horizontal prop - maybe we should pass that through to native?


// 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
Original file line number Diff line number Diff line change
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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