본문 바로가기

리액트 네이티브

리액트 네이티브 WebView 중첩

react-native-webview는 안드로이드에서 NestingScroll API를 준수하지 않기 때문에, ScrollView 중첩과 마찬가지로 ScrollView 안에 WebView를 랜더링하면 스크롤이 잘 작동하지 않습니다.

 

이 경우 WebView의 높이를 컨텐츠 높이까지 늘려서 사용했는데, WebView의 높이가 너무 길어지면 앱이 꺼지거나 스크롤이 튀기는 현상이 발생했었습니다.

 

그리고 웹뷰도 브라우저이기 때문에, 컴포지터 스래드에서 뷰 너비에 맞는 부분만 가져와 표시하는 최적화가 적용되어 있을텐데, 모든 컨텐츠를 표시하면 리소스도 많이 잡아먹을 것 같습니다.

 

이 경우 NestedScrollWebView를 사용할 수 있습니다.

 

react-native-troika/packages/nested-scroll-webview at master · sdcxtech/react-native-troika

Native UI Component for React Native, including nested-scroll, pull-to-refresh, bottom-sheet, etc. - sdcxtech/react-native-troika

github.com

 

패치만 수행하는 라이브러리고, react-native-webview를 그대로 사용하면 됩니다.

 

react-native-webview'RNCWebView' 문자열로 네이티브 모듈에 접근합니다.

import { requireNativeComponent } from "react-native";
import type { NativeWebViewAndroid } from "./WebViewTypes";

const RNCWebView: typeof NativeWebViewAndroid = requireNativeComponent(
  'RNCWebView',
);

export default RNCWebView;

https://github.com/react-native-webview/react-native-webview/blob/v11.26.1/src/WebViewNativeComponent.android.ts

 

nested-scroll-webview는 react-native-webivew에서 정의한 RNCWebView에 해당하는 ViewManager 대신사용할 ViewManager 정의합니다.

package com.reactnativecommunity.webview; // react-native-webview와 동일한 패키지

import static com.reactnativecommunity.webview.RNCWebViewManager.REACT_CLASS;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;

@ReactModule(name = REACT_CLASS) // RNCWebView 모듈을 덮어씌운다.
public class RNCNestedScrollWebViewManager extends RNCWebViewManager { // 기존 ViewManager 그대로 이용
    @Override
    protected RNCWebView createRNCWebViewInstance(ThemedReactContext reactContext) {
        return new RNCNestedScrollWebView(reactContext); // 수정된 View 객체 사용
    }

    @Override
    public boolean canOverrideExistingModule() {
        return true;
    }
}

https://github.com/sdcxtech/react-native-troika/blob/e65a0f6b313aceed288197ba61fa9cbc1dc1d451/packages/nested-scroll-webview/android/src/main/java/com/reactnativecommunity/webview/RNCNestedScrollWebViewManager.java#L9

 

react-native-webview12 버전부터 안드로이드 부분이 많이 바뀌어서 11버전까지만 지원됩니다.

 

일단 아래와 같이 작동시킬 수는 있는데, 정석대로 하려면 WebViewManager 클래스를 새로 만들어야 합니다.

 

문제가 되는 부분까지 보여드리기 위해 기존 코드를 수정하는 방법으로 알려드리겠습니다.

1. react-native-webview 수정

RNCWebViewManagerImpl 기능을 상속하여 그대로 사용하기 위해 막혀있는 접근 제한자를 풀어줍니다. 

 

12버전에서 새로 추가된 구현 클래스는 코틀린으로 만들어졌는데, 코틀린의 클래스는 기본적으로 final이기 때문에, 이 부분을 풀어줍니다.

 

RNCWebViewManagerImpl.kt

open class RNCWebViewManagerImpl {
    // ...

    open fun createRNCWebViewInstance(context: ThemedReactContext): RNCWebView {
        return RNCWebView(context)
    }

    // ... 
}

https://github.com/react-native-webview/react-native-webview/blob/v13.12.2/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManagerImpl.kt#L34-L61

 

RNCWebViewManager를 상속하여 덮어씌울 ViewManager가 RNCWebViewManagerImpl 상속 객체를 사용할 수 있도록 private에서 public으로 풀어줍니다. 

 

RNCWebViewManager.java

public class RNCWebViewManager extends ViewGroupManager<RNCWebViewWrapper> {
    protected RNCWebViewManagerImpl mRNCWebViewManagerImpl;
}

https://github.com/react-native-webview/react-native-webview/blob/v13.12.2/android/src/oldarch/com/reactnativecommunity/webview/RNCWebViewManager.java#L28

2. nested-scroll-webview 수정

RNCWebViewManagerImpl.java 생성

package com.reactnativecommunity.webview;

import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;

public class RNCNestedScrollWebViewManagerImpl extends RNCWebViewManagerImpl {
    @Override
    public RNCWebView createRNCWebViewInstance(ThemedReactContext reactContext) {
        return new RNCNestedScrollWebView(reactContext);
    }
}

 

RNCWebViewManager.java 수정

package com.reactnativecommunity.webview;

import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;

@ReactModule(name = RNCWebViewManagerImpl.NAME) // "RNCWebView" 
public class RNCNestedScrollWebViewManager extends RNCWebViewManager {
    public RNCNestedScrollWebViewManager() {
        mRNCWebViewManagerImpl = new RNCNestedScrollWebViewManagerImpl();
    }

    @Override
    public RNCWebViewWrapper createViewInstance(ThemedReactContext reactContext) {
        return mRNCWebViewManagerImpl.createViewInstance(reactContext);
    }
}

https://github.com/sdcxtech/react-native-troika/blob/e65a0f6b313aceed288197ba61fa9cbc1dc1d451/packages/nested-scroll-webview/android/src/main/java/com/reactnativecommunity/webview/RNCNestedScrollWebViewManager.java#L8-L19

 

RNCWebViewRNCWebViewManager의 중첩 클래스에서 독립했습니다.

 

RNCNestedScrollWebView.java 수정

public class RNCNestedScrollWebView extends RNCWebViewManager.RNCWebView implements NestedScrollingChild3 {
    // ...
}

https://github.com/sdcxtech/react-native-troika/blob/e65a0f6b313aceed288197ba61fa9cbc1dc1d451/packages/nested-scroll-webview/android/src/main/java/com/reactnativecommunity/webview/RNCNestedScrollWebView.java#L18

3. 프로젝트 수정

MainApplication.kt 경로에 NestedWebViewPackage.java 생성

package com.myapp; // 수정

import androidx.annotation.NonNull;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import com.reactnativecommunity.webview.RNCNestedScrollWebViewManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class NestedWebViewPackage implements ReactPackage {
    @NonNull
    @Override
    public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

    @NonNull
    @Override
    public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
        return Arrays.asList(
                new RNCNestedScrollWebViewManager()
        );
    }
}

 

MainApplication.kt 수정

package com.myapp

class MainApplication : Application(), ReactApplication {
  override val reactNativeHost: ReactNativeHost =
      object : DefaultReactNativeHost(this) {
        override fun getPackages(): List<ReactPackage> =
            PackageList(this).packages.apply {
              add(NestedWebViewPackage()) // 추가
            }
        // ...
      }
}