웹뷰(WebView)란 프레임워크에 내장된 웹 브라우저 컴포넌트로 뷰(View)의 형태로 앱에 임베딩하는 것을 말한다.
쉽게 말해서, App에서 웹브라우저를 이용해 화면을 보여주는 방식을 뜻한다.
(인스타그램에서 링크를 클릭하면 내장된 브라우저로 화면을 보여주는 방식)
$ npm install --save react-native-webview
설치 후 ios는
cd ios && pod install && ..
WebView에 width와 height를 지정하지 않으면 화면이 보이지 않는다. 디바이스 크키에 꽉차게 들어가게 Dimensions
를 import하여 크기를 지정해줬다.
import React from 'react';
import {SafeAreaView, StyleSheet, Dimensions} from 'react-native';
import WebView from 'react-native-webview';
const windowWidth = Dimensions.get('window').width;
const windowHeight = Dimensions.get('window').height;
const Webview = () => {
return (
<SafeAreaView style={styles.container}>
<WebView
ref={ref}
style={styles.webview}
source={{uri: 'https://naver.com'}}
onNavigationStateChange={e => setNavState(e)}
/>
</SafeAreaView>
);
};
export default Webview;
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-between',
},
webview: {
flex: 1,
width: windowWidth,
height: windowHeight,
},
});
Home 스크린에서 버튼을 클릭하며 웹뷰가 나오게 하기 위해서 네이게이션을 셋팅해준다.
// Navigator.js
(...)
const HomeStack = () => {
return (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={({navigation}) => ({
// headerShown: false,
title: 'Home',
headerLeft: () => (
<TouchableOpacity onPress={() => navigation.openDrawer()}>
<Text>Drawer</Text>
</TouchableOpacity>
),
})}
/>
<Stack.Screen
name="Detail"
component={Detail}
options={{title: 'Detail'}}
/>
<Stack.Screen name="Web" component={Webview} />
</Stack.Navigator>
);
};
(...)
// HomeScreen.js
(...)
const HomeScreen = ({navigation}) => {
const [userInfo, setUserInfo] = useState(false);
return (
<SafeAreaView>
<Text>Home</Text>
<Button
title="네이버로 이동"
onPress={() => navigation.navigate('Web')}
/>
</SafeAreaView>
);
};
홈에서 버튼을 클릭하면 웹뷰 컴포넌트로 이동해서 네이버 페이지를 보여준다.
성공적으로 웹뷰를 불러왔으나 웹뷰에서 뉴스를 클릭해서 이동하고 뒤로가기 버튼(<Home
) 클릭하면 네이버 홈으로 이동하는게 아니라 앱의 HomeScreen으로 이동하고 웹뷰가 종료된다.
물리적인 백버튼이 있는 안드로이드에서도 마찬가지로 뒤로가기 버튼을 클릭하면 웹뷰가 종료된다.
이 문제를 해결하는 방법으로는 헤더에 있는 백버튼에 새로운 이벤트를 지정해줘야한다.
// Webview.js
import {HeaderBackButton} from '@react-navigation/elements';
const Webview = ({navigation}) => {
const ref = useRef(null);
const [navState, setNavState] = useState();
useEffect(() => {
const canGoBack = navState.canGoBack;
const onPress = () => {
if (canGoBack) {
ref.current.goBack();
return true;
} else {
return false;
}
};
navigation.setOptions({
headerLeft: () =>
canGoBack ? <HeaderBackButton onPress={onPress} /> : null,
});
// 안드로이드 백 버튼(물리적 버튼)을 누르면 웹뷰가 종료되지 않고 바로 전 화면으로 넘어가게 하기 위함.
BackHandler.addEventListener('hardwareBackPress', onPress);
// clean up
return () => {
BackHandler.removeEventListener('hardwareBackPress', onPress);
};
}, [navState.canGoBack]);
return (
<SafeAreaView style={styles.container}>
<WebView
ref={ref}
style={styles.webview}
source={{uri: 'https://naver.com'}}
onNavigationStateChange={e => setNavState(e)}
/>
</SafeAreaView>
);
};
onNavigationStateChange
: react-native-webview에서 제공하는 API이다.
Example:<WebView source={{ uri: 'https://reactnative.dev' }} onNavigationStateChange={(navState) => { // Keep track of going back navigation within component this.canGoBack = navState.canGoBack; }} />
navState:
canGoBack canGoForward loading navigationType (iOS only) target title url
navState
를 콘솔에 출력하면 아래와 같이 나온다. 이 객체안에canGoBack
를 사용해서 뒤로가기 버튼의 이벤트를 생성하면 된다.
Only for Android
공식문서에 따르면 BackHandler.addEventListener
는 이벤트 리스너를 생성하고 NativeEventSubscription
를 리턴한다. 이 NativeEventSubscription
는 NativeEventSubscription.remove
메소드를 사용해서 clean-up 되어야한다.
import React, {useEffect} from 'react';
import {Text, View, StyleSheet, BackHandler, Alert} from 'react-native';
const App = () => {
useEffect(() => {
const backAction = () => {
Alert.alert('Hold on!', 'Are you sure you want to go back?', [
{
text: 'Cancel',
onPress: () => null,
style: 'cancel',
},
{text: 'YES', onPress: () => BackHandler.exitApp()},
]);
return true;
};
const backHandler = BackHandler.addEventListener(
'hardwareBackPress',
backAction,
);
return () => backHandler.remove();
}, []);
return (
<View style={styles.container}>
<Text style={styles.text}>Click Back button!</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
text: {
fontSize: 18,
fontWeight: 'bold',
},
});
export default App;