Skip to content

Commit cf88452

Browse files
arekkubaczkowskiArkadiusz KubaczkowskitroZee
authored
feat(ios): disable/enable scroll bounces (#274)
* chore: add prop to disable/enable scroll bounces * chore: add readme and control button * chore: generate types Co-authored-by: Arkadiusz Kubaczkowski <[email protected]> Co-authored-by: Piotr Trocki <[email protected]>
1 parent 5916b6d commit cf88452

File tree

11 files changed

+61
-9
lines changed

11 files changed

+61
-9
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ const styles = StyleSheet.create({
137137
|`showPageIndicator: boolean`|Shows the dots indicator at the bottom of the view|iOS
138138
|`overScrollMode: OverScollMode`|Used to override default value of overScroll mode. Can be `auto`, `always` or `never`. Defaults to `auto`|Android
139139
|`offscreenPageLimit: number`|Set the number of pages that should be retained to either side of the currently visible page(s). Pages beyond this limit will be recreated from the adapter when needed. Defaults to RecyclerView's caching strategy. The given value must either be larger than 0.|Android
140+
|`overdrag: boolean`|Allows for overscrolling after reaching the end or very beginning or pages|iOS
140141

141142
## Preview
142143

example/src/BasicViewPagerExample.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export function BasicViewPagerExample() {
1818
ref={ref}
1919
style={styles.viewPager}
2020
initialPage={0}
21+
overdrag={navigationPanel.overdragEnabled}
2122
scrollEnabled={navigationPanel.scrollEnabled}
2223
onPageScroll={navigationPanel.onPageScroll}
2324
onPageSelected={navigationPanel.onPageSelected}
@@ -40,6 +41,7 @@ export function BasicViewPagerExample() {
4041
[navigationPanel.pages]
4142
)}
4243
</AnimatedViewPager>
44+
4345
<NavigationPanel {...navigationPanel} />
4446
</SafeAreaView>
4547
);

example/src/component/NavigationPanel/ControlPanel.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ export function ControlsPanel({
1313
dotsEnabled,
1414
progress,
1515
disablePagesAmountManagement,
16+
overdragEnabled,
1617
setPage,
1718
addPage,
1819
removePage,
1920
toggleScroll,
2021
toggleDots,
2122
toggleAnimation,
23+
toggleOverdrag,
2224
}: NavigationPanelProps) {
2325
const firstPage = useCallback(() => setPage(0), [setPage]);
2426
const prevPage = useCallback(() => setPage(activePage - 1), [
@@ -33,6 +35,7 @@ export function ControlsPanel({
3335
pages.length,
3436
setPage,
3537
]);
38+
3639
return (
3740
<>
3841
<View style={styles.buttons}>
@@ -45,13 +48,19 @@ export function ControlsPanel({
4548
text={dotsEnabled ? 'Hide dots' : 'Show dots'}
4649
onPress={toggleDots}
4750
/>
51+
<Button
52+
style={styles.buttonAdjustment}
53+
text={overdragEnabled ? 'Overdrag Enabled' : 'Overdrag Disabled'}
54+
onPress={() => toggleOverdrag()}
55+
/>
4856
</View>
4957
{!disablePagesAmountManagement ? (
5058
<View style={styles.buttons}>
5159
<Button text="Add new page" onPress={addPage} />
5260
<Button text="Remove last page" onPress={removePage} />
5361
</View>
5462
) : null}
63+
5564
<View style={styles.buttons}>
5665
<Button
5766
text={isAnimated ? 'Turn off animations' : 'Turn animations back on'}

example/src/hook/useNavigationPanel.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export function useNavigationPanel(
2929
);
3030
const [activePage, setActivePage] = useState(0);
3131
const [isAnimated, setIsAnimated] = useState(true);
32+
const [overdragEnabled, setOverdragEnabled] = useState(false);
3233
const [scrollEnabled, setScrollEnabled] = useState(true);
3334
const [scrollState, setScrollState] = useState('idle');
3435
const [dotsEnabled, setDotsEnabled] = useState(false);
@@ -70,6 +71,10 @@ export function useNavigationPanel(
7071
() => setDotsEnabled((enabled) => !enabled),
7172
[]
7273
);
74+
const toggleOverdrag = useCallback(
75+
() => setOverdragEnabled((enabled) => !enabled),
76+
[]
77+
);
7378

7479
const onPageScroll = useMemo(
7580
() =>
@@ -144,6 +149,7 @@ export function useNavigationPanel(
144149
scrollEnabled,
145150
dotsEnabled,
146151
progress,
152+
overdragEnabled,
147153
setPage,
148154
addPage,
149155
removePage,
@@ -154,6 +160,7 @@ export function useNavigationPanel(
154160
onPageScroll,
155161
onPageSelected,
156162
onPageScrollStateChanged,
163+
toggleOverdrag,
157164
logs,
158165
};
159166
}

ios/ReactNativePageView.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN
2020
@property(nonatomic, copy) RCTDirectEventBlock onPageSelected;
2121
@property(nonatomic, copy) RCTDirectEventBlock onPageScroll;
2222
@property(nonatomic, copy) RCTDirectEventBlock onPageScrollStateChanged;
23+
@property(nonatomic) BOOL overdrag;
2324

2425

2526
- (void)goTo:(NSInteger)index animated:(BOOL)animated;

ios/ReactNativePageView.m

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher {
4141
_coalescingKey = 0;
4242
_eventDispatcher = eventDispatcher;
4343
_cachedControllers = [NSHashTable weakObjectsHashTable];
44+
_overdrag = YES;
4445
}
4546
return self;
4647
}
@@ -95,9 +96,9 @@ - (void)embed {
9596
self.scrollView = (UIScrollView *)subview;
9697
}
9798
}
98-
99+
99100
self.reactPageViewController = pageViewController;
100-
101+
101102
UIPageControl *pageIndicatorView = [self createPageIndicator];
102103

103104
pageIndicatorView.numberOfPages = self.reactSubviews.count;
@@ -210,17 +211,17 @@ - (void)goTo:(NSInteger)index animated:(BOOL)animated {
210211
if (numberOfPages == 0 || index < 0) {
211212
return;
212213
}
213-
214+
214215
UIPageViewControllerNavigationDirection direction = (index > self.currentIndex) ? UIPageViewControllerNavigationDirectionForward : UIPageViewControllerNavigationDirectionReverse;
215216

216217
NSInteger indexToDisplay = index < numberOfPages ? index : numberOfPages - 1;
217218

218219
UIView *viewToDisplay = self.reactSubviews[indexToDisplay];
219220
UIViewController *controllerToDisplay = [self findAndCacheControllerForView:viewToDisplay];
220-
221+
221222
self.reactPageIndicatorView.numberOfPages = numberOfPages;
222223
self.reactPageIndicatorView.currentPage = indexToDisplay;
223-
224+
224225
[self setReactViewControllers:indexToDisplay
225226
with:controllerToDisplay
226227
direction:direction
@@ -233,12 +234,12 @@ - (UIViewController *)findAndCacheControllerForView:(UIView *)viewToDisplay {
233234

234235
UIViewController *controllerToDisplay = [self findCachedControllerForView:viewToDisplay];
235236
UIViewController *current = [self currentlyDisplayed];
236-
237+
237238
if (!controllerToDisplay && current.view.reactTag == viewToDisplay.reactTag) {
238239
controllerToDisplay = current;
239240
}
240241
if (!controllerToDisplay) {
241-
controllerToDisplay = [[UIViewController alloc] initWithView:viewToDisplay];
242+
controllerToDisplay = [[UIViewController alloc] initWithView:viewToDisplay];
242243
}
243244
[self.cachedControllers addObject:controllerToDisplay];
244245

@@ -255,7 +256,7 @@ - (UIViewController *)nextControllerForController:(UIViewController *)controller
255256
}
256257

257258
direction == UIPageViewControllerNavigationDirectionForward ? index++ : index--;
258-
259+
259260
if (index < 0 || (index > (numberOfPages - 1))) {
260261
return nil;
261262
}
@@ -334,6 +335,14 @@ - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
334335

335336
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
336337
[self.eventDispatcher sendEvent:[[RCTOnPageScrollStateChanged alloc] initWithReactTag:self.reactTag state:@"settling" coalescingKey:_coalescingKey++]];
338+
339+
if (!_overdrag) {
340+
if (_currentIndex == 0 && scrollView.contentOffset.x <= scrollView.bounds.size.width) {
341+
*targetContentOffset = CGPointMake(scrollView.bounds.size.width, 0);
342+
} else if (_currentIndex == _reactPageIndicatorView.numberOfPages -1 && scrollView.contentOffset.x >= scrollView.bounds.size.width) {
343+
*targetContentOffset = CGPointMake(scrollView.bounds.size.width, 0);
344+
}
345+
}
337346
}
338347

339348
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
@@ -343,6 +352,15 @@ - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
343352
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
344353
CGPoint point = scrollView.contentOffset;
345354
float offset = 0;
355+
356+
if (!_overdrag) {
357+
if (_currentIndex == 0 && scrollView.contentOffset.x < scrollView.bounds.size.width) {
358+
scrollView.contentOffset = CGPointMake(scrollView.bounds.size.width, 0);
359+
} else if (_currentIndex == _reactPageIndicatorView.numberOfPages - 1 && scrollView.contentOffset.x > scrollView.bounds.size.width) {
360+
scrollView.contentOffset = CGPointMake(scrollView.bounds.size.width, 0);
361+
}
362+
}
363+
346364
if (self.orientation == UIPageViewControllerNavigationOrientationHorizontal) {
347365
if (self.frame.size.width != 0) {
348366
offset = (point.x - self.frame.size.width)/self.frame.size.width;

ios/ReactViewPagerManager.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ @implementation ReactViewPagerManager
1515
RCT_EXPORT_VIEW_PROPERTY(onPageSelected, RCTDirectEventBlock)
1616
RCT_EXPORT_VIEW_PROPERTY(onPageScroll, RCTDirectEventBlock)
1717
RCT_EXPORT_VIEW_PROPERTY(onPageScrollStateChanged, RCTDirectEventBlock)
18+
RCT_EXPORT_VIEW_PROPERTY(overdrag, BOOL)
19+
1820

1921
- (void) goToPage
2022
: (nonnull NSNumber *)reactTag index
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
/// <reference types="react" />
22
import type { NavigationPanelProps } from './types';
3-
export declare function ControlsPanel({ activePage, isAnimated, pages, scrollState, scrollEnabled, dotsEnabled, progress, disablePagesAmountManagement, setPage, addPage, removePage, toggleScroll, toggleDots, toggleAnimation, }: NavigationPanelProps): JSX.Element;
3+
export declare function ControlsPanel({ activePage, isAnimated, pages, scrollState, scrollEnabled, dotsEnabled, progress, disablePagesAmountManagement, overdragEnabled, setPage, addPage, removePage, toggleScroll, toggleDots, toggleAnimation, toggleOverdrag, }: NavigationPanelProps): JSX.Element;

lib/typescript/example/src/hook/useNavigationPanel.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export declare function useNavigationPanel(pagesAmount?: number, onPageSelectedC
1919
position: number;
2020
offset: number;
2121
};
22+
overdragEnabled: boolean;
2223
setPage: (page: number) => void;
2324
addPage: () => void;
2425
removePage: () => void;
@@ -32,5 +33,6 @@ export declare function useNavigationPanel(pagesAmount?: number, onPageSelectedC
3233
onPageScroll: (...args: any[]) => void;
3334
onPageSelected: (...args: any[]) => void;
3435
onPageScrollStateChanged: (e: PageScrollStateChangedNativeEvent) => void;
36+
toggleOverdrag: () => void;
3537
logs: EventLog[];
3638
};

lib/typescript/src/types.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,9 @@ export interface ViewPagerProps {
9595
* Android only
9696
*/
9797
overScrollMode?: OverScrollMode;
98+
/**
99+
* Determines whether it's possible to overscroll a bit
100+
* after reaching end or very beginning of pages.
101+
*/
102+
overdrag?: boolean;
98103
}

0 commit comments

Comments
 (0)