feat(iOS, Stack, Tabs): improve nested container integration -> introduce Container & ContainerItem#4227
Conversation
31ceb4a to
b963ae2
Compare
b963ae2 to
3b297c3
Compare
| if (self.window != nil) { | ||
| if (_stackNavigationController.parentViewController == nil) { | ||
| BOOL mountResult = [RNSContainerHelpers addChildViewController:_stackNavigationController | ||
| toViewControllerManaging:self.reactSuperview | ||
| withContainerView:self]; | ||
| if (mountResult) { | ||
| [self setupViewConstraintsForController:_stackNavigationController]; | ||
| } | ||
| } | ||
|
|
||
| [_stackNavigationController attachToParentContainerItem]; | ||
| } else { | ||
| [_stackNavigationController detachFromParentContainerItem]; | ||
| } | ||
| } |
There was a problem hiding this comment.
I dislike the fact, that this logic is not self contained, i.e. stack navigation controller does not handle this internally, but instead the trigger for attach/detach actions comes from the host. IMO this is wrong and needs to be improved.
There was a problem hiding this comment.
Why can't we use didMoveToParentViewController?
There was a problem hiding this comment.
Claude sees some objections that I do not fully understand yet 😄
There was a problem hiding this comment.
Done. I think it's good enough. I have a single concern regarding whether the top-container is ever removed from the root view controller (seems that they are not).
There was a problem hiding this comment.
Pull request overview
Ports the Android container-nesting protocol to iOS by introducing RNSContainer / RNSContainerItem, enabling nested containers (stack/tabs) to propagate nesting info and resolve a “current content scroll view” across container boundaries (for special effects like scroll-to-top / scroll-edge behavior).
Changes:
- Introduce iOS container-nesting primitives (
RNSContainer,RNSContainerItem) plus composition helpers (RNSContainerItemSupport,RNSParentContainerItemRegistry). - Adopt these protocols in stack/tabs controllers and host component views; unify content scroll-view resolution via cached → nested-container → heuristic.
- Export scroll-view-marker integration-test scenarios via the component-integration-tests index.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| ios/tabs/screen/RNSTabsScreenViewController.mm | Makes the tabs screen VC a RNSContainerItem and delegates scroll-view lookup to RNSContainerItemSupport. |
| ios/tabs/screen/RNSTabsScreenViewController.h | Declares RNSTabsScreenViewController conformance to RNSContainerItem. |
| ios/tabs/screen/RNSTabsScreenComponentView.mm | Caches marker-registered scroll view for container-nesting resolution. |
| ios/tabs/screen/RNSTabsScreenComponentView.h | Exposes cachedContentScrollView to the owning controller. |
| ios/tabs/host/RNSTabsHostComponentView.mm | Hooks container attach/detach to window lifecycle and mounts the tab bar controller. |
| ios/tabs/host/RNSTabBarController.mm | Implements RNSContainer to resolve the current tab’s content scroll view and attach/detach to parent item. |
| ios/tabs/host/RNSTabBarController.h | Declares RNSContainer conformance and attach/detach API. |
| ios/helpers/container/RNSParentContainerItemRegistry.mm | New helper that registers/unregisters a container with the nearest parent RNSContainerItem. |
| ios/helpers/container/RNSParentContainerItemRegistry.h | Header for the parent-item registry composition holder. |
| ios/helpers/container/RNSContainerItemSupport.mm | New helper that owns the nested-container ref and shared scroll-view resolution order. |
| ios/helpers/container/RNSContainerItemSupport.h | Header documenting the shared resolution order (cached → nested container → heuristic). |
| ios/helpers/container/RNSContainerItem.h | New protocol for container items that can host a nested container and expose findContentScrollView. |
| ios/helpers/container/RNSContainer.h | New protocol for containers that can resolve the currently presented item’s content scroll view. |
| ios/gamma/stack/screen/RNSStackScreenController.mm | Makes stack screen controller a RNSContainerItem using RNSContainerItemSupport. |
| ios/gamma/stack/screen/RNSStackScreenController.h | Declares RNSContainerItem conformance. |
| ios/gamma/stack/screen/RNSStackScreenComponentView.mm | Implements scroll-view seeking to cache marker-registered scroll view. |
| ios/gamma/stack/screen/RNSStackScreenComponentView.h | Exposes cachedContentScrollView to the owning controller. |
| ios/gamma/stack/host/RNSStackNavigationController.mm | Implements RNSContainer and attaches/detaches to parent container item. |
| ios/gamma/stack/host/RNSStackNavigationController.h | Declares RNSContainer conformance and attach/detach API. |
| ios/gamma/stack/host/RNSStackHostComponentView.mm | Hooks container attach/detach to window lifecycle and mounts the nav controller. |
| apps/src/tests/component-integration-tests/scroll-view-marker/index.ts | Re-exports scroll-view-marker scenarios for consumption elsewhere. |
| apps/src/tests/component-integration-tests/index.tsx | Exports the scroll-view-marker scenario group at the integration-tests entry point. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
kligarski
left a comment
There was a problem hiding this comment.
What about SplitView? Should it also implement Container/ContainerItem and handle SVM integration?
| * nearest `RNSContainerItem`. We test via `respondsToSelector:` rather than `conformsToProtocol:` | ||
| * to match the lock-free idiom used elsewhere (e.g. `RNSScrollViewMarkerComponentView`). |
There was a problem hiding this comment.
nit: I'm not sure if we need to mention here that we're using respondsToSelector because we're using it in other files.
| if (self.window != nil) { | ||
| if (_stackNavigationController.parentViewController == nil) { | ||
| BOOL mountResult = [RNSContainerHelpers addChildViewController:_stackNavigationController | ||
| toViewControllerManaging:self.reactSuperview | ||
| withContainerView:self]; | ||
| if (mountResult) { | ||
| [self setupViewConstraintsForController:_stackNavigationController]; | ||
| } | ||
| } | ||
|
|
||
| [_stackNavigationController attachToParentContainerItem]; | ||
| } else { | ||
| [_stackNavigationController detachFromParentContainerItem]; | ||
| } | ||
| } |
There was a problem hiding this comment.
Why can't we use didMoveToParentViewController?
… behavior Wire the stack screen's descendant scroll view into the owning controller via `setContentScrollView:forEdge:`, mirroring the tabs screen. This enables native UINavigationBar scroll edge appearance and top-screen-tap scroll-to-top (iPad, latest iOS) for stack screens, closing the asymmetry with the tabs path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…errides Drop `willMove/didMoveToParentViewController:` overrides that only called super (identical to not overriding). Removes the orphaned `Overrides` pragma in RNSStackNavigationController and the dead `willMove` override in RNSTabBarController; the latter's `didMove` retains its real logic. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Align RNSParentContainerItemRegistry.attachContainer with the Android registry: unconditionally adopt the ancestor-walk result instead of early-returning when no parent item is found. A re-attach that finds no parent now clears the previously-remembered parent rather than leaving `_parentItem` (and the old item's nested-container ref) stale. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@kligarski AFAIK UISplitViewController does not bring any additional interactions, so it seems atm. that integration with that component is not required. |
…ntrollers The stack navigation controller and tab bar controller now register / unregister with the nearest parent RNSContainerItem from their own didMoveToParentViewController: lifecycle, instead of being driven externally by the host views' didMoveToWindow. This keeps the container-nesting logic internal to the container and reverts the host views to mount-only. attachToParentContainerItem / detachFromParentContainerItem are no longer part of the public controller API; their header declarations are removed.
Makes sense. If we manage to integrate Stack v5 with SplitView's per-column navigation controllers then everything should work fine. |
|
I'm gonna land this one after the CI passes. |
kligarski
left a comment
There was a problem hiding this comment.
I haven't checked the runtime after the changes but the code looks good.
Description
Port the container-nesting protocol from Android to iOS so nesting information
propagates between containers, and use it to resolve the content scroll view for
special effects (e.g. scroll-to-top) across a nesting boundary.
On iOS the protocol is expressed at the view-controller level, mirroring the
Android side. Shared state/logic lives in two composition holders:
RNSContainerItemSupport(item side) andRNSParentContainerItemRegistry(container side).
Closes https://github.com/software-mansion/react-native-screens-labs/issues/1573
Closes https://github.com/software-mansion/react-native-screens-labs/issues/1424
Changes
RNSContainerandRNSContainerItemprotocols and the twocomposition holders (
RNSContainerItemSupport,RNSParentContainerItemRegistry)mirroring the Android implementation.
RNSStackNavigationControllerandRNSTabBarControlleradoptRNSContainerand self-register / unregister with the nearest parent
RNSContainerItemfromtheir own
didMoveToParentViewController:lifecycle (i.e. as the container isadded to / removed from the view-controller hierarchy). The host views only
mount the controller.
RNSStackScreenControllerandRNSTabsScreenViewControlleradoptRNSContainerItem. Content-scroll-view resolution now followscached -> nested container -> descendant-chain heuristic.
(
parentViewController) upwards to the nearestRNSContainerItem.RNSTabsScreenViewController.resolveContentScrollViewdelegates tofindContentScrollView, removing the duplicated edge / heuristic lookup.setContentScrollView:forEdge:(native
UINavigationBarscroll edge appearance, top-screen-tap scroll-to-topon iPad), matching the tabs screen.
Before & after - visual documentation
There is no change in behaviour. It should work correctly right now, just not "by accident".
Test plan
Use the
test-stack-svm-tabs-special-effectsto confirm that indeed, special effects now do work in nested container scenario, even when the "standard UIKit heuristic" is broken.Checklist