2

React ネイティブ WebView コンポーネントは Android ではリンクを開くことができませんが、IOS では問題なく正常に動作します。押した後、Androidでは何も起こっていません。エラーはありません。同時に、反応するネイティブ ハイパーリンクでリンクを開こうとしましたが、正常に動作します。どうすれば解決できますか教えてください。

const IS_IOS = Platform.OS === 'ios';

const script = `
;(function() {
var wrapper = document.createElement("div");
wrapper.id = "height-wrapper";
while (document.body.firstChild) {
    wrapper.appendChild(document.body.firstChild);
}
document.body.appendChild(wrapper);
var i = 0;
function click(e) {
  e.preventDefault();
  window.postMessage(click, '*');
};
function updateHeight() {
    document.title = wrapper.scrollHeight;
    window.location.hash = ++i;
}
function linkProcessed() {
    var links = document.getElementsByTagName('a');
    for (var i = 0; i < links.length; i++) {
        links[i].onclick = function (e) {
            e.preventDefault();
            window.ReactNativeWebView.postMessage(e.currentTarget.getAttribute("href"), '*');
        }
    }

    var links = document.getElementsByTagName('img');
    for (var i = 0; i < links.length; i++) {
        links[i].onclick = function (e) {
            e.preventDefault();
            window.ReactNativeWebView.postMessage(e.currentTarget.getAttribute("src"), '*');
        }
    }
}
updateHeight();
setTimeout(linkProcessed, 1000);
window.addEventListener("load", function() {
    updateHeight();
    setTimeout(updateHeight, 1000);
});
window.addEventListener("resize", updateHeight);
}());
`;

const postMessage = `(function() {
    var originalPostMessage = window.postMessage;

    var patchedPostMessage = function(message, targetOrigin, transfer) {
        originalPostMessage(message, targetOrigin, transfer);
    };

    patchedPostMessage.toString = function() {
        return String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage');
    };

    window.postMessage = patchedPostMessage;
})();`;

const style = `
<style>
body, html, #height-wrapper {
    margin: 0;
    padding: 0;
}
#height-wrapper {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
}
</style>
<script>
${script}
</script>
`;

const textStyleToCss = (textStyle: StyleProp<TextStyle>, styles?: StyleProp<ViewStyle>): string => {
  if (!textStyle) {
    return '';
  }

  const {
    fontSize,
    fontFamily,
    fontUrl,
    lineHeight,
    letterSpacing,
    color,
  } = StyleSheet.flatten(textStyle) as any;

  const {
    backgroundColor,
  } = StyleSheet.flatten(styles) as any;

  const css = IS_IOS ?
    `<style type="text/css">@font-face {font-family: MyFont; font-style: normal;
    font-weight: 400; src: local('${fontFamily}'), url('${fontUrl}');}
body,* {
    font-family: MyFont; font-size: ${fontSize}pt;text-align: left; line-height: ${lineHeight}pt; letter-spacing: ${letterSpacing}pt; color: ${color};
    background-color: ${backgroundColor};
}
img{height: auto;max-width: 100%;}
iframe {height: auto;max-width: 100%;}
div{height: auto;max-width: 100%;}
</style>` :

    `<style type="text/css">@font-face {font-family: MyFont; font-style: normal; 
     font-weight: 400; src: url("${fontUrl}");}
body,* {
    font-family: MyFont; font-size: ${fontSize}px;text-align: left; line-height: ${lineHeight}px; letter-spacing: ${letterSpacing}px; color: ${color};
    background-color: ${backgroundColor};
}
img{height: auto;max-width: 100%;}
iframe {height: auto;max-width: 100%;}
div{height: auto;max-width: 100%;}
   </style>`;

  return css;
};

const markdownLinksMap = (text: string) => {
  const markdownLinkReg = /\[([^\[\]]+)\]\(([^)]+)\)/img;

  text = text.replace(markdownLinkReg, (substring, ...args) => {
    const title = args[0];
    const url = args[1];
    const linkKey = `<a href="${url}">${title}</a>`;

    return linkKey;
  });

  return text;
};

const codeInject = (args: { html: string, textStyle?: StyleProp<TextStyle>, style?: StyleProp<ViewStyle> }): string => {
  const textWithoutMarkdown = markdownLinksMap(args.html);

  return (`
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    ${textStyleToCss(args.textStyle, args.style)}
</head>
<body>${'<div style="color: ' + args.textStyle!.color + '">' + textWithoutMarkdown + '</div>'}${style}</body> 
  `)
};

interface TextStyleProp extends TextStyle {
  fontSize: number;
  fontFamily: string;
  lineHeight: number;
  letterSpacing: number;
  color: string;
  fontUrl: string;
}

interface Props {
  html: string;
  navigateToWebview?: (url: string) => void;

  style?: StyleProp<ViewStyle>;
  textStyle?: StyleProp<TextStyleProp>;
}

interface State {
  correctHtml: string;
  height: number;
}

export class AutoHeightWebView extends React.Component<Props, State> {
  public static getDerivedStateFromProps(props: Props, state: State) {
    if (props.html) {
      const correctHtml = codeInject(props);

      return { correctHtml };
    }

    return null;
  }

  private webview: any;

  constructor(props: Props) {
    super(props);

    this.state = {
      correctHtml: codeInject(props),
      height: 120,
    };
  }

  public componentWillUnmount(): void {
    if (this.webview) {
      this.webview.stopLoading();
      this.webview = null;
    }
  }

  public render() {
    if (!(this.props.html && this.props.html.length > 0)) {
      return (<View />);
    } else {
      return (
        <View style={[SS.container, this.props.style]}>
          <WebView
            ref={this.setWebView}
            automaticallyAdjustContentInsets={true}
            source={{ html: this.state.correctHtml, baseUrl: '' }}
            onMessage={this.onMessage}
            javaScriptEnabled={true}
            injectedJavaScript={`${postMessage};document.body.scrollHeight;`}
            style={{ height: this.state.height }}
            onNavigationStateChange={this.onNavigationChange}
            domStorageEnabled={true}
            scalesPageToFit={Platform.select({ios: undefined, android: true})}
            startInLoadingState={true}
            scrollEnabled={true}
            showsHorizontalScrollIndicator={false}
            showsVerticalScrollIndicator={false}
            bounces={false}
            originWhitelist={['*']}
          />
        </View>
      );
    }
  }

  private onNavigationChange = (event: NavState): void => {
    if (event.title) {
      const htmlHeight = Number(event.title); //convert to number
      if (htmlHeight && this.state.height !== htmlHeight) {
        this.setState({ height: htmlHeight });
      }
    }
  }

  private onMessage = (event: NativeSyntheticEvent<WebViewMessageEventData>): void => {
    const url = event.nativeEvent.data;
    if (this.props.navigateToWebview) {
      this.props.navigateToWebview(url);
    } else {
      Linking.openURL(url);
    }
  }

  private setWebView = (webview: any): void => {
    this.webview = webview;
  };
}

const SS = EStyleSheet.create({
  container: { flex: 1, overflow: 'hidden' },
});
4

1 に答える 1