Skip to content

Commit 380a8e3

Browse files
authored
fix: pager being moved by nested scroll view (#1015)
1 parent 4b64803 commit 380a8e3

File tree

3 files changed

+129
-19
lines changed

3 files changed

+129
-19
lines changed

example/src/App.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { MaterialTopBarExample } from './MaterialTopTabExample';
3636
import { createNativeStackNavigator } from '@react-navigation/native-stack';
3737
import { SafeAreaProvider } from 'react-native-safe-area-context';
3838
import { PagerHookExample } from './PagerHookExample';
39+
import { NestedHorizontalScrollViewExample } from './NestedHorizontalScrollViewExample';
3940

4041
const examples = [
4142
{ component: BasicPagerViewExample, name: 'Basic Example' },
@@ -50,7 +51,10 @@ const examples = [
5051
component: ScrollablePagerViewExample,
5152
name: 'Scrollable PagerView Example',
5253
},
53-
{ component: TabViewInsideScrollViewExample, name: 'TabView inside ScrollView Example' },
54+
{
55+
component: TabViewInsideScrollViewExample,
56+
name: 'TabView inside ScrollView Example',
57+
},
5458
{
5559
component: ScrollViewInsideExample,
5660
name: 'ScrollView inside PagerView Example',
@@ -60,6 +64,10 @@ const examples = [
6064
name: 'Nest PagerView Example',
6165
},
6266
{ component: ScrollableTabBarExample, name: 'ScrollableTabBarExample' },
67+
{
68+
component: NestedHorizontalScrollViewExample,
69+
name: 'NestedHorizontalScrollViewExample',
70+
},
6371
{ component: AutoWidthTabBarExample, name: 'AutoWidthTabBarExample' },
6472
{ component: TabBarIconExample, name: 'TabBarIconExample' },
6573
{ component: CustomIndicatorExample, name: 'CustomIndicatorExample' },
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import React, { useMemo } from 'react';
2+
import {
3+
StyleSheet,
4+
View,
5+
SafeAreaView,
6+
Animated,
7+
Text,
8+
ScrollView,
9+
} from 'react-native';
10+
11+
import PagerView from 'react-native-pager-view';
12+
13+
import { LikeCount } from './component/LikeCount';
14+
import { NavigationPanel } from './component/NavigationPanel';
15+
import { useNavigationPanel } from './hook/useNavigationPanel';
16+
17+
const AnimatedPagerView = Animated.createAnimatedComponent(PagerView);
18+
19+
/**
20+
* When dragging the horizontal ScrollView inside the PagerView, it can slightly push pager view into a different page.
21+
* This is because scrollview in the same direction overdrags the outer scrollview (pager view).
22+
* This example reproduces this behavior.
23+
* Go to the second page and try to drag the horizontal scrollview.
24+
*/
25+
export function NestedHorizontalScrollViewExample() {
26+
const { ref, ...navigationPanel } = useNavigationPanel(3);
27+
28+
return (
29+
<SafeAreaView style={styles.container}>
30+
<AnimatedPagerView
31+
{...navigationPanel}
32+
testID="pager-view"
33+
ref={ref}
34+
style={styles.PagerView}
35+
initialPage={0}
36+
pageMargin={10}
37+
>
38+
{useMemo(
39+
() =>
40+
navigationPanel.pages.map((page, index) => {
41+
if (index === 1) {
42+
return (
43+
<ScrollView
44+
horizontal
45+
key={page.key}
46+
contentContainerStyle={{ margin: 20 }}
47+
>
48+
<View
49+
style={[styles.box, { backgroundColor: 'lightblue' }]}
50+
>
51+
<Text
52+
testID={`pageNumber${index}`}
53+
>{`Scroll View page number ${index}`}</Text>
54+
</View>
55+
<View style={styles.box}>
56+
<Text
57+
testID={`pageNumber${index}`}
58+
>{`Scroll View page number ${index}`}</Text>
59+
</View>
60+
</ScrollView>
61+
);
62+
}
63+
64+
return (
65+
<View
66+
testID="pager-view-content"
67+
key={page.key}
68+
style={page.style}
69+
collapsable={false}
70+
>
71+
<LikeCount />
72+
<Text
73+
testID={`pageNumber${index}`}
74+
>{`page number ${index}`}</Text>
75+
</View>
76+
);
77+
}),
78+
[navigationPanel.pages]
79+
)}
80+
</AnimatedPagerView>
81+
<NavigationPanel {...navigationPanel} />
82+
</SafeAreaView>
83+
);
84+
}
85+
86+
const styles = StyleSheet.create({
87+
container: {
88+
flex: 1,
89+
backgroundColor: 'white',
90+
},
91+
image: {
92+
width: 300,
93+
height: 200,
94+
padding: 20,
95+
},
96+
PagerView: {
97+
flex: 1,
98+
},
99+
box: {
100+
width: 300,
101+
height: '100%',
102+
backgroundColor: 'lightgreen',
103+
padding: 20,
104+
},
105+
});

ios/RNCPagerViewComponentView.mm

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -296,53 +296,51 @@ - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
296296

297297

298298
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
299-
CGFloat contentOffset = [self isHorizontal] ? scrollView.contentOffset.x : scrollView.contentOffset.y;
300-
CGFloat frameSize = [self isHorizontal] ? scrollView.frame.size.width : scrollView.frame.size.height;
301-
299+
BOOL isHorizontal = [self isHorizontal];
300+
CGFloat contentOffset = isHorizontal ? scrollView.contentOffset.x : scrollView.contentOffset.y;
301+
CGFloat frameSize = isHorizontal ? scrollView.frame.size.width : scrollView.frame.size.height;
302+
302303
if (frameSize == 0) {
303304
return;
304305
}
305306

306-
float offset = (contentOffset - frameSize)/frameSize;
307-
307+
float offset = (contentOffset - frameSize) / frameSize;
308308
float absoluteOffset = fabs(offset);
309-
310309
NSInteger position = _currentIndex;
311310

312311
BOOL isHorizontalRtl = [self isHorizontalRtlLayout];
313312
BOOL isAnimatingBackwards = isHorizontalRtl ? offset > 0.05f : offset < 0;
314-
315-
if (scrollView.isDragging) {
313+
BOOL isBeingMovedByNestedScrollView = !scrollView.isDragging && !scrollView.isTracking;
314+
if (scrollView.isDragging || isBeingMovedByNestedScrollView) {
316315
_destinationIndex = isAnimatingBackwards ? _currentIndex - 1 : _currentIndex + 1;
317316
}
318317

319318
if (isAnimatingBackwards) {
320-
position = _destinationIndex;
321-
absoluteOffset = fmax(0, 1 - absoluteOffset);
319+
position = _destinationIndex;
320+
absoluteOffset = fmax(0, 1 - absoluteOffset);
322321
}
323322

324323
if (!_overdrag) {
325324
NSInteger maxIndex = _nativeChildrenViewControllers.count - 1;
326-
NSInteger firstPageIndex = !isHorizontalRtl ? 0 : maxIndex;
327-
NSInteger lastPageIndex = !isHorizontalRtl ? maxIndex : 0;
325+
NSInteger firstPageIndex = isHorizontalRtl ? maxIndex : 0;
326+
NSInteger lastPageIndex = isHorizontalRtl ? 0 : maxIndex;
328327
BOOL isFirstPage = _currentIndex == firstPageIndex;
329328
BOOL isLastPage = _currentIndex == lastPageIndex;
330-
CGFloat contentOffset =[self isHorizontal] ? scrollView.contentOffset.x : scrollView.contentOffset.y;
331-
CGFloat topBound = [self isHorizontal] ? scrollView.bounds.size.width : scrollView.bounds.size.height;
329+
CGFloat topBound = isHorizontal ? scrollView.bounds.size.width : scrollView.bounds.size.height;
332330

333331
if ((isFirstPage && contentOffset <= topBound) || (isLastPage && contentOffset >= topBound)) {
334-
CGPoint croppedOffset = [self isHorizontal] ? CGPointMake(topBound, 0) : CGPointMake(0, topBound);
332+
CGPoint croppedOffset = isHorizontal ? CGPointMake(topBound, 0) : CGPointMake(0, topBound);
335333
scrollView.contentOffset = croppedOffset;
336-
absoluteOffset=0;
334+
absoluteOffset = 0;
337335
position = isLastPage ? lastPageIndex : firstPageIndex;
338336
}
339337
}
340338

341339
float interpolatedOffset = absoluteOffset * labs(_destinationIndex - _currentIndex);
342-
343340
[self sendScrollEventsForPosition:position offset:interpolatedOffset];
344341
}
345342

343+
346344
#pragma mark - UIPageViewControllerDelegate
347345

348346
- (void)pageViewController:(UIPageViewController *)pageViewController
@@ -356,7 +354,6 @@ - (void)pageViewController:(UIPageViewController *)pageViewController
356354
int position = (int) currentIndex;
357355
const auto eventEmitter = [self pagerEventEmitter];
358356
eventEmitter->onPageSelected(RNCViewPagerEventEmitter::OnPageSelected{.position = static_cast<double>(position)});
359-
eventEmitter->onPageScroll(RNCViewPagerEventEmitter::OnPageScroll{.position = static_cast<double>(position), .offset = 0.0});
360357
}
361358
}
362359

0 commit comments

Comments
 (0)