React Native WebView에서 폴더블 폰 너비를 대응해보자

리브레리브레
3 min read

최근 React Native로 앱을 개발하고 있던 친구에게서 연락이 왔다.
이전에 React Native WebView로 폴더블 폰 너비를 대응한 경험이 있어, 이와 관련해서 어떻게 처리했는지 궁금하다고 했다.
그래서 방법을 알려줬는데 해당 내용을 인터넷에서 찾아보자니 어려움을 겪었다고 하여, 이 참에 과거에 해당 사항을 어떻게 대응했는지 블로그에 남겨보기로 했다.

앱에서 반응형을 대응할 경우: useWindowDimensions을 활용하자.

React Native에서는 Dimensions라는 객체에서 윈도우 너비를 가져오거나, React Native에서 제공해주는 Hook api인 useWindowDimensions를 통해서 윈도우 너비를 가져올 수 있다.

이때, useWindowDimensions 훅은 폴더블 폰의 너비 변경에 따라 값을 자동으로 갱신해주므로, 반응형 레이아웃에 아주 맛깔나게 쓰기 좋은 장점이 있다.

export function Example() {
  const { width } = useWindowDimensions();

  return (
    <View>
        <Text>{`현재 너비는 ${width} 입니다.`}</Text>
    </View>
  )
}

그런데, WebView에서?

React Native로 구현하는 사람들이라면 분명 React Native WebView를 사용할텐데, WebView에서 폴더블 대응을 처음 해보는 사람들이라면 useWindowDimensions에서 내려주는 값을 그대로 전달하면 되겠지 생각하고 width, height를 WebView 컴포넌트의 style에 넣어서 반영할 것이다.

하지만 이렇게 할 경우, WebView의 너비는 전혀 변하지 않는다.

WebView 컴포넌트는 앱 컴포넌트이다.
변경 되어야 하는건 WebView에서 띄우는 그 ‘웹 페이지’인데, 웹 페이지에서 변경을 하는 게 아니라 앱 컴포넌트인 WebView에서 스타일 변경을 반영하다 보니 WebView에서 원하는 대로 반응형 스타일이 반영되지 않는 것이다.

결국 그렇게 스타일을 전달하면 WebView 안쪽에선 처음 시점의 뷰포트 크기만 인식할 뿐, 화면을 접거나 펼쳐도 레이아웃엔 전혀 영향을 주지 않는다.

해결책 1) useEffect와 postMessage 활용하기

당시 이 문제를 해결하기 위해 내가 채택한 방법은 useEffect로 postMessage를 활용하는 거였다.
스크린 너비를 useEffect가 의존하도록 deps에 넣어준 뒤, 화면 크기가 바뀔 때마다 postMessage를 통해서 WebView에 변경된 너너비가 전달되게 해줬다.

즉, 앱에서 너비가 변경된 것을 감지하면 그 정보를 postMessage를 통해 WebView 안으로 전달하는 거였다.

아래의 앱 코드를 보자.

export function AppWebView() {
  const webviewRef = useRef<WebView>(null);
  const { width } = useWindowDimensions();

  useEffect(() => {
    webviewRef.current?.postMessage(
      JSON.stringify({ type: "RESIZE", width })
    );
  }, [width]);

  return (
    <View style={{ flex: 1 }}>
      <WebView
        ref={webviewRef}
        source={{ uri: MY_WEB_APP_DOMAIN }}
      />
    </View>
  );
}

내용처럼 useWindowDimensions에서 받아온 width를 postMessage를 이용하여 값을 전달한다.
WebView에서 보일 웹 페이지의 코드는 아래처럼 해두자.

export function WebApp() {
  const [width, setWidth] = useState<number>(0);

  useEffect(() => {
    const changeWidth = (event: MessageEvent) => {
      try {
        const data = JSON.parse(event.data);
        if (data.type === "RESIZE") {
          setWidth(data.width);
        }
      } catch (e) {
        console.error(event.data);
      }
    }

    window.addEventListener("message", changeWidth);
    return () => window.removeEventListener("message", changeWidth);
  }, []);

  return (
    <div
      style={{
        width: width ? `${width}px` : "100%",
        height: "100vh",
      }}
    >
      웹페이지
    </div>
  );
}

위처럼 nativeEvent에서 postMessage를 통해 값을 보낸 앱 데이터는 이제 addEventListener를 통해서 메시지 이벤트를 받을 때마다 너비가 가변적으로 바뀌게 된다.

해결책 2) injectedJavaScript로 반영하기

해당 문제를 해결하고 한참 뒤에, 사이드 프로젝트로 React Native를 하던 중에 폴더블 폰 대응을 체크할 사항이 있었다.
당시 같이 개발하던 프론트 개발자가 이 사항에 대해서 자신은 injectedJavaScript를 활용하여 너비가 바뀔 때마다 변경되는 값들을 전달하여 반영시켰다고 했다.

아래와 같이 말이다.

export function AppWebView() {
  const webviewRef = useRef<WebView>(null);
  const { width } = useWindowDimensions();

  useEffect(() => {
    webviewRef.current?.postMessage(
      JSON.stringify({ type: "RESIZE", width })
    );
  }, [width]);

  return (
    <View style={{ flex: 1 }}>
      <WebView
        ref={webviewRef}
        source={{ uri: MY_WEB_APP_DOMAIN }}
        injectedJavaScript={`
          (function() {
            window.addEventListener("message", function(event) {
              try {
                const data = JSON.parse(event.data);
                if (data.type === "RESIZE") {
                  document.body.style.width = data.width + "px";
                }
              } catch (e) {
                console.error(event.data);
              }
            });
          })();
        `}
      />
    </View>
  );
}

단, 이 방식은 본인이 웹 페이지를 직접 만들고 제어하는 게 아닌 외부의 웹 페이지를 제어해야 하는 상황에 추천한다.
외부 페이지는 소스 수정을 할 수 있는 권한이 없다보니 DOM 조작을 받아와 처리하는 게 어려우므로 이 방식을 통해 강제로 조작해야 한다.

이렇게 내가 직접 구현했던 방식과 다른 개발자가 알려줬던 방식을 이야기해봤다.
어느 방법이든 자신의 프로젝트에 맞게 알맞게 사용해서, WebView 폴더블 대응에 고통받는 시간을 줄도록 해보자.

Adius!

0
Subscribe to my newsletter

Read articles from 리브레 directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

리브레
리브레

2년차 프론트엔드 개발자입니다. 웹(React, Next.js)과 웹뷰, 앱(React Native) 개발 경험이 있어 플랫폼에 구애받는 개발이 가능합니다. 선 개발 후 개선에 따른 빠른 개발을 지향하고, 여러 번 QA와 테스트를 통해 기능을 개선합니다. 작업 경과를 중간, 완료 때마다 공유하고 논의해 소통 에러와 기능의 문제점을 최소화하고 있습니다.