(WebView‑обёртка для сайта , React Native)
Небольшой шаблон и руководство по быстрому созданию мобильного приложения на базе существующего сайта с использованием WebView — простого, кроссплатформенного и гибкого подхода.
- Одностраничное приложение: весь сайт в
WebView - Нативный сплэш‑скрин и значок приложения (иконка, launch screen)
- Индикатор загрузки, обработка ошибок и состояния сети
- Обработка Android‑Back, нативная навигация (header/back buttons)
- JS‑bridge: вызовы из Web → React Native → Web и обратно
- Доступ к геолокации, push‑уведомлениям, share меню, документ‑сканам и др.
- Кеширование, offline‑режим, CodePush‑обновления
⚠️ Важно при публикации в App StoreApps that are simply web sites bundled as Apps "may be rejected"
"Your app should include features, content, and UI that elevate it beyond a repackaged website"
:contentReference[oaicite:0]{index=0}
Чтобы проходить review, нужно добавить нативные фичи (push‑уведомления, offline‑режим, share‑actions, QR‑сканер, биометрию и т. п.) и визуально "превратить веб в app" :contentReference[oaicite:1]{index=1}.
- Node.js ≥ 18
- React Native ≥ 0.70 (или Expo SDK ≥ 48)
react-native-webviewпоследней версии (вдобавок к core RN)
(раньше был встроен, с 2024 переведён в отдельный модуль) :contentReference[oaicite:2]{index=2}- На Android:
AndroidManifest.xmlпрописано<uses-permission android:name="android.permission.INTERNET" />; при http‑лочках —<application android:usesCleartextTraffic="true"> - На iOS: при «не‑https» доменах — добавить
NSAllowsArbitraryLoadsв Info.plist
npx create-expo-app webview-wrapper
cd webview-wrapper
expo install react-native-webviewЗамените App.js:
import React, { useRef, useState, useEffect } from 'react';
import { SafeAreaView, View, ActivityIndicator, Platform, BackHandler, Share } from 'react-native';
import { WebView } from 'react-native-webview';
export default function App() {
const webview = useRef(null);
const [canGoBack, setCanGoBack] = useState(false);
const [loading, setLoading] = useState(true);
const onNavState = (nav) => {
setCanGoBack(nav.canGoBack);
};
useEffect(() => {
const onBackPress = () => {
if (canGoBack) {
webview.current.goBack();
return true;
}
return false;
};
BackHandler.addEventListener('hardwareBackPress', onBackPress);
return () =>
BackHandler.removeEventListener('hardwareBackPress', onBackPress);
}, [canGoBack]);
const injectedJS = `
window.isNativeApp = true;
true;
`;
return (
<SafeAreaView style={{ flex: 1 }}>
{loading && (
<View
style={{
position: 'absolute',
top: 0, left: 0, right: 0, bottom: 0,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#fff'
}}
>
<ActivityIndicator size="large" />
</View>
)}
<WebView
ref={webview}
source={{ uri: 'https://example.com' }}
onNavigationStateChange={onNavState}
injectedJavaScriptBeforeContentLoaded={injectedJS}
onLoadEnd={() => setLoading(false)}
onMessage={(e) => {
/* пример: пост‑сообщения Web → RN */
const { data } = e.nativeEvent;
console.log('Message from Web:', data);
}}
javaScriptEnabled
domStorageEnabled
/>
<View style={{ position: 'absolute', bottom: 16, right: 16 }}>
<ActivityIndicator size="small" color="#000" />
</View>
</SafeAreaView>
);
}Теперь:
- При Android‑Back будет навигация
WebView← - Загрузка и индикатор сплэша
injectedJavaScriptBeforeContentLoadedперед загрузкойonMessage()для связи с Web
- Используйте
@react-native-community/netinfoдля проверки сети (см.onError,renderErrorвWebView) - При отсутствии сети показывайте родной
Viewс кнопкой повторить загрузку или кешом
- Expo Push/OneSignal для нативных push
- На сайте отправьте запрос JS в Web, а в приложении — native диалог + action
window.ReactNativeWebView.postMessage(
JSON.stringify({ action: 'share', url: location.href })
);В RN:
onMessage={({ nativeEvent }) => {
const msg = JSON.parse(nativeEvent.data);
if (msg.action === 'share') Share.share({ url: msg.url });
}}- Используйте
onShouldStartLoadWithRequest(iOS/Android ≥0.57) - Перехватывайте внешние ссылки (
target="_blank"), обрабатывайте либо открывайте через Linking
- Минимум требований Guideline 4.2 / 2.12: должно быть больше, чем просто сайт в
WebView([apple.stackexchange.com][1]) - Укажите
Privacy Policyи предоставьте правила конфиденциальности - Используйте
WKWebView(a.k.a.react-native-webview) безUIWebView— иначе возможен rejection по deprecated API ([stackoverflow.com][2]) - В
App Store Connectпри review: кратко опишите JS‑bridge и push‑фичи в notes
- Менее жёсткие правила, но всё равно рекомендуется добавить офлайн‑режим и нативную навигацию
- При финальной сборке подпишите APK для release (jks‑ключ и
gradle)
- Splash-сцена: Android —
android/app/src/main/res/splash.xmlCustom, iOS — LaunchImage/Xcode Asset. - Значки приложения: Expo config →
app.json(значки, разрешения). - OTA‑обновления: Используйте Microsoft CodePush или Expo Updates, если сайт обновляется без билда приложения.
- Инжект скриптов:
injectedJavaScript,injectJavaScript()после навигации (onLoadEnd) - JS → RN → JS пример в шаблоне
/webview-wrapper
├─ App.js <-- наш главный WebView
├─ assets/ <-- иконки, splash, изображения
├─ package.json
├─ app.json <-- Expo: название, versionCode, etc.
├─ android/... <-- Android: splash, манифест
└─ ios/... <-- iOS: App‑ID, Info‑plist, права
| Ошибка / Симптом | Причина / Решение |
|---|---|
requireNativeComponent: "RNCWebView" was not found |
Кэш/подключение react-native-webview сломано. Удалите node_modules, сбросьте Pods, выполните pod install. |
App crashes on iOS при open в WebView |
Нужен WKWebView, убедитесь, что v > 11.0 библиотеки; удалите UIWebView. |
| Android‑Back закрывает приложение | Не возвращён true из обработчика Web Back (BackHandler). |
| Серый фон / SafeArea не учитывается | Используйте SafeAreaView (iOS) и statusBarHeight (Android). |
Этот подход — быстрый старт для превращения существующего веба в мобильное приложение. Но для успешной публикации особенно в App Store, важно не ограничиваться только обёрткой: добавить нативные фичи и офлайн‑режим, позаботиться о пользовательском UX, и лишь потом — деплоять.