Skip to content

Commit 146b04f

Browse files
scrollTo()
1 parent 6b5cf64 commit 146b04f

File tree

8 files changed

+145
-22
lines changed

8 files changed

+145
-22
lines changed

README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,33 @@ When set to `true` this will hide the default keyboard accessory view.
9292

9393
### `focus()`
9494

95+
```javascript
96+
focus(string: string)
97+
```
98+
9599
#### Android
96-
Calls [`requestFocus`](https://developer.android.com/reference/android/webkit/WebView#requestFocus(int,%20android.graphics.Rect)) and shows the keyboard.
100+
Calls [`requestFocus()`](https://developer.android.com/reference/android/webkit/WebView#requestFocus(int,%20android.graphics.Rect)) and shows the keyboard.
97101

98102
#### iOS
99103
Calls [`HTMLElement.blur()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLOrForeignElement/blur) and [`HTMLElement.focus()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLOrForeignElement/focus) on [`document.activeElement`](https://developer.mozilla.org/en-US/docs/Web/API/DocumentOrShadowRoot/activeElement). It won't work if [`keyboardDisplayRequiresUserAction`](#keyboardDisplayRequiresUserAction) is `true` or if [`document.activeElement`](https://developer.mozilla.org/en-US/docs/Web/API/DocumentOrShadowRoot/activeElement) is not focusable. It is recommended to just focus your field from JavaScript instead of calling this method, calling blur beforehand may be required.
100104

101-
### `injectJavaScript(string)`
105+
### `injectJavaScript()`
106+
107+
```javascript
108+
injectJavaScript(string: string)
109+
```
102110

103111
Executes the JavaScript string.
104112

113+
### `scrollTo()`
114+
115+
```javascript
116+
scrollTo(options?: {x?: number, y?: number, animated?: boolean})
117+
```
118+
119+
Scrolls to a given x, y offset, either immediately or with a smooth animation.
120+
121+
By default x and y are `0`, animated is `true`.
105122
## Requirements
106123

107124
- React Native 0.60 or later

android/src/main/java/com/reactnativewebviewalternative/WebViewAlternativeManager.kt

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package com.reactnativewebviewalternative
22

3+
import android.R.attr.x
4+
import android.R.attr.y
5+
import android.animation.ObjectAnimator
6+
import android.animation.PropertyValuesHolder
37
import android.annotation.SuppressLint
8+
import android.content.res.Resources
49
import android.os.Build
510
import android.view.MotionEvent
611
import android.view.inputmethod.InputMethodManager
@@ -21,6 +26,7 @@ class WebViewAlternativeManager(private val reactContext: ReactApplicationContex
2126
LOAD_HTML_STRING(1),
2227
FOCUS(2),
2328
INJECT_JAVASCRIPT(3),
29+
SCROLL_TO(4),
2430
}
2531

2632
companion object {
@@ -119,11 +125,29 @@ class WebViewAlternativeManager(private val reactContext: ReactApplicationContex
119125
}
120126
}
121127
}
128+
Command.SCROLL_TO.commandId -> {
129+
if (args != null) {
130+
val density = Resources.getSystem().displayMetrics.density
131+
val x = (args.getDouble(0) * density).toInt()
132+
val y = (args.getDouble(1) * density).toInt()
133+
if (args.getBoolean(2)) {
134+
val scrollX = PropertyValuesHolder.ofInt("scrollX", root.scrollX, x)
135+
val scrollY = PropertyValuesHolder.ofInt("scrollY", root.scrollY, y)
136+
ObjectAnimator.ofPropertyValuesHolder(root, scrollX, scrollY).start()
137+
} else {
138+
root.scrollTo(x, y)
139+
}
140+
}
141+
}
122142
}
123143
}
124144

125145
override fun getCommandsMap(): Map<String, Int> {
126-
return MapBuilder.of("loadHTMLString", Command.LOAD_HTML_STRING.commandId, "focus", Command.FOCUS.commandId, "injectJavaScript", Command.INJECT_JAVASCRIPT.commandId)
146+
return MapBuilder.of("loadHTMLString", Command.LOAD_HTML_STRING.commandId,
147+
"focus", Command.FOCUS.commandId,
148+
"injectJavaScript", Command.INJECT_JAVASCRIPT.commandId,
149+
"scrollTo", Command.SCROLL_TO.commandId
150+
)
127151
}
128152

129153
override fun getExportedCustomDirectEventTypeConstants(): Map<String, Map<String, String>> {

example/src/App.tsx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useRef, useState } from 'react'
2-
import { StyleSheet, View, Platform, Button, Text } from 'react-native'
2+
import { StyleSheet, Platform, Button, Text, SafeAreaView } from 'react-native'
33
import WebView, { WebViewRef } from 'react-native-webview-alternative'
44

55
const demo1: string = require('./demo1.html')
@@ -9,35 +9,46 @@ export default function App() {
99
const ref = useRef<WebViewRef>(null)
1010

1111
return (
12-
<View style={styles.container}>
12+
<SafeAreaView style={styles.container}>
1313
<Button
1414
title="Set input text to 'Hello World'"
1515
onPress={() => {
1616
ref.current?.injectJavaScript('input.value = "Hello World"')
1717
}}
1818
/>
19+
<Button
20+
title="Scroll to 4th section"
21+
onPress={() => {
22+
ref.current?.scrollTo({ y: 400, animated: false })
23+
}}
24+
/>
25+
<Button
26+
title="Scroll to top"
27+
onPress={() => {
28+
ref.current?.scrollTo({ animated: false })
29+
}}
30+
/>
1931
<Text>{message}</Text>
2032
<WebView
2133
ref={ref}
2234
source={{ html: demo1 }}
23-
keyboardDisplayRequiresUserAction={false}
35+
// keyboardDisplayRequiresUserAction={false}
2436
style={styles.webView}
25-
scrollEnabled={false}
37+
// scrollEnabled={false}
2638
hideKeyboardAccessoryView
2739
onMessage={({ nativeEvent: { message } }) => (
2840
setMessage(String(message)), console.log(message, typeof message)
2941
)}
3042
onLoad={() => Platform.OS === 'android' && ref.current?.focus()}
3143
/>
32-
</View>
44+
</SafeAreaView>
3345
)
3446
}
3547

3648
const styles = StyleSheet.create({
3749
container: {
3850
flex: 1,
3951
alignItems: 'center',
40-
justifyContent: 'center',
4152
},
4253
webView: {
4354
width: '100%',

example/src/demo1.html

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,28 @@
1111
body {
1212
margin: 0;
1313
}
14+
15+
h6 {
16+
margin: 0;
17+
}
18+
19+
.tall-div {
20+
height: 100px;
21+
}
1422
</style>
1523
</head>
1624
<body>
17-
<h6>Helloo</h6>
18-
<input type="text" id="asd" autofocus />
25+
<div class="tall-div">
26+
<h6>Helloo</h6>
27+
<input type="text" id="asd" autofocus />
28+
</div>
29+
<div class="tall-div" style="background-color: #10a6c9"></div>
30+
<div class="tall-div" style="background-color: #a35dda"></div>
31+
<div class="tall-div" style="background-color: #37357c"></div>
32+
<div class="tall-div" style="background-color: #118d7d"></div>
33+
<div class="tall-div" style="background-color: #5f74dc"></div>
34+
<div class="tall-div" style="background-color: #0f9775"></div>
35+
<div class="tall-div" style="background-color: #92bec1"></div>
1936
<script type="text/javascript">
2037
window.input = document.getElementById('asd');
2138
input.addEventListener('input', () => {

ios/RCTWebViewAlternative.m

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ @implementation RCTWebViewAlternativeContentViewHelper
1414
- (BOOL)keyboardDisplayRequiresUserAction {
1515
if (_webView == nil) return YES;
1616

17-
if ([_webView respondsToSelector:sel_getUid("keyboardDisplayRequiresUserAction")]) {
17+
#pragma clang diagnostic push
18+
#pragma clang diagnostic ignored "-Wundeclared-selector"
19+
if ([_webView respondsToSelector:@selector(keyboardDisplayRequiresUserAction)]) {
20+
#pragma clang diagnostic pop
1821
return [(RCTWebViewAlternative *)_webView keyboardDisplayRequiresUserAction];
1922
}
2023

@@ -24,7 +27,10 @@ - (BOOL)keyboardDisplayRequiresUserAction {
2427
- (BOOL)hideKeyboardAccessoryView {
2528
if (_webView == nil) return YES;
2629

27-
if ([_webView respondsToSelector:sel_getUid("hideKeyboardAccessoryView")]) {
30+
#pragma clang diagnostic push
31+
#pragma clang diagnostic ignored "-Wundeclared-selector"
32+
if ([_webView respondsToSelector:@selector(hideKeyboardAccessoryView)]) {
33+
#pragma clang diagnostic pop
2834
return [(RCTWebViewAlternative *)_webView hideKeyboardAccessoryView];
2935
}
3036

@@ -68,34 +74,49 @@ - (void)setKeyboardDisplayRequiresUserAction:(BOOL)keyboardDisplayRequiresUserAc
6874
Class contentViewClass = objc_lookUpClass("WKContentView");
6975
IMP override;
7076

71-
SEL keyboardDisplayRequiresUserActionSelector = sel_getUid("keyboardDisplayRequiresUserAction");
77+
#pragma clang diagnostic push
78+
#pragma clang diagnostic ignored "-Wundeclared-selector"
79+
SEL keyboardDisplayRequiresUserActionSelector = @selector(keyboardDisplayRequiresUserAction);
80+
#pragma clang diagnostic pop
7281
BOOL (* getKeyboardDisplayRequiresUserAction)(id, SEL) = (BOOL (*)(id, SEL))method_getImplementation(class_getInstanceMethod(RCTWebViewAlternativeContentViewHelper.class, keyboardDisplayRequiresUserActionSelector));
7382
if (@available(iOS 13.0.0, *)) {
74-
SEL selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:");
83+
#pragma clang diagnostic push
84+
#pragma clang diagnostic ignored "-Wundeclared-selector"
85+
SEL selector = @selector(_elementDidFocus:userIsInteracting:blurPreviousNode:activityStateChanges:userObject:);
86+
#pragma clang diagnostic pop
7587
method = class_getInstanceMethod(contentViewClass, selector);
7688
IMP original = method_getImplementation(method);
7789
override = imp_implementationWithBlock(^void(UIView *this, void *information, BOOL userIsInteracting, BOOL blurPreviousNode, uint activityStateChanges, id userObject) {
7890
if (!getKeyboardDisplayRequiresUserAction(this, keyboardDisplayRequiresUserActionSelector)) userIsInteracting = YES;
7991
((void (*)(id, SEL, void*, BOOL, BOOL, uint, id))original)(this, selector, information, TRUE, blurPreviousNode, activityStateChanges, userObject);
8092
});
8193
} else if (@available(iOS 12.2.0, *)) {
82-
SEL selector = sel_getUid("_elementDidFocus:userIsInteracting:blurPreviousNode:changingActivityState:userObject:");
94+
#pragma clang diagnostic push
95+
#pragma clang diagnostic ignored "-Wundeclared-selector"
96+
SEL selector = @selector(_elementDidFocus:userIsInteracting:blurPreviousNode:changingActivityState:userObject:);
97+
#pragma clang diagnostic pop
8398
method = class_getInstanceMethod(contentViewClass, selector);
8499
IMP original = method_getImplementation(method);
85100
override = imp_implementationWithBlock(^void(UIView *this, void* arg0, BOOL userIsInteracting, BOOL arg2, BOOL arg3, id arg4) {
86101
if (!getKeyboardDisplayRequiresUserAction(this, keyboardDisplayRequiresUserActionSelector)) userIsInteracting = YES;
87102
((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(this, selector, arg0, TRUE, arg2, arg3, arg4);
88103
});
89104
} else if (@available(iOS 11.3.0, *)) {
90-
SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:");
105+
#pragma clang diagnostic push
106+
#pragma clang diagnostic ignored "-Wundeclared-selector"
107+
SEL selector = @selector(_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:);
108+
#pragma clang diagnostic pop
91109
method = class_getInstanceMethod(contentViewClass, selector);
92110
IMP original = method_getImplementation(method);
93111
override = imp_implementationWithBlock(^void(UIView *this, void *arg0, BOOL userIsInteracting, BOOL arg2, BOOL arg3, id arg4) {
94112
if (!getKeyboardDisplayRequiresUserAction(this, keyboardDisplayRequiresUserActionSelector)) userIsInteracting = YES;
95113
((void (*)(id, SEL, void *, BOOL, BOOL, BOOL, id))original)(this, selector, arg0, TRUE, arg2, arg3, arg4);
96114
});
97115
} else {
98-
SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:");
116+
#pragma clang diagnostic push
117+
#pragma clang diagnostic ignored "-Wundeclared-selector"
118+
SEL selector = @selector(_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:);
119+
#pragma clang diagnostic pop
99120
method = class_getInstanceMethod(contentViewClass, selector);
100121
IMP original = method_getImplementation(method);
101122
override = imp_implementationWithBlock(^void(UIView *this, void *arg0, BOOL userIsInteracting, BOOL arg2, id arg3) {
@@ -120,8 +141,11 @@ - (void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView {
120141
if (method != nil) return; // Already swizzled
121142

122143
Class contentViewClass = objc_lookUpClass("WKContentView");
123-
SEL inputAccessoryViewSelector = sel_getUid("inputAccessoryView");
124-
SEL hideKeyboardAccessoryViewSelector = sel_getUid("hideKeyboardAccessoryView");
144+
#pragma clang diagnostic push
145+
#pragma clang diagnostic ignored "-Wundeclared-selector"
146+
SEL inputAccessoryViewSelector = @selector(inputAccessoryView);
147+
SEL hideKeyboardAccessoryViewSelector = @selector(hideKeyboardAccessoryView);
148+
#pragma clang diagnostic pop
125149
BOOL (* getHideKeyboardAccessoryViewSelector)(id, SEL) = (BOOL (*)(id, SEL))method_getImplementation(class_getInstanceMethod(RCTWebViewAlternativeContentViewHelper.class, hideKeyboardAccessoryViewSelector));
126150
method = class_getInstanceMethod(contentViewClass, inputAccessoryViewSelector);
127151
IMP original = method_getImplementation(method);

ios/WebviewAlternative.m renamed to ios/WebViewAlternative.m

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@ - (RCTWebViewAlternative *)view {
5555
}];
5656
}
5757

58+
RCT_EXPORT_METHOD(scrollTo:(nonnull NSNumber *) reactTag offsetX:(CGFloat)x offsetY:(CGFloat)y animated:(BOOL)animated) {
59+
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
60+
UIView *view = viewRegistry[reactTag];
61+
if (!view || ![view isKindOfClass:RCTWebViewAlternative.class]) {
62+
RCTLogError(@"Cannot find NativeView with tag #%@", reactTag);
63+
return;
64+
}
65+
66+
[((RCTWebViewAlternative *)view).scrollView setContentOffset:(CGPoint){x, y} animated:animated];
67+
}];
68+
}
69+
5870
RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL)
5971
RCT_EXPORT_VIEW_PROPERTY(keyboardDisplayRequiresUserAction, BOOL)
6072
RCT_EXPORT_VIEW_PROPERTY(hideKeyboardAccessoryView, BOOL)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-native-webview-alternative",
3-
"version": "0.0.1",
3+
"version": "0.0.2",
44
"description": "Alternative to react-native-webview",
55
"main": "lib/commonjs/index.js",
66
"module": "lib/module/index.js",

src/index.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,27 @@ interface WebViewAlternativePropsIOS {
66
hideKeyboardAccessoryView?: boolean
77
}
88

9+
export interface WebViewMessageEvent {
10+
nativeEvent: { message: object | string | number | boolean }
11+
}
12+
913
interface WebViewAlternativeProps extends ViewProps, WebViewAlternativePropsIOS {
1014
source?: { html: string; baseURL?: string } | null
1115
children?: ReactNode
1216
scrollEnabled?: boolean
1317
onLoad?(): void
14-
onMessage?(event: { nativeEvent: { message: object | string | number | boolean } }): void
18+
onMessage?(event: WebViewMessageEvent): void
19+
}
20+
21+
export interface ScrollToOptions {
22+
x?: number
23+
y?: number
24+
animated?: boolean
1525
}
1626

1727
export interface WebViewAlternativeRef {
1828
focus(): void
29+
scrollTo(options?: ScrollToOptions): void
1930
injectJavaScript(string: string): void
2031
}
2132

@@ -54,6 +65,13 @@ function WebViewAlternative({ source = null, ...props }: WebViewAlternativeProps
5465
undefined,
5566
)
5667
},
68+
scrollTo({ x = 0, y = 0, animated = true }: ScrollToOptions = {}) {
69+
UIManager.dispatchViewManagerCommand(
70+
findNodeHandle(nativeComponentRef.current),
71+
UIManager.getViewManagerConfig('WebViewAlternative').Commands.scrollTo,
72+
[x, y, animated],
73+
)
74+
},
5775
injectJavaScript(string: string) {
5876
UIManager.dispatchViewManagerCommand(
5977
findNodeHandle(nativeComponentRef.current),

0 commit comments

Comments
 (0)