From 7c3ce089a06a174271bfce8bf886ef24bf357b22 Mon Sep 17 00:00:00 2001
From: troZee <12766071+troZee@users.noreply.github.com>
Date: Sat, 15 Aug 2026 09:40:49 +0200
Subject: [PATCH 1/9] fix(#1083): animated setPage lands on wrong index
---
.gitignore | 1 +
...issue_1083_modal_set_page_repro_setup.yaml | 19 ++++
.../issue_1083_modal_set_page_repro.yaml | 44 +++++++++
example/src/App.tsx | 19 +++-
example/src/ModalSetPageExample.tsx | 99 +++++++++++++++++++
ios/PagerViewProvider.swift | 64 ++++++++++++
6 files changed, 245 insertions(+), 1 deletion(-)
create mode 100644 .maestro/setup/issue_1083_modal_set_page_repro_setup.yaml
create mode 100644 .maestro/tests/issue_1083_modal_set_page_repro.yaml
create mode 100644 example/src/ModalSetPageExample.tsx
diff --git a/.gitignore b/.gitignore
index c979e30d..c6cae8bc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -82,3 +82,4 @@ example/ios/PagerViewExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
!.yarn/releases
!.yarn/sdks
!.yarn/versions
+.agent-device
diff --git a/.maestro/setup/issue_1083_modal_set_page_repro_setup.yaml b/.maestro/setup/issue_1083_modal_set_page_repro_setup.yaml
new file mode 100644
index 00000000..b9f1403a
--- /dev/null
+++ b/.maestro/setup/issue_1083_modal_set_page_repro_setup.yaml
@@ -0,0 +1,19 @@
+appId: ${APP_ID}
+---
+- launchApp
+
+- scrollUntilVisible:
+ element:
+ id: 'issue-1083-modal-set-page-repro'
+ direction: DOWN
+
+- tapOn:
+ id: 'issue-1083-modal-set-page-repro'
+
+- extendedWaitUntil:
+ visible:
+ id: 'issue-1083-requested-page'
+ timeout: 10000
+
+- assertVisible: 'Last requested page: 0'
+- assertVisible: 'Page 0'
diff --git a/.maestro/tests/issue_1083_modal_set_page_repro.yaml b/.maestro/tests/issue_1083_modal_set_page_repro.yaml
new file mode 100644
index 00000000..a225e381
--- /dev/null
+++ b/.maestro/tests/issue_1083_modal_set_page_repro.yaml
@@ -0,0 +1,44 @@
+appId: com.pagerviewexample
+tags:
+ - ios
+ - regression
+---
+- runFlow: ../setup/issue_1083_modal_set_page_repro_setup.yaml
+
+# This deliberately invokes setPage while the pager is obscured by a native
+# stack modal. Before the fix, React state became 0 while SwiftUI showed Page 1.
+- tapOn: 'Open Modal'
+
+- extendedWaitUntil:
+ visible: 'Modal screen'
+ timeout: 10000
+
+- tapOn: 'Submit & advance pager'
+
+- extendedWaitUntil:
+ visible:
+ id: 'issue-1083-requested-page'
+ timeout: 10000
+
+- assertVisible: 'Last requested page: 1'
+- assertVisible: 'Page 1'
+
+- tapOn: 'Advance pager directly'
+- assertVisible: 'Last requested page: 2'
+- assertVisible: 'Page 2'
+
+- tapOn: 'Open Modal'
+
+- extendedWaitUntil:
+ visible: 'Modal screen'
+ timeout: 10000
+
+- tapOn: 'Submit & advance pager'
+
+- extendedWaitUntil:
+ visible:
+ id: 'issue-1083-requested-page'
+ timeout: 10000
+
+- assertVisible: 'Last requested page: 0'
+- assertVisible: 'Page 0'
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 87920525..dad78458 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -36,6 +36,10 @@ import { PagerHookExample } from './PagerHookExample';
import { NestedHorizontalScrollViewExample } from './NestedHorizontalScrollViewExample';
import { Issue1098NestedPagerRepro } from './Issue1098NestedPagerRepro';
import { Issue1099SafeAreaRepro } from './Issue1099SafeAreaRepro';
+import {
+ ModalSetPageHomeScreen,
+ ModalSetPageModalScreen,
+} from './ModalSetPageExample';
function BasicPagerViewExampleScreen() {
return ;
@@ -108,6 +112,11 @@ const additionalExamples: Example[] = [
];
const ghIssues: Example[] = [
+ {
+ component: ModalSetPageHomeScreen,
+ name: 'Issue #1083 Modal SetPage Repro',
+ testID: 'issue-1083-modal-set-page-repro',
+ },
{
component: Issue1098NestedPagerRepro,
name: 'Issue #1098 Nested Pager Repro',
@@ -175,7 +184,7 @@ function App() {
{ghIssues.map((example) => (
{
//@ts-ignore
@@ -249,6 +258,14 @@ export function Navigation() {
component={example.component}
/>
))}
+
diff --git a/example/src/ModalSetPageExample.tsx b/example/src/ModalSetPageExample.tsx
new file mode 100644
index 00000000..4b16f871
--- /dev/null
+++ b/example/src/ModalSetPageExample.tsx
@@ -0,0 +1,99 @@
+/**
+ * Repro for #1083: setPage lands on the wrong index when called while the
+ * pager is obscured by a presented native-stack modal on iOS.
+ */
+import * as React from 'react';
+import { Button, SafeAreaView, StyleSheet, Text, View } from 'react-native';
+import {
+ useNavigation,
+ useRoute,
+ type RouteProp,
+} from '@react-navigation/native';
+import { type NativeStackNavigationProp } from '@react-navigation/native-stack';
+import PagerView from 'react-native-pager-view';
+
+export type ModalSetPageParamList = {
+ 'Issue #1083 Modal SetPage Repro': undefined;
+ 'ModalSetPageModal': { onSubmit: () => void };
+};
+
+const PAGES = ['Page 0', 'Page 1', 'Page 2'];
+const PAGE_COLORS = ['#fde2e2', '#e2fde6', '#e2e8fd'];
+
+export function ModalSetPageHomeScreen() {
+ const pagerRef = React.useRef(null);
+ const [currentPage, setCurrentPage] = React.useState(0);
+ const navigation =
+ useNavigation<
+ NativeStackNavigationProp<
+ ModalSetPageParamList,
+ 'Issue #1083 Modal SetPage Repro'
+ >
+ >();
+
+ const advancePager = React.useCallback(() => {
+ const next = (currentPage + 1) % PAGES.length;
+ console.log('[setPage]', next);
+ pagerRef.current?.setPage(next);
+ setCurrentPage(next);
+ }, [currentPage]);
+
+ const openModal = React.useCallback(() => {
+ navigation.navigate('ModalSetPageModal', { onSubmit: advancePager });
+ }, [advancePager, navigation]);
+
+ return (
+
+
+
+ Last requested page: {currentPage}
+
+
+
+
+
+ {PAGES.map((label, i) => (
+
+ {label}
+
+ ))}
+
+
+ );
+}
+
+export function ModalSetPageModalScreen() {
+ const navigation =
+ useNavigation<
+ NativeStackNavigationProp
+ >();
+ const route =
+ useRoute>();
+
+ const submit = React.useCallback(() => {
+ // Intentionally synchronous: update the hidden pager, then dismiss.
+ route.params.onSubmit();
+ navigation.popTo('Issue #1083 Modal SetPage Repro');
+ }, [navigation, route]);
+
+ return (
+
+
+ Modal screen
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ flex: { flex: 1 },
+ controls: { padding: 16, gap: 8 },
+ label: { fontSize: 14, color: '#444' },
+ page: { flex: 1, alignItems: 'center', justifyContent: 'center' },
+ pageLabel: { fontSize: 32, fontWeight: '600' },
+});
diff --git a/ios/PagerViewProvider.swift b/ios/PagerViewProvider.swift
index 644a0575..dbb6fef5 100644
--- a/ios/PagerViewProvider.swift
+++ b/ios/PagerViewProvider.swift
@@ -118,6 +118,36 @@ import UIKit
}
@objc public func goTo(index: Int, animated: Bool) {
+ if animated && hasPresentedViewController() {
+ // A native-stack modal can begin its dismissal in the same JavaScript
+ // callback as `setPage`. Starting a SwiftUI TabView animation in that
+ // transaction makes TabView apply the selection twice, landing one page
+ // too far. Let UIKit register the dismissal first, then perform the
+ // pager animation after the transition has completed.
+ // Native-stack begins its transition on a following run-loop pass.
+ // Waiting briefly lets its transition coordinator become observable.
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
+ self?.setPageWhenPresentationTransitionCompletes(index: index)
+ }
+ } else {
+ setPage(index: index, animated: animated)
+ }
+ }
+
+ private func setPageWhenPresentationTransitionCompletes(index: Int) {
+ guard let transitionCoordinator = activeTransitionCoordinator() else {
+ setPage(index: index, animated: true)
+ return
+ }
+
+ transitionCoordinator.animate(alongsideTransition: nil) { [weak self] _ in
+ DispatchQueue.main.async {
+ self?.setPage(index: index, animated: true)
+ }
+ }
+ }
+
+ private func setPage(index: Int, animated: Bool) {
if animated {
withAnimation {
props.currentPage = index
@@ -127,6 +157,40 @@ import UIKit
}
}
+ /// Finds the native-stack transition that may be dismissing a modal over
+ /// this pager. The coordinator becomes available only after the imperative
+ /// command has returned to the main run loop.
+ private func activeTransitionCoordinator() -> UIViewControllerTransitionCoordinator? {
+ var viewController = reactViewController()
+ while let current = viewController {
+ if let transitionCoordinator = current.transitionCoordinator {
+ return transitionCoordinator
+ }
+ viewController = current.parent
+ }
+
+ var rootViewController = window?.rootViewController
+ while let current = rootViewController {
+ if let transitionCoordinator = current.transitionCoordinator {
+ return transitionCoordinator
+ }
+ rootViewController = current.presentedViewController
+ }
+
+ return nil
+ }
+
+ private func hasPresentedViewController() -> Bool {
+ var viewController = window?.rootViewController
+ while let current = viewController {
+ if current.presentedViewController != nil {
+ return true
+ }
+ viewController = current.parent
+ }
+ return false
+ }
+
private func setupView() {
if self.hostingController != nil {
syncParentViewController()
From 5ca4794f83a302bd05679b325bb2b038fddd1183 Mon Sep 17 00:00:00 2001
From: troZee <12766071+troZee@users.noreply.github.com>
Date: Sat, 15 Aug 2026 09:52:05 +0200
Subject: [PATCH 2/9] refactor
---
example/src/App.tsx | 13 ++++++-------
.../Issue1083ModalSetPageExample.tsx} | 2 +-
.../{ => gh-issues}/Issue1098NestedPagerRepro.tsx | 0
.../src/{ => gh-issues}/Issue1099SafeAreaRepro.tsx | 2 +-
4 files changed, 8 insertions(+), 9 deletions(-)
rename example/src/{ModalSetPageExample.tsx => gh-issues/Issue1083ModalSetPageExample.tsx} (98%)
rename example/src/{ => gh-issues}/Issue1098NestedPagerRepro.tsx (100%)
rename example/src/{ => gh-issues}/Issue1099SafeAreaRepro.tsx (98%)
diff --git a/example/src/App.tsx b/example/src/App.tsx
index dad78458..88b31628 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -34,12 +34,12 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { PagerHookExample } from './PagerHookExample';
import { NestedHorizontalScrollViewExample } from './NestedHorizontalScrollViewExample';
-import { Issue1098NestedPagerRepro } from './Issue1098NestedPagerRepro';
-import { Issue1099SafeAreaRepro } from './Issue1099SafeAreaRepro';
+import { Issue1098NestedPagerRepro } from './gh-issues/Issue1098NestedPagerRepro';
+import { Issue1099SafeAreaRepro } from './gh-issues/Issue1099SafeAreaRepro';
import {
- ModalSetPageHomeScreen,
+ Issue1083ModalSetPageExample,
ModalSetPageModalScreen,
-} from './ModalSetPageExample';
+} from './gh-issues/Issue1083ModalSetPageExample';
function BasicPagerViewExampleScreen() {
return ;
@@ -113,9 +113,8 @@ const additionalExamples: Example[] = [
const ghIssues: Example[] = [
{
- component: ModalSetPageHomeScreen,
+ component: Issue1083ModalSetPageExample,
name: 'Issue #1083 Modal SetPage Repro',
- testID: 'issue-1083-modal-set-page-repro',
},
{
component: Issue1098NestedPagerRepro,
@@ -184,7 +183,7 @@ function App() {
{ghIssues.map((example) => (
{
//@ts-ignore
diff --git a/example/src/ModalSetPageExample.tsx b/example/src/gh-issues/Issue1083ModalSetPageExample.tsx
similarity index 98%
rename from example/src/ModalSetPageExample.tsx
rename to example/src/gh-issues/Issue1083ModalSetPageExample.tsx
index 4b16f871..0272f2fc 100644
--- a/example/src/ModalSetPageExample.tsx
+++ b/example/src/gh-issues/Issue1083ModalSetPageExample.tsx
@@ -20,7 +20,7 @@ export type ModalSetPageParamList = {
const PAGES = ['Page 0', 'Page 1', 'Page 2'];
const PAGE_COLORS = ['#fde2e2', '#e2fde6', '#e2e8fd'];
-export function ModalSetPageHomeScreen() {
+export function Issue1083ModalSetPageExample() {
const pagerRef = React.useRef(null);
const [currentPage, setCurrentPage] = React.useState(0);
const navigation =
diff --git a/example/src/Issue1098NestedPagerRepro.tsx b/example/src/gh-issues/Issue1098NestedPagerRepro.tsx
similarity index 100%
rename from example/src/Issue1098NestedPagerRepro.tsx
rename to example/src/gh-issues/Issue1098NestedPagerRepro.tsx
diff --git a/example/src/Issue1099SafeAreaRepro.tsx b/example/src/gh-issues/Issue1099SafeAreaRepro.tsx
similarity index 98%
rename from example/src/Issue1099SafeAreaRepro.tsx
rename to example/src/gh-issues/Issue1099SafeAreaRepro.tsx
index 7af682fa..d86d5d7e 100644
--- a/example/src/Issue1099SafeAreaRepro.tsx
+++ b/example/src/gh-issues/Issue1099SafeAreaRepro.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import { ScrollView, StyleSheet, Text, View } from 'react-native';
import PagerView from 'react-native-pager-view';
-import { IMAGE_URIS } from './utils';
+import { IMAGE_URIS } from '../utils';
const CARD_HEIGHT = 500;
From 42a351606fe3ddcb836fd7446a8cfdf364d348b2 Mon Sep 17 00:00:00 2001
From: troZee <12766071+troZee@users.noreply.github.com>
Date: Sat, 15 Aug 2026 09:54:12 +0200
Subject: [PATCH 3/9] fix maestro test
---
.maestro/setup/issue_1083_modal_set_page_repro_setup.yaml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.maestro/setup/issue_1083_modal_set_page_repro_setup.yaml b/.maestro/setup/issue_1083_modal_set_page_repro_setup.yaml
index b9f1403a..c493b4bd 100644
--- a/.maestro/setup/issue_1083_modal_set_page_repro_setup.yaml
+++ b/.maestro/setup/issue_1083_modal_set_page_repro_setup.yaml
@@ -4,11 +4,11 @@ appId: ${APP_ID}
- scrollUntilVisible:
element:
- id: 'issue-1083-modal-set-page-repro'
+ id: 'Issue #1083 Modal SetPage Repro'
direction: DOWN
- tapOn:
- id: 'issue-1083-modal-set-page-repro'
+ id: 'Issue #1083 Modal SetPage Repro'
- extendedWaitUntil:
visible:
From 6888a4b0d6b272bca41562426420b58384e57e4b Mon Sep 17 00:00:00 2001
From: troZee <12766071+troZee@users.noreply.github.com>
Date: Sat, 15 Aug 2026 10:04:36 +0200
Subject: [PATCH 4/9] tests cleanup
---
.maestro/{tests => issues}/issue_1083_modal_set_page_repro.yaml | 0
.maestro/{tests => issues}/issue_1098_nested_pager_repro.yaml | 0
scripts/run-maestro-tests.sh | 1 +
3 files changed, 1 insertion(+)
rename .maestro/{tests => issues}/issue_1083_modal_set_page_repro.yaml (100%)
rename .maestro/{tests => issues}/issue_1098_nested_pager_repro.yaml (100%)
diff --git a/.maestro/tests/issue_1083_modal_set_page_repro.yaml b/.maestro/issues/issue_1083_modal_set_page_repro.yaml
similarity index 100%
rename from .maestro/tests/issue_1083_modal_set_page_repro.yaml
rename to .maestro/issues/issue_1083_modal_set_page_repro.yaml
diff --git a/.maestro/tests/issue_1098_nested_pager_repro.yaml b/.maestro/issues/issue_1098_nested_pager_repro.yaml
similarity index 100%
rename from .maestro/tests/issue_1098_nested_pager_repro.yaml
rename to .maestro/issues/issue_1098_nested_pager_repro.yaml
diff --git a/scripts/run-maestro-tests.sh b/scripts/run-maestro-tests.sh
index 160c2236..6af663d7 100644
--- a/scripts/run-maestro-tests.sh
+++ b/scripts/run-maestro-tests.sh
@@ -99,6 +99,7 @@ fi
shopt -s nullglob
allTestFiles=(
.maestro/tests/*.yaml
+ .maestro/issues/*.yaml
.maestro/"$PLATFORM"-only/*.yaml
)
From b6a825464be0e63b9d427310d753361ed63d9e2f Mon Sep 17 00:00:00 2001
From: troZee <12766071+troZee@users.noreply.github.com>
Date: Tue, 1 Sep 2026 19:14:52 +0200
Subject: [PATCH 5/9] apply feedback
---
.../issue_1083_modal_set_page_repro.yaml | 42 ++++++++++++-------
...issue_1083_modal_set_page_repro_setup.yaml | 5 ++-
example/ios/Podfile.lock | 4 +-
example/package.json | 10 ++---
.../Issue1083ModalSetPageExample.tsx | 18 ++++++--
ios/PagerViewProvider.swift | 39 +++++++++++++----
6 files changed, 81 insertions(+), 37 deletions(-)
diff --git a/.maestro/issues/issue_1083_modal_set_page_repro.yaml b/.maestro/issues/issue_1083_modal_set_page_repro.yaml
index a225e381..314ca461 100644
--- a/.maestro/issues/issue_1083_modal_set_page_repro.yaml
+++ b/.maestro/issues/issue_1083_modal_set_page_repro.yaml
@@ -7,38 +7,52 @@ tags:
# This deliberately invokes setPage while the pager is obscured by a native
# stack modal. Before the fix, React state became 0 while SwiftUI showed Page 1.
-- tapOn: 'Open Modal'
+- tapOn:
+ id: 'issue-1083-open-modal'
- extendedWaitUntil:
visible: 'Modal screen'
timeout: 10000
-- tapOn: 'Submit & advance pager'
+- tapOn:
+ id: 'issue-1083-submit'
+
+- extendedWaitUntil:
+ visible: 'Last requested page: 1'
+ timeout: 10000
- extendedWaitUntil:
visible:
- id: 'issue-1083-requested-page'
+ id: 'issue-1083-page-1'
timeout: 10000
-- assertVisible: 'Last requested page: 1'
-- assertVisible: 'Page 1'
+- tapOn:
+ id: 'issue-1083-advance-directly'
-- tapOn: 'Advance pager directly'
-- assertVisible: 'Last requested page: 2'
-- assertVisible: 'Page 2'
+- extendedWaitUntil:
+ visible: 'Last requested page: 2'
+ timeout: 10000
-- tapOn: 'Open Modal'
+- extendedWaitUntil:
+ visible:
+ id: 'issue-1083-page-2'
+ timeout: 10000
+
+- tapOn:
+ id: 'issue-1083-open-modal'
- extendedWaitUntil:
visible: 'Modal screen'
timeout: 10000
-- tapOn: 'Submit & advance pager'
+- tapOn:
+ id: 'issue-1083-submit'
- extendedWaitUntil:
- visible:
- id: 'issue-1083-requested-page'
+ visible: 'Last requested page: 0'
timeout: 10000
-- assertVisible: 'Last requested page: 0'
-- assertVisible: 'Page 0'
+- extendedWaitUntil:
+ visible:
+ id: 'issue-1083-page-0'
+ timeout: 10000
diff --git a/.maestro/setup/issue_1083_modal_set_page_repro_setup.yaml b/.maestro/setup/issue_1083_modal_set_page_repro_setup.yaml
index c493b4bd..fb1afa10 100644
--- a/.maestro/setup/issue_1083_modal_set_page_repro_setup.yaml
+++ b/.maestro/setup/issue_1083_modal_set_page_repro_setup.yaml
@@ -1,4 +1,4 @@
-appId: ${APP_ID}
+appId: com.pagerviewexample
---
- launchApp
@@ -16,4 +16,5 @@ appId: ${APP_ID}
timeout: 10000
- assertVisible: 'Last requested page: 0'
-- assertVisible: 'Page 0'
+- assertVisible:
+ id: 'issue-1083-page-0'
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
index ca19358f..ef7d2cf5 100644
--- a/example/ios/Podfile.lock
+++ b/example/ios/Podfile.lock
@@ -1402,7 +1402,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- - react-native-pager-view (9.0.0):
+ - react-native-pager-view (9.0.2):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@@ -2496,7 +2496,7 @@ SPEC CHECKSUMS:
React-Mapbuffer: 1aa9126122d4247ffc24bf9d28d50ce923499a71
React-microtasksnativemodule: d86581169e9bb5bb6f5fc3c5052f890016c1bf21
React-mutationobservernativemodule: 9a0c4e866f1ef2a57acebc902ebacbdf469ae741
- react-native-pager-view: 91a6d37f552b9c47445cc94792ad75cfa08487b2
+ react-native-pager-view: 67ea8575ea8f5b1f986cfa96f1e127a698a44431
react-native-safe-area-context: c1eb308f4b36372a4de4b3bdaa8ed695ec3dd461
React-NativeModulesApple: cc6ec4767844d610e92cc358bd3ea34937438d56
React-networking: a8ce15641ed7775d5b54a9d0d32defc367c2216e
diff --git a/example/package.json b/example/package.json
index 5afe7406..5a4432a3 100644
--- a/example/package.json
+++ b/example/package.json
@@ -7,13 +7,9 @@
"pods": "bundle install && cd ios && bundle exec pod install && cd ..",
"test": "jest",
"lint": "eslint .",
- "mkdist": "node -e \"require('node:fs').mkdirSync('dist', { recursive: true, mode: 0o755 })\"",
- "android": "react-native run-android --appId com.pagerviewexample --list-devices",
- "ios": "react-native run-ios --list-devices",
- "visionos": "react-native run-visionos",
- "build:android": "bun mkdist && react-native bundle --entry-file index.js --platform android --dev true --bundle-output dist/main.android.jsbundle --assets-dest dist/res",
- "build:ios": "bun mkdist && react-native bundle --entry-file index.js --platform ios --dev false --bundle-output dist/main.ios.jsbundle --assets-dest dist",
- "build:visionos": "bun mkdist && react-native bundle --entry-file index.js --platform ios --dev true --bundle-output dist/main.visionos.jsbundle --assets-dest dist"
+ "android": "react-native run-android --appId com.pagerviewexample",
+ "ios": "react-native run-ios",
+ "visionos": "react-native run-visionos"
},
"dependencies": {
"@callstack/react-native-visionos": "^0.79.6",
diff --git a/example/src/gh-issues/Issue1083ModalSetPageExample.tsx b/example/src/gh-issues/Issue1083ModalSetPageExample.tsx
index 0272f2fc..67cbe8e2 100644
--- a/example/src/gh-issues/Issue1083ModalSetPageExample.tsx
+++ b/example/src/gh-issues/Issue1083ModalSetPageExample.tsx
@@ -48,8 +48,16 @@ export function Issue1083ModalSetPageExample() {
Last requested page: {currentPage}
-
-
+
+
{PAGES.map((label, i) => (
@@ -84,7 +92,11 @@ export function ModalSetPageModalScreen() {
Modal screen
-
+
);
diff --git a/ios/PagerViewProvider.swift b/ios/PagerViewProvider.swift
index dbb6fef5..aa9c374f 100644
--- a/ios/PagerViewProvider.swift
+++ b/ios/PagerViewProvider.swift
@@ -124,27 +124,48 @@ import UIKit
// transaction makes TabView apply the selection twice, landing one page
// too far. Let UIKit register the dismissal first, then perform the
// pager animation after the transition has completed.
- // Native-stack begins its transition on a following run-loop pass.
- // Waiting briefly lets its transition coordinator become observable.
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
- self?.setPageWhenPresentationTransitionCompletes(index: index)
- }
+ // Native-stack may need several run-loop passes before its transition
+ // coordinator becomes observable. Check briefly for that coordinator
+ // instead of starting the SwiftUI animation on the very next pass.
+ setPageWhenPresentationTransitionCompletes(
+ index: index,
+ attemptsRemaining: 10
+ )
} else {
setPage(index: index, animated: animated)
}
}
- private func setPageWhenPresentationTransitionCompletes(index: Int) {
+ private func setPageWhenPresentationTransitionCompletes(
+ index: Int,
+ attemptsRemaining: Int
+ ) {
guard let transitionCoordinator = activeTransitionCoordinator() else {
- setPage(index: index, animated: true)
+ guard attemptsRemaining > 0 && hasPresentedViewController() else {
+ setPage(index: index, animated: true)
+ return
+ }
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { [weak self] in
+ self?.setPageWhenPresentationTransitionCompletes(
+ index: index,
+ attemptsRemaining: attemptsRemaining - 1
+ )
+ }
return
}
- transitionCoordinator.animate(alongsideTransition: nil) { [weak self] _ in
+ let didSchedulePageChange = transitionCoordinator.animate(
+ alongsideTransition: nil
+ ) { [weak self] _ in
DispatchQueue.main.async {
self?.setPage(index: index, animated: true)
}
}
+
+ if !didSchedulePageChange {
+ setPage(index: index, animated: true)
+ }
}
private func setPage(index: Int, animated: Bool) {
@@ -181,7 +202,7 @@ import UIKit
}
private func hasPresentedViewController() -> Bool {
- var viewController = window?.rootViewController
+ var viewController = reactViewController()
while let current = viewController {
if current.presentedViewController != nil {
return true
From 574a26a4820eaea518692ae678736610837d137d Mon Sep 17 00:00:00 2001
From: troZee <12766071+troZee@users.noreply.github.com>
Date: Tue, 1 Sep 2026 19:42:13 +0200
Subject: [PATCH 6/9] fix test after merge
---
.maestro/tests/material_top_bar_example.yaml | 3 ++-
example/src/MaterialTopTabExample.tsx | 17 ++++++++++++++++-
2 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/.maestro/tests/material_top_bar_example.yaml b/.maestro/tests/material_top_bar_example.yaml
index 2f441a6a..4813f4c4 100644
--- a/.maestro/tests/material_top_bar_example.yaml
+++ b/.maestro/tests/material_top_bar_example.yaml
@@ -34,7 +34,8 @@ appId: com.pagerviewexample
id: 'material-top-bar-detail-screen'
timeout: 5000
-- pressKey: Back
+- tapOn:
+ id: 'material-top-bar-back-button'
- extendedWaitUntil:
visible:
diff --git a/example/src/MaterialTopTabExample.tsx b/example/src/MaterialTopTabExample.tsx
index d6cb7b25..79715d7c 100644
--- a/example/src/MaterialTopTabExample.tsx
+++ b/example/src/MaterialTopTabExample.tsx
@@ -95,6 +95,17 @@ const DetailScreen = () => (
);
+const detailScreenOptions = ({ navigation }: any) => ({
+ headerBackVisible: false,
+ headerLeft: () => (
+
+ ),
+});
+
export function MaterialTopBarExample() {
const { Screen, Navigator } = createNativeStackNavigator();
const [isSignedIn, setIsSignedIn] = useState(false);
@@ -118,7 +129,11 @@ export function MaterialTopBarExample() {
)}
)}
-
+
);
}
From d911fbe6b23303808eff11ce2f4ce520dc4e178a Mon Sep 17 00:00:00 2001
From: troZee <12766071+troZee@users.noreply.github.com>
Date: Tue, 1 Sep 2026 20:15:40 +0200
Subject: [PATCH 7/9] fix podfile
---
example/ios/Podfile.lock | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
index ef7d2cf5..285d3356 100644
--- a/example/ios/Podfile.lock
+++ b/example/ios/Podfile.lock
@@ -1402,7 +1402,7 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- - react-native-pager-view (9.0.2):
+ - react-native-pager-view (9.0.3):
- hermes-engine
- RCTRequired
- RCTTypeSafety
@@ -2496,7 +2496,7 @@ SPEC CHECKSUMS:
React-Mapbuffer: 1aa9126122d4247ffc24bf9d28d50ce923499a71
React-microtasksnativemodule: d86581169e9bb5bb6f5fc3c5052f890016c1bf21
React-mutationobservernativemodule: 9a0c4e866f1ef2a57acebc902ebacbdf469ae741
- react-native-pager-view: 67ea8575ea8f5b1f986cfa96f1e127a698a44431
+ react-native-pager-view: 27f9cb535d59648492018892b7e3bffe0b511807
react-native-safe-area-context: c1eb308f4b36372a4de4b3bdaa8ed695ec3dd461
React-NativeModulesApple: cc6ec4767844d610e92cc358bd3ea34937438d56
React-networking: a8ce15641ed7775d5b54a9d0d32defc367c2216e
From 7ea78bba7e2a2347e65ca2eb895e8be8cffa727b Mon Sep 17 00:00:00 2001
From: troZee <12766071+troZee@users.noreply.github.com>
Date: Tue, 1 Sep 2026 20:38:23 +0200
Subject: [PATCH 8/9] revert package.json
---
example/package.json | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/example/package.json b/example/package.json
index 5a4432a3..064bcd7c 100644
--- a/example/package.json
+++ b/example/package.json
@@ -7,9 +7,13 @@
"pods": "bundle install && cd ios && bundle exec pod install && cd ..",
"test": "jest",
"lint": "eslint .",
+ "mkdist": "node -e \"require('node:fs').mkdirSync('dist', { recursive: true, mode: 0o755 })\"",
"android": "react-native run-android --appId com.pagerviewexample",
"ios": "react-native run-ios",
- "visionos": "react-native run-visionos"
+ "visionos": "react-native run-visionos",
+ "build:android": "bun mkdist && react-native bundle --entry-file index.js --platform android --dev true --bundle-output dist/main.android.jsbundle --assets-dest dist/res",
+ "build:ios": "bun mkdist && react-native bundle --entry-file index.js --platform ios --dev false --bundle-output dist/main.ios.jsbundle --assets-dest dist",
+ "build:visionos": "bun mkdist && react-native bundle --entry-file index.js --platform ios --dev true --bundle-output dist/main.visionos.jsbundle --assets-dest dist"
},
"dependencies": {
"@callstack/react-native-visionos": "^0.79.6",
From f46e65b53cbd54d6ebeb008caf635904809950d4 Mon Sep 17 00:00:00 2001
From: troZee <12766071+troZee@users.noreply.github.com>
Date: Tue, 1 Sep 2026 20:42:03 +0200
Subject: [PATCH 9/9] ignore **/.xcode.env
---
.gitignore | 1 +
example/.gitignore | 1 +
example/ios/.xcode.env | 11 -----------
example/visionos/.xcode.env | 11 -----------
4 files changed, 2 insertions(+), 22 deletions(-)
delete mode 100644 example/ios/.xcode.env
delete mode 100644 example/visionos/.xcode.env
diff --git a/.gitignore b/.gitignore
index c6cae8bc..b9296349 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,6 +28,7 @@ DerivedData
*.ipa
*.xcuserstate
project.xcworkspace
+**/.xcode.env
**/.xcode.env.local
# Android/IJ
diff --git a/example/.gitignore b/example/.gitignore
index d0ece84a..fdb76b17 100644
--- a/example/.gitignore
+++ b/example/.gitignore
@@ -6,6 +6,7 @@
.gradle/
.idea/
.vs/
+**/.xcode.env
**/.xcode.env.local
Pods/
build/
diff --git a/example/ios/.xcode.env b/example/ios/.xcode.env
deleted file mode 100644
index 3d5782c7..00000000
--- a/example/ios/.xcode.env
+++ /dev/null
@@ -1,11 +0,0 @@
-# This `.xcode.env` file is versioned and is used to source the environment
-# used when running script phases inside Xcode.
-# To customize your local environment, you can create an `.xcode.env.local`
-# file that is not versioned.
-
-# NODE_BINARY variable contains the PATH to the node executable.
-#
-# Customize the NODE_BINARY variable here.
-# For example, to use nvm with brew, add the following line
-# . "$(brew --prefix nvm)/nvm.sh" --no-use
-export NODE_BINARY=$(command -v node)
diff --git a/example/visionos/.xcode.env b/example/visionos/.xcode.env
deleted file mode 100644
index 3d5782c7..00000000
--- a/example/visionos/.xcode.env
+++ /dev/null
@@ -1,11 +0,0 @@
-# This `.xcode.env` file is versioned and is used to source the environment
-# used when running script phases inside Xcode.
-# To customize your local environment, you can create an `.xcode.env.local`
-# file that is not versioned.
-
-# NODE_BINARY variable contains the PATH to the node executable.
-#
-# Customize the NODE_BINARY variable here.
-# For example, to use nvm with brew, add the following line
-# . "$(brew --prefix nvm)/nvm.sh" --no-use
-export NODE_BINARY=$(command -v node)