以前こちらの記事でMarkdownをHTML化して表示するための方法をいくつか紹介しました。

その中で開発中のアプリで使っているHTMLViewはいけてないですね、という話を書きました。

が、色々調べているうちに、ちょっと使い方が間違っていたのではということに気がついたのでアップデートしておきます。

valueの渡しかた

HTMLViewvalueプロパティを受け取ります。なのでこれまではMarkdownから生成されたHTMLをそのまま渡していました。

1<HTMLView value={html} />

公式READMEのサンプルコードをみてもこうなっています。

 1render() {
 2  const htmlContent = `<p><a href="http://jsdf.co">&hearts; nice job!</a></p>`;
 3
 4  return (
 5    <HTMLView
 6      value={htmlContent}
 7      stylesheet={styles}
 8    />
 9  );
10}

でもこれだと妙に行間が広かったりとか画像が見えなかったりとかして、質が悪い感じがしていました。 うまくスタイルも効かせることができずに困っていました。

あるときIssueか何かを見ていてコード例が載っていたんですが、それを真似てHTMLをhtmlタグとbodyタグで囲んでみたら妙に広い行間や画像が見えない件が解決しました。

1<HTMLView value={`<html><body>${html}</body></html>`} />

コードを見た感じではhtmlタグやbodyタグを扱っているようには見えなかったので、なぜ?という気がしますが、変更後のほうが良さそうに見えるのでこちらを使うように修正したいと考えています。

こちらのサンプルにおけるbefore afterの画像を載せておきます。

before

After

残念ながら入れ子の<ul>はうまくいきません。中のコードを見ても考慮されていないので対応するにはライブラリ本体に手を入れる必要があるかと。

renderNodeで各DOMをカスタマイズ

先程の例のAfterを見ると画像の横幅がはみ出ています。これを直すためにrenderNode関数を渡して、表示するコンポーネントをカスタマイズできるようです。

renderNodeに関してはREADMEに載ってるので完全に僕がサボってたわけですが、便利ですね。renderNode必須かもな、という気がしています。

先程の例のような画像はみだしの場合は、横幅を限定するようなstyleをあてたImageコンポーネントを使ってやれば解決します。

もともとこうだったのを

1<HTMLView
2  value={`<html><body>${html}</body></html>`}
3 />

こっちにします。

1<HTMLView
2  value={`<html><body>${html}</body></html>`}
3  renderNode={this.renderNode.bind(this)}
4 />

this.renderNodeの実装は例えばこんな感じです。

 1const { width } = Dimensions.get('window');
 2
 3renderNode(node, index, siblings, parent, _) {
 4  if (node.name === 'img') {
 5    const { src } = node.attribs;
 6    return (
 7      <Image
 8        key={index}
 9        style={{ width: width * 0.9, height: 300 }}
10        source={{ uri: src }}
11        resizeMode="contain"
12      />
13    );
14  }
15}

imgタグの場合には、幅をデバイスの90%に、高さを適当な値に固定したImageコンポーネントを表示してねというのを書いておきます。

そしたらこうなります。横幅がちゃんと画面に収まりました。

おわり

<ul>が惜しいですが、いい感じになってきました。

実際のプロジェクトでは同じような設定で使うことが多い気がするので、renderNodeなどを共通してやってくれるコンポーネントを1つ挟むことで利用するのが良さそうです。