Skip to content

[ScrollView] Add support for nested scrollable views. #8024

@alloy

Description

@alloy

For Discussion

In larger complex view hierarchies it may happen that a ScrollView-based component is nested inside another ScrollView.

Currently a nested ScrollView won’t receive scroll events, only the outer parent ScrollView will. In order to receive them, you would have to push scroll event callbacks down each child component of the parent ScrollView and all of those would have to send them down to their children, just in case a component somewhere down the tree needs to receive such events.

The developer and user of that component shouldn’t need to jump through hoops to have ScrollView callbacks work, but should be able to write isolated components that are unaware of the presence of other ScrollView instances in the view hierarchy and have React Native deal with the details of dispatching events to all components that require them.

Examples

Contrived

Multiple ListView instances as part of a larger scrolling view:

<ScrollView>
  <Header />
  <ListView />
  <ListView />
  <Footer />
</ScrollView>

Often the advice is to add views that are supposed to be above or below a ListView as a header or footer of the ListView, but that doesn’t solve the case where both ListView instance have to scroll in tandem as part of the parent ScrollView.

Real-world

My real world example is a ‘tab view’ component that displays a ScrollView-based component that needs to paginate.

You can run the example app that’s contained in the repo to see it for yourself, or in our production app that is currently in the store, but here’s a short demonstration of the ‘WORKS’ grid component being nested inside the top-level ScrollView and still paginating onEndReached as expected:

nested scrollview

Proof of Concept

For my app I wrote some 🐒-patches that do the following:

  1. When a RCTScrollView is added to a view hierarchy, it registers for a RCTScrollEvent notification.
  2. When a RCTScrollView receives a native scroll callback it first does its own JS scroll event dispatching and afterwards it posts a RCTScrollEvent notification for any child RCTScrollView instances to pick-up.
  3. The child RCTScrollView instance creates a new RCTScrollEvent object that offsets the scrollview’s contentOffset to reflect its location inside the parent RCTScrollView.
  4. The child RCTScrollView then dispatches the JS scroll event as normal and the component doesn’t know any better than that it has scrolled as normal.

Notes:

  • Please don’t judge my implementation as anything other than a POC of the high-level functionality.
  • I used NSNotificationCenter because there’s a potential 1-to-many relationship. I haven’t noticed any discernible lag in delivery, but there’s also no guarantee. A possible alternative would be to use a proxy object as the UIScrollView delegate that can dispatch events to multiple delegates (example).
  • While I have not yet used the callbacks for a ListView component in the current version of our app, I have tested and verified that the onEndReached callback gets triggered, thus I see no reason why the removeClippedSubviews optimisation wouldn’t work either. At least that’s my goal, as I will need this soon.

Discussion

  • I’m looking for suggestions as to how this should be implemented for it to be acceptable in React Native.
  • When I discussed this with @javache offline he suggested abstracting the scroll position/event part from ScrollView into a Scrollable container. That container could then create the ScrollView if not inside a parent ScrollView.
  • I want to spend the time to write the iOS implementation.
  • I currently do not target Android and thus don’t feel like I’m the right person to implement it for that platform at the moment, at least not from the get-go.

/cc @javache @nicklockwood

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions