React Native で父親専用 PDF ビューワーを作った

PDF ビューワーなんて世の中にゴマンとあるのに、どうしてもうちの父親の要件を満たすアプリが見つからなくて、もう自分で作るかということで作った。

## 成果物

https://github.com/macoshita/AwesomeFinder

![image](https://qiita-image-store.s3.amazonaws.com/0/17722/b5a90c93-649b-cf94-b89d-266847e6490d.png)

## 要件

- 父親がこれまで作ってきたギターコード付き楽譜 PDF 400 件ほどを素早く一覧・検索・表示できること
- iPad 第4世代 Wi-Fi モデル iOS 10　で動作すること。特定個人向けなので AppStore になくても OK
- オフラインで利用可能。 iTunes でファイル共有するやり方はすでに教えてある

（この程度の要件なら既製品が見つかりそうなもんだけど、いくつか試したものはだいたいファイル一覧が激重で、そうでなくても一覧の並び順が unicode 順だったりオフラインで使えなかったりした）

## なぜ React Native ?

幾つか上がった候補と却下理由は下記。

- Swift : GUI でのレイアウトがダルくていつもそこで投げ出してるんで回避
- Xamarin : C# ほとんど書いたことないからモチベが続かなさそうと思って回避。あと VS
- ionic : 今回すでに「他のアプリが基本激重」というのがわかっていたので、ブラウザ実装厳しそうと思って回避
- NativeScript : PDF 表示するプラグインが見当たらなかったので回避

普段は Vue ばっか触ってて React や Angular の経験はあまりないので NativeScript は割と迷ったけど、ハマったときに情報多そうな RN を選んだ。結果として要件を満たすものが作れたし React の勉強にもなったので、良い選択だったかなと思う。

## 実装

### ルーティング

[Navigator](https://facebook.github.io/react-native/docs/using-navigators.html) 読んで、これは生で叩くものじゃないのではと感じ [react-native-router-flux](https://github.com/aksonov/react-native-router-flux) を入れた。画面数少ないし生で叩いてもよかったかもだけど、少なくとも楽はできたと思う。

でも react-native-router-flux も Navigator も各シーンの内容・タイトル・左のボタン・右のボタンがシーンコンポーネントの render でレンダリングされないのが不便なので、正直ベストプラクティスではない感がある。右上の「投稿」ボタンを押すと写真を投稿するとか、組むのが難しいような。

### ファイルリスト

[react-native-fs](https://github.com/johanneslumpe/react-native-fs) を使ってファイルの一覧を取得している。フォルダは父親の運用だと一切作らないので何も気にしてない。

ここでは、ファイル名に使われてる漢字に自動でふりがなを振り、それでソートすることにしたが、その部分に結構時間がかかった。
最初 [kuromoji.js](https://github.com/takuyaa/kuromoji.js) でやろうとしたけど React Native 用の辞書ローダーを追加実装しないと動かないことに気づき断念。

調べてみると、 iOS には漢字からローマ字取得して、さらにローマ字をひらがなに変換する機能があったので、そこだけ Objective-C で実装した。実装したと言っても [こちらのコード](https://github.com/murakami/workbook/tree/master/mac/Ruby) を [Native Modules のドキュメント](https://facebook.github.io/react-native/docs/native-modules-ios.html) 通りにポーティングしたらあっさり動いた。

そのままの実装だと50件くらいしかレンダリングされてなくて、一覧をスクロールしていくとジワジワと読み込みが走ってその度につっかかるような動きになったので、 [ドキュメントの Performance](https://facebook.github.io/react-native/docs/performance.html) を参考に initialListSize を初めからファイルの総数に設定して対処した。ファイル数が1,000とか超えると辛そうではある。

検索バーは [react-native-search-bar](https://github.com/umhan35/react-native-search-bar) を使った。 onChangeText で ListViewDataSource を再フィルタしている。

### ビューワー

~~当初は [webview で PDF 開けば普通に表示できる](http://qiita.com/macoshita/items/b745fe480eae5cf44ce6) ということでそうしてたんだけど、どうも画面移動しても表示されたりされなかったりして、結局 [React Native PDF View](https://github.com/cnjon/react-native-pdf-view) というライブラリを使った。すごくスムーズに動いてくれて助かった。~~

React Native PDF View には 2 ページ目が表示されないという不具合があった……（父親の PDF ほとんど1ページしかなかったから気づかなかった）。で、結局 WebView で PDF を表示する方法に戻したんだけど、React Native の WebView は UIWebView ということをしり、 WKWebView を使うライブラリを導入したらうまく動いた。詳しくは[こちら](http://qiita.com/macoshita/items/b745fe480eae5cf44ce6)

### ストレージ

AsyncStorage でファイル名をふりがなに変換した結果を保存している。少なくとも、毎回ふりがな変換するよりは高速になったし、ふりがなを編集する画面を設けて超電磁砲と書いてレールガンと読みたい場合とかにも対応できるようになった。もしかしたら SQLite とか使った方がいいのかもしれない。

### その他

- react-native のコマンドは下記をよく使った
    - `react-native run-ios --simulator 'iPad Retina'` iPad シミュレータで実行
    - `react-native run-ios --device` デバイスで実行
    - `react-native run-ios --configuration Release --device` リリース版をデバイスで実行
- デバイス転送してるとき、シェイクすると開発メニューが出てきてリロードできたりする。でも iPad 振り回すの辛いので、開発メニューから hot reload をオンにした
- 「RN のバグでライブラリが読み込めないみたい。ごめんね watchman のキャッシュを消してね」と結構言われるけど、大体自分のミスだった。
- [シミュレータでファイル共有の場所を探す](http://qiita.com/macoshita/items/821a6093f4232e7e0903)のがなかなか手間。でも react-native-fs 使ってる場合は `console.log(RNFS.DocumentDirectoryPath)` でログ出力できる。

## まとめ

ハマったのは kuromoji.js が動かなかったことくらいで、むしろ比較的かんたんにネイティブ実装に逃げられたのが好印象。何よりネットに情報が多いのでスムーズに作れた。もうひとつふたつは何か作ってみようと思う。