From e4c8dab2aeae4c07d0f5f699a0061fefac5e99be Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Thu, 16 May 2019 18:27:16 -0400 Subject: [PATCH] feat(onScroll): Add `onScroll` callback for iOS & Android (#516) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add `onScroll` callback for iOS & Android This code was mostly extracted from https://github.com/react-native-community/react-native-webview/pull/202 I tried and tried to make it work with `Animated.event`'s `useNativeDriver`, but I was unsuccessful 😢 that'll have to be done later once I understand better how Animated's native stuff is hooked up. * fix crash for missing onScroll --- .../webview/RNCWebViewManager.java | 25 +++++++++++++++++++ ios/RNCWKWebView.m | 25 +++++++++++++++++++ ios/RNCWKWebViewManager.m | 1 + src/WebView.ios.tsx | 1 + src/WebViewTypes.ts | 7 ++++++ 5 files changed, 59 insertions(+) diff --git a/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java b/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java index 85ddad8..940da6c 100644 --- a/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java +++ b/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java @@ -31,6 +31,9 @@ import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.FrameLayout; +import com.facebook.react.views.scroll.ScrollEvent; +import com.facebook.react.views.scroll.ScrollEventType; +import com.facebook.react.views.scroll.OnScrollDispatchHelper; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.ReactContext; @@ -446,6 +449,7 @@ public class RNCWebViewManager extends SimpleViewManager { } export.put(TopLoadingProgressEvent.EVENT_NAME, MapBuilder.of("registrationName", "onLoadingProgress")); export.put(TopShouldStartLoadWithRequestEvent.EVENT_NAME, MapBuilder.of("registrationName", "onShouldStartLoadWithRequest")); + export.put(ScrollEventType.SCROLL.getJSEventName(), MapBuilder.of("registrationName", "onScroll")); return export; } @@ -780,6 +784,7 @@ public class RNCWebViewManager extends SimpleViewManager { protected @Nullable RNCWebViewClient mRNCWebViewClient; protected boolean sendContentSizeChangeEvents = false; + private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper(); /** * WebView must be created with an context of the current activity @@ -886,6 +891,26 @@ public class RNCWebViewManager extends SimpleViewManager { dispatchEvent(this, new TopMessageEvent(this.getId(), message)); } + protected void onScrollChanged(int x, int y, int oldX, int oldY) { + super.onScrollChanged(x, y, oldX, oldY); + + if (mOnScrollDispatchHelper.onScrollChanged(x, y)) { + ScrollEvent event = ScrollEvent.obtain( + this.getId(), + ScrollEventType.SCROLL, + x, + y, + mOnScrollDispatchHelper.getXFlingVelocity(), + mOnScrollDispatchHelper.getYFlingVelocity(), + this.computeHorizontalScrollRange(), + this.computeVerticalScrollRange(), + this.getWidth(), + this.getHeight()); + + dispatchEvent(this, event); + } + } + protected void cleanupCallbacksAndDestroy() { setWebViewClient(null); destroy(); diff --git a/ios/RNCWKWebView.m b/ios/RNCWKWebView.m index dc351a5..ea7863f 100644 --- a/ios/RNCWKWebView.m +++ b/ios/RNCWKWebView.m @@ -34,6 +34,7 @@ static NSURLCredential* clientAuthenticationCredential; @property (nonatomic, copy) RCTDirectEventBlock onLoadingProgress; @property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest; @property (nonatomic, copy) RCTDirectEventBlock onMessage; +@property (nonatomic, copy) RCTDirectEventBlock onScroll; @property (nonatomic, copy) WKWebView *webView; @end @@ -509,6 +510,30 @@ static NSURLCredential* clientAuthenticationCredential; if (!_scrollEnabled) { scrollView.bounds = _webView.bounds; } + else if (_onScroll != nil) { + NSDictionary *event = @{ + @"contentOffset": @{ + @"x": @(scrollView.contentOffset.x), + @"y": @(scrollView.contentOffset.y) + }, + @"contentInset": @{ + @"top": @(scrollView.contentInset.top), + @"left": @(scrollView.contentInset.left), + @"bottom": @(scrollView.contentInset.bottom), + @"right": @(scrollView.contentInset.right) + }, + @"contentSize": @{ + @"width": @(scrollView.contentSize.width), + @"height": @(scrollView.contentSize.height) + }, + @"layoutMeasurement": @{ + @"width": @(scrollView.frame.size.width), + @"height": @(scrollView.frame.size.height) + }, + @"zoomScale": @(scrollView.zoomScale ?: 1), + }; + _onScroll(event); + } } - (void)setDirectionalLockEnabled:(BOOL)directionalLockEnabled diff --git a/ios/RNCWKWebViewManager.m b/ios/RNCWKWebViewManager.m index 8de7f93..6585297 100644 --- a/ios/RNCWKWebViewManager.m +++ b/ios/RNCWKWebViewManager.m @@ -56,6 +56,7 @@ RCT_EXPORT_VIEW_PROPERTY(allowsLinkPreview, BOOL) */ RCT_EXPORT_VIEW_PROPERTY(messagingEnabled, BOOL) RCT_EXPORT_VIEW_PROPERTY(onMessage, RCTDirectEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock) RCT_EXPORT_METHOD(postMessage:(nonnull NSNumber *)reactTag message:(NSString *)message) { diff --git a/src/WebView.ios.tsx b/src/WebView.ios.tsx index 9e3c2be..ee8ca63 100644 --- a/src/WebView.ios.tsx +++ b/src/WebView.ios.tsx @@ -382,6 +382,7 @@ class WebView extends React.Component { onLoadingProgress={this.onLoadingProgress} onLoadingStart={this.onLoadingStart} onMessage={this.onMessage} + onScroll={this.props.onScroll} onShouldStartLoadWithRequest={onShouldStartLoadWithRequest} ref={this.webViewRef} scalesPageToFit={scalesPageToFit} diff --git a/src/WebViewTypes.ts b/src/WebViewTypes.ts index 5a572a3..4591de2 100644 --- a/src/WebViewTypes.ts +++ b/src/WebViewTypes.ts @@ -7,6 +7,7 @@ import { NativeMethodsMixin, Constructor, UIManagerStatic, + NativeScrollEvent, } from 'react-native'; export interface WebViewCommands { @@ -213,6 +214,7 @@ export interface CommonNativeWebViewProps extends ViewProps { injectedJavaScript?: string; mediaPlaybackRequiresUserAction?: boolean; messagingEnabled: boolean; + onScroll?: (event: NativeScrollEvent) => void; onLoadingError: (event: WebViewErrorEvent) => void; onLoadingFinish: (event: WebViewNavigationEvent) => void; onLoadingProgress: (event: WebViewProgressEvent) => void; @@ -542,6 +544,11 @@ export interface WebViewSharedProps extends ViewProps { */ renderLoading?: () => ReactElement; + /** + * Function that is invoked when the `WebView` scrolls. + */ + onScroll?: (event: NativeScrollEvent) => void; + /** * Function that is invoked when the `WebView` has finished loading. */