Skip to content

feat(iOS, Stack, Tabs): improve nested container integration -> introduce Container & ContainerItem#4227

Merged
kkafar merged 10 commits into
mainfrom
@kkafar/stack-svm-tabs-integration-special-effects-2-ios-1
Jul 2, 2026
Merged

feat(iOS, Stack, Tabs): improve nested container integration -> introduce Container & ContainerItem#4227
kkafar merged 10 commits into
mainfrom
@kkafar/stack-svm-tabs-integration-special-effects-2-ios-1

Conversation

@kkafar

@kkafar kkafar commented Jun 29, 2026

Copy link
Copy Markdown
Member

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) and RNSParentContainerItemRegistry
(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

  • Introduce the RNSContainer and RNSContainerItem protocols and the two
    composition holders (RNSContainerItemSupport, RNSParentContainerItemRegistry)
    mirroring the Android implementation.
  • RNSStackNavigationController and RNSTabBarController adopt RNSContainer
    and self-register / unregister with the nearest parent RNSContainerItem from
    their own didMoveToParentViewController: lifecycle (i.e. as the container is
    added to / removed from the view-controller hierarchy). The host views only
    mount the controller.
  • RNSStackScreenController and RNSTabsScreenViewController adopt
    RNSContainerItem. Content-scroll-view resolution now follows
    cached -> nested container -> descendant-chain heuristic.
  • The parent walk follows the view-controller containment chain
    (parentViewController) upwards to the nearest RNSContainerItem.
  • RNSTabsScreenViewController.resolveContentScrollView delegates to
    findContentScrollView, removing the duplicated edge / heuristic lookup.
  • Wire the stack screen's content scroll view via setContentScrollView:forEdge:
    (native UINavigationBar scroll edge appearance, top-screen-tap scroll-to-top
    on 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-effects to confirm that indeed, special effects now do work in nested container scenario, even when the "standard UIKit heuristic" is broken.

Checklist

  • Included code example that can be used to test this change.
  • For visual changes, included screenshots / GIFs / recordings documenting the change.
  • For API changes, updated relevant public types.
  • Ensured that CI passes

Base automatically changed from @kkafar/stack-svm-tabs-integration-special-effects-1-android-1 to main June 29, 2026 15:07
@kkafar kkafar force-pushed the @kkafar/stack-svm-tabs-integration-special-effects-2-ios-1 branch from 31ceb4a to b963ae2 Compare June 29, 2026 15:26
@kkafar kkafar force-pushed the @kkafar/stack-svm-tabs-integration-special-effects-2-ios-1 branch from b963ae2 to 3b297c3 Compare June 30, 2026 10:10
Comment on lines 50 to 64
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];
}
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't we use didMoveToParentViewController?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude sees some objections that I do not fully understand yet 😄

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Comment thread ios/gamma/stack/host/RNSStackNavigationController.h Outdated
Comment thread ios/gamma/stack/host/RNSStackNavigationController.mm Outdated
@kkafar kkafar requested a review from Copilot June 30, 2026 10:29
@kkafar kkafar marked this pull request as ready for review June 30, 2026 10:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread ios/helpers/container/RNSParentContainerItemRegistry.mm
Comment thread ios/tabs/host/RNSTabsHostComponentView.mm Outdated
Comment thread ios/gamma/stack/host/RNSStackHostComponentView.mm Outdated
Comment thread ios/gamma/stack/host/RNSStackNavigationController.mm

@kligarski kligarski left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about SplitView? Should it also implement Container/ContainerItem and handle SVM integration?

Comment on lines +32 to +33
* nearest `RNSContainerItem`. We test via `respondsToSelector:` rather than `conformsToProtocol:`
* to match the lock-free idiom used elsewhere (e.g. `RNSScrollViewMarkerComponentView`).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'm not sure if we need to mention here that we're using respondsToSelector because we're using it in other files.

Comment thread ios/tabs/screen/RNSTabsScreenViewController.mm Outdated
Comment on lines 50 to 64
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];
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't we use didMoveToParentViewController?

kkafar and others added 3 commits July 2, 2026 11:27
… 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>
@kkafar

kkafar commented Jul 2, 2026

Copy link
Copy Markdown
Member Author

@kligarski AFAIK UISplitViewController does not bring any additional interactions, so it seems atm. that integration with that component is not required.

kkafar added 2 commits July 2, 2026 13:44
…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.
@kligarski

Copy link
Copy Markdown
Contributor

@kligarski AFAIK UISplitViewController does not bring any additional interactions, so it seems atm. that integration with that component is not required.

Makes sense. If we manage to integrate Stack v5 with SplitView's per-column navigation controllers then everything should work fine.

@kkafar

kkafar commented Jul 2, 2026

Copy link
Copy Markdown
Member Author

I'm gonna land this one after the CI passes.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.

@kligarski kligarski left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't checked the runtime after the changes but the code looks good.

@kkafar kkafar merged commit 7e1ce75 into main Jul 2, 2026
7 checks passed
@kkafar kkafar deleted the @kkafar/stack-svm-tabs-integration-special-effects-2-ios-1 branch July 2, 2026 14:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants