Please allow getting a ScrollView's contentOffset without having to listen to the onScroll event.
Listening to the onScroll event for this creates unnecessary overhead and it's not 100% reliable as the event is throttled.
You have to control the scrollthrottle or you can create a debounce function.
AFAIK I should set scrollthrottle to 16 for 100% accuracy, but I'm really not interested on having the callback invoked that frequently during user scroll (even with a debounce function) if what I need is really to get the contentOffset at some other time when the user is not scrolling?
Any ideas why offering to get the contentOffset is difficult or inappropriate?
you can try using onScrollAnimationEnd to get the value...i know there were some issues with it in previous versions. but, maybe it's fixed now.
Ok. So any ideas why offering to get the contentOffset is difficult or inappropriate?
I know it may not be a good practice to ask question in an issue. But it'll be helpful for me and others who end up looking for an answer here if we can have an answer before closing this. Thx!
There are only two ways to get the contentOffset -- to expose a method like getContentOffset(function(contentOffset) { ... }) which adds an extra frame or two of latency, or for the scroll view to internally subscribe to the onScroll event with the throttling set to 16ms. Without understanding your use case it's hard to say which is better.
Going to close this issue - if you're interested in writing a PR for it we can move discussion there, otherwise you can just track the contentOffset with the onScroll event as @ide suggested and it should be straightforward. A use case where this would be too cumbersome or awkward is welcome! Also keep in mind, you could just wrap the ScrollView component to implement this.
How can I get contentOffset when using onMomentumScrollEnd ?
For anyone else wondering what @ide means by
expose a method like getContentOffset(function(contentOffset) { ... })
Add the following method to React/Views/RCTScrollViewManager.m
RCT_EXPORT_METHOD(getContentOffset:(nonnull NSNumber *)reactTag
callback:(RCTResponseSenderBlock)callback)
{
[self.bridge.uiManager addUIBlock:
^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTScrollView *> *viewRegistry) {
RCTScrollView *view = viewRegistry[reactTag];
if (!view || ![view isKindOfClass:[RCTScrollView class]]) {
RCTLogError(@"Cannot find RCTScrollView with tag #%@", reactTag);
return;
}
CGPoint offset = view.scrollView.contentOffset;
callback(@[@{
@"x" : @(offset.x),
@"y" : @(offset.y)
}]);
}];
}
Then in your React component you can consume it like this,
import React, { Component } from 'react';
import { NativeModules, ScrollView } from 'react-native';
class MyComponentThatScrolls {
SomeMethod() {
const reactTag = this.refs.scrollView.getScrollableNode();
NativeModules.ScrollViewManager.getContentOffset(reactTag, (offset) => {
// do something with offset
});
}
render() {
return <ScrollView ref={'scrollView'} />
}
}
Note This is asynchronous as React will schedule a task to execute on the main thread which will then schedule another task to execute on the javascript thread.
This latency has been a huge problem for me.
FYI this react-native-invoke tutorial shows one way to create a getContentOffset method: https://medium.com/@talkol/invoke-any-native-api-directly-from-pure-javascript-in-react-native-1fb6afcdf57d#.df3j2w5ne
Re @brentvatne's request for a use-case, I have a list of items in my ScrollView, which are each links to a different screen. When the user presses one of them, I want to save the current ScrollView position so that when they come back, they're in the same spot.
I only need to get the position when they press one of them, so a native getContentOffset method would be very useful.
@dsernst @brentvatne yes, scroll restoration is exactly the use case I'm trying to achieve as well.
Also note: react-navigation keeps previous scenes in memory (which preserves scroll), but you may not be using react-navigation or have other reasons why the previous scene is removed from memory.
It seems an ordeal to have to use react-native-invoke to do such a common thing, especially if what you're making is a package for other people to use as well (then they too have to follow the setup directions for react-native-invoke).
@brentvatne can this be reopened? Scroll restoration is certainly not an uncommon use case and using onScroll for that seems a huge overhead. If there is no other workaround, isn't this something that should be addressed eventually. I understand it's low priority right now.
@MrLoh - I think the best way forward is for someone to open a pull request with code similar to what @daniel-nagy shared above for review and ideally along with an Android implementation. Not sure what value there is in opening this issue when there is already some code that we can use for a PR. Also, this isn't really a bug, it's a feature request.
need it or scrollTo callback
So, over the last week I've wasted a whole bunch of hours unsuccessfully trying to get to grips with things enough to implement this - unfortunately I just don't have the knowledge of Android or of React Native's internals required. I will post some thoughts, though:
onScroll is not fit for the task, as I describe at https://stackoverflow.com/a/47984187/1709587. Use cases like scroll restoration therefore cannot be correctly implemented in the general case as React Native currently stands.NativeModules.ScrollViewManager which doesn't even exist on Android. The method ought to go somewhere that exists for both platformsUIManager, which exists for both platforms. I dunno whether that's a clean place for it to go; I honestly find the existing JS API pretty confusing and don't understand why the methods on these manager classes aren't directly exposed on View/ScrollView.UIManagerModule, UIImplementation, UIViewOperationsQueue, and NativeViewHierarchyManager (like with measure()), and that the last of those would need to actually get the offset from a ReactScrollView or ReactHorizontalScrollView; I'm far from sure, though!getting on event onContentSizeChange
onContentSizeChange={(contentWidth, contentHeight)=>{
this.setState({contentWidth, contentHeight})
}}
I have a usecase for this feature: I am currently building a "scroll into view" api for RN. My initial API supposed we might only pass a scrollView ref to a provider (context) to get the thing working. But the unability to get y offset forces me to finally create a HOC to wrap an existing ScrollView comp, which was not exactly how I wanted to design my library and complicates a bit things.
Not a big deal, but still think including this in RN would be nice. (I want my lib to be Expo compatible, so don't want to ship native code)
I also guess it means that people using my lib won't be able to use in the same time native driver scroll animations (like parallax effets) as I must add myself a onScroll js listener
I've managed to cover tracking the _actual_ current scroll positions:
onScrollEndDrag={this.handleOnScrollEndDrag}
onMomentumScrollEnd={this.handleOnMomentumScrollEnd}
Not 100% sure of the technical reliability, but in practice, it's functioning properly in the apps I'm currently working on.
Most helpful comment
For anyone else wondering what @ide means by
Add the following method to
React/Views/RCTScrollViewManager.mThen in your React component you can consume it like this,
Note This is asynchronous as React will schedule a task to execute on the main thread which will then schedule another task to execute on the javascript thread.
This latency has been a huge problem for me.