Skip to content
Merged
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
38 changes: 38 additions & 0 deletions .maestro/issues/issue_1096_keyboard_shrink_repro.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
appId: com.pagerviewexample
tags:
- ios
- regression
---
- runFlow: ../setup/issue_1096_keyboard_shrink_repro_setup.yaml

# With the keyboard open, SwiftUI keyboard avoidance must not shrink the page.
# A shrunk page clips Row 5 and moves its native frame, so the tap misses and
# the counter stays at 0.
- tapOn:
id: 'issue-1096-input'

- waitForAnimationToEnd:
timeout: 3000

- assertVisible:
id: 'issue-1096-last-row'

- tapOn:
id: 'issue-1096-last-row'

- assertVisible: 'last-row taps: 1'

# Dismiss the keyboard deterministically before checking the sheet drops back
# down. `hideKeyboard` alone is unreliable here: on iOS it only performs two
# blind swipes at the screen centre, and this screen has no scroll view with a
# non-default `keyboardDismissMode` under that point, so the keyboard survives.
# A single-line TextInput blurs on submit, so Enter is the reliable dismiss.
- pressKey: Enter

# No-op once the keyboard is already down; still dismisses natively on Android.
- hideKeyboard

- tapOn:
id: 'issue-1096-last-row'

- assertVisible: 'last-row taps: 2'
17 changes: 17 additions & 0 deletions .maestro/setup/issue_1096_keyboard_shrink_repro_setup.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
appId: ${APP_ID}
---
- launchApp

# The issue examples sit below the fundamental examples on the home screen.
- scrollUntilVisible:
element:
id: 'Issue #1096 Keyboard Shrink Repro'
direction: DOWN

- tapOn:
id: 'Issue #1096 Keyboard Shrink Repro'

- extendedWaitUntil:
visible:
id: 'issue-1096-pager'
timeout: 10000
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1402,7 +1402,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- react-native-pager-view (9.0.3):
- react-native-pager-view (9.0.5):
- hermes-engine
- RCTRequired
- RCTTypeSafety
Expand Down Expand Up @@ -2496,7 +2496,7 @@ SPEC CHECKSUMS:
React-Mapbuffer: 1aa9126122d4247ffc24bf9d28d50ce923499a71
React-microtasksnativemodule: d86581169e9bb5bb6f5fc3c5052f890016c1bf21
React-mutationobservernativemodule: 9a0c4e866f1ef2a57acebc902ebacbdf469ae741
react-native-pager-view: 27f9cb535d59648492018892b7e3bffe0b511807
react-native-pager-view: a18f87e19eedc3053afd4150fb52ac5f92f02d64
react-native-safe-area-context: c1eb308f4b36372a4de4b3bdaa8ed695ec3dd461
React-NativeModulesApple: cc6ec4767844d610e92cc358bd3ea34937438d56
React-networking: a8ce15641ed7775d5b54a9d0d32defc367c2216e
Expand Down
5 changes: 5 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { PagerHookExample } from './PagerHookExample';
import { NestedHorizontalScrollViewExample } from './NestedHorizontalScrollViewExample';
import { Issue1096KeyboardShrinkRepro } from './gh-issues/Issue1096KeyboardShrinkRepro';
import { Issue1098NestedPagerRepro } from './gh-issues/Issue1098NestedPagerRepro';
import { Issue1099SafeAreaRepro } from './gh-issues/Issue1099SafeAreaRepro';
import {
Expand Down Expand Up @@ -121,6 +122,10 @@ const ghIssues: Example[] = [
component: Issue1083ModalSetPageExample,
name: 'Issue #1083 Modal SetPage Repro',
},
{
component: Issue1096KeyboardShrinkRepro,
name: 'Issue #1096 Keyboard Shrink Repro',
},
{
component: Issue1098NestedPagerRepro,
name: 'Issue #1098 Nested Pager Repro',
Expand Down
148 changes: 148 additions & 0 deletions example/src/gh-issues/Issue1096KeyboardShrinkRepro.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/**
* Repro for #1096: SwiftUI keyboard avoidance must not shrink PagerView pages.
*
* The pager sits in a bottom sheet that follows the keyboard with a transform,
* like react-native-keyboard-controller's KeyboardStickyView. React Native
* keeps the page at full height, but without the fix the page is framed to a
* keyboard-shrunk GeometryReader: the lower rows are clipped, and their native
* frames no longer match where React Native laid them out, so taps miss them.
*/
import React, { useEffect, useRef, useState } from 'react';
import {
Animated,
Keyboard,
Pressable,
StyleSheet,
Text,
TextInput,
View,
} from 'react-native';
import PagerView from 'react-native-pager-view';

const ROWS = ['Row 1', 'Row 2', 'Row 3', 'Row 4', 'Row 5'];

function useKeyboardLift() {
const lift = useRef(new Animated.Value(0)).current;

useEffect(() => {
const show = Keyboard.addListener('keyboardWillShow', (event) => {
Animated.timing(lift, {
toValue: -event.endCoordinates.height,
duration: event.duration,
useNativeDriver: true,
}).start();
});
const hide = Keyboard.addListener('keyboardWillHide', (event) => {
Animated.timing(lift, {
toValue: 0,
duration: event.duration,
useNativeDriver: true,
}).start();
});
return () => {
show.remove();
hide.remove();
};
}, [lift]);

return lift;
}

export function Issue1096KeyboardShrinkRepro() {
const lift = useKeyboardLift();
const [lastRowTaps, setLastRowTaps] = useState(0);

return (
<View style={styles.screen} testID="issue-1096-screen">
<Text style={styles.description}>
Focus the input. All five rows must stay visible above the input, and
tapping Row 5 must increment the counter.
</Text>
<Animated.View
style={[styles.sheet, { transform: [{ translateY: lift }] }]}
>
<PagerView style={styles.pager} initialPage={0} testID="issue-1096-pager">
<View key="rows" style={styles.page} collapsable={false}>
{ROWS.map((row, index) =>
index === ROWS.length - 1 ? (
<Pressable
key={row}
style={styles.row}
testID="issue-1096-last-row"
onPress={() => setLastRowTaps((count) => count + 1)}
>
<Text style={styles.rowText}>{row}</Text>
</Pressable>
) : (
<View key={row} style={styles.row}>
<Text style={styles.rowText}>{row}</Text>
</View>
)
)}
</View>
<View key="second" style={styles.page} collapsable={false}>
<Text style={styles.rowText}>Second page</Text>
</View>
</PagerView>
<Text testID="issue-1096-counter" style={styles.counter}>
{`last-row taps: ${lastRowTaps}`}
</Text>
<TextInput
testID="issue-1096-input"
placeholder="Focus me"
style={styles.input}
/>
</Animated.View>
</View>
);
}

const styles = StyleSheet.create({
screen: {
flex: 1,
justifyContent: 'flex-end',
},
description: {
position: 'absolute',
top: 24,
left: 24,
right: 24,
fontSize: 16,
lineHeight: 22,
textAlign: 'center',
},
sheet: {
backgroundColor: 'magenta',
paddingBottom: 34,
},
pager: {
height: 5 * 56,
},
page: {
flex: 1,
backgroundColor: '#b9f6ca',
},
row: {
height: 56,
justifyContent: 'center',
paddingHorizontal: 16,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: '#00000033',
},
rowText: {
color: '#111111',
fontSize: 18,
},
counter: {
padding: 8,
color: '#ffffff',
fontWeight: '600',
},
input: {
height: 44,
marginHorizontal: 16,
paddingHorizontal: 12,
borderRadius: 8,
backgroundColor: '#ffffff',
},
});
3 changes: 3 additions & 0 deletions ios/PagerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ struct PagerView: View {
}
}
}
// GeometryReader is the root since the vertical layout; without this it
// honours the keyboard region and pages get framed to the shrunk proxy.
.ignoresSafeArea()
.onAppear {
// Apply initial prop values that .onChange won't catch
// (.onChange only fires on changes, not on initial values)
Expand Down
Loading