まだCordovaで消耗してるの？4ヶ月かけてCordovaからNativeScriptアプリに移行して良かったこと

XZ(クローゼット)というクローゼット管理＆コーデ提案アプリを開発しています。
下の画面は、自分が登録している服を使って、天気や気温に合わせたコーデを提案してくれるコーデ予報画面で、コーデを見ているところです。

![Jun-24-2018 12-06-43.gif](https://qiita-image-store.s3.amazonaws.com/0/170712/1906622f-1082-4605-9832-79c27f1bf5f8.gif)


2018年4月にフルリニューアルを行い、アプリ側フレームワークを「Cordova」から「NativeScript」に全面的に移行しました。

それまでCordovaで抱えていた課題と、NativeScriptへの移行して良かったことを、まとめていきたいと思います。NativeScriptを本格的に開発運用している例は日本では少ないので、CordovaアプリやWebViewを使ったハイブリッドアプリで、同じように課題を抱えているデベロッパーの方に参考になるところがあれば幸いです。

# Cordovaアプリで抱えていた課題

### リストでのパフォーマンスの限界

投稿された服やコーディネート（コーデ）の画像が私たちのアプリのコンテンツです。なので、画像のリストがアプリのメイン画面でした。リストの一番下までスクロールすると、次のページの内容が自動で読みこまれる「無限スクロール」をして何ページも見ていると、アプリがどんどん「もっさり」してきます。WebViewなので、DOMが大きくなっていくにしたがって動作が重くなってしまうのです。スクロールがガクガクしたり、遷移のときに5秒ぐらい固まったり。DOMの内容を定期的に捨てるようにしたりしてなんとか性能を確保していましたが、なんでこんなネイティブアプリでは普通にできていることに苦労するんだろう？という思いが絶えませんでした。

### プラグインに無い機能は基本使えない

Cordovaではネイティブの機能をプラグインで使うことができ、例えばカメラ機能であればcordova-plugin-cameraを入れれば、とりあえず普通のカメラの機能は使えます。ですが、カメラプレビュー画面に「明るいところで撮影してください」という文言を出したいとしたら？シャッターボタンを少し大きくしたい場合は？　その瞬間、Cordovaでは、プラグインを自作する、しかありません。しかもObjectiveC(iOS)とJava(Android)を駆使し、かつJavaScriptへのブリッジインターフェースまで実装しなくてはなりません。Webのフロントエンドエンジニアにとっては、非常に骨の折れる仕事です。正直、実装にかけた時間に見合うだけの効果を得られないくらい面倒です。長年の運用で出した結論は「Cordovaで誰かが既にプラグインで実装してないことは基本やらない（＝諦める）」です。

### ネイティブのSDKが簡単に導入できない

アプリを運用していると、いろいろな機能を入れたくなってきます。
「広告をいれたい」
「トラッキングをしたい」
「LINEログインをさせたい」　などなど。

そのSDKを使うCordovaプラグインを誰も作ってない場合には、プラグインを自作する必要があります。特に日本の広告事業者のSDKは、Cordova対応しているところは少なかったため苦労することが多かったです。広告提供している先方の担当者と話していて「アプリならSDKをいれたらすぐに使えますよ〜」と言われて「実はWebViewアプリなのでネイティブのSDKは使えないんです...」ということが結構ありました。もちろんプラグインを作れば良いのですが、ちょっとSDKを試してみる、だけのために数日（下手したら1週間以上）かけてプラグインを実装することは厳しいことが多く、「SDKは基本いれられない」というスタンスに。

ちなみに、広告をネイティブビューで表示するSDKの場合、Cordovaではどんなにプラグインを書いたところで仕様上実装ができません。（厳密にはWebView上に別Viewをオーバーレイさせることはプラグインを駆使すればできるはずですが、HTMLで書いている画面の中、例えばリストの途中に表示するといったことができないため、使える場所に限界があります）


いろいろありますが、まとめると「WebView限界だよね」ということです。


# NativeScriptに移行してよかったこと

### リストのパフォーマンスが圧倒的に高速化した

NativeScriptでは、標準のListViewコンポーネントを使うことでiOSではUITableView、Androidではandroid.widget.ListViewが使われます。画面上に表示されている要素のみがメモリ上に展開され、レンダリングされるため、かなりスクロールしてリストが長くなっても、描画が重くなることはありません。スクロールが圧倒的にスムーズになり、遷移時のかくつきも激減しました。Cordovaで行っていた性能チューニングが全く不要になり、ネイティブUI部品の凄さに感動しました。

![Jun-24-2018 11-35-49.gif](https://qiita-image-store.s3.amazonaws.com/0/170712/b81263b9-ca05-24f2-22c6-ab5a73cb97ed.gif)


### どんなネイティブSDKでも導入できる

NativeScriptでは、プラグインを書くことで、podfileやgradleを書けば、どんなネイティブSDKでも即座に導入することができます。Cordovaのプラグインと違い、ObjectiveCやJavaを一切書かずにライブラリのAPIを呼び出せるので、導入までの工数が圧倒的に短縮できました。また、Cordovaでは不可能だったネイティブViewの組み込みも問題なくできるため、広告によるマネタイズの可能性も広がりました。

### TypeScriptのみで、ネイティブAPIを呼び出せる

NativeScriptではiOSやAndroidのAPIをTypeScriptで呼び出すことができます。例えば、ビューの背景をiOSでよく使われている「すりガラス」っぽい効果にしたい場合は、以下のようなAngularのDirectiveを書けば完成です。

```ts
@Directive({
	selector: "[xzBgBlur]"
})
export class XzBgBlurDirective implements OnInit {

	constructor(private el: ElementRef) {
	}

	ngOnInit() {
		let view = this.el.nativeElement;

		if( view.ios ){
			// すりガラス調にする
			// https://github.com/NativeScript/NativeScript/issues/2192
			let uiView : UIView = <UIView>view.ios;
			let navEffectView = UIVisualEffectView.alloc().initWithEffect(UIBlurEffect.effectWithStyle(UIBlurEffectStyle.Light));
			navEffectView.frame = {
				origin: { x: uiView.frame.origin.x, y: uiView.frame.origin.y },
				size: { width: uiView.frame.size.width, height: uiView.frame.size.height }
			};
			navEffectView.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
			navEffectView.userInteractionEnabled = false;
			uiView.addSubview(navEffectView);
			uiView.backgroundColor = XzColorUtil.color("#a1a3a5", 0.2 ).ios;
			uiView.sendSubviewToBack(navEffectView);
		}

	}
}
```
UIVisualEffectViewといったiOSのネイティブのViewオブジェクトを、TypeScriptからインスタンス化(alloc)したり、frameやautoresizingMaskといったネイティブのプロパティにTypeScriptから値を代入していることに注目してください。ObjectiveCは1行も書く必要がありません。これは画期的なことだと思いませんか？
※この点、ネイティブAPIを呼び出すのに、ObjectiveCやJavaを書かなくてはいけないReact Nativeではなく、NativeScriptを私たちが採用した大きな理由です)

この`xzBgBlur` Directiveを、すりガラスにしたい要素に設定します

```html
<GridLayout class="dialog-container" xzBgBlur>
```

するとこのように、背景がうっすら透けている見た目が完成します。↓

<img width="375" alt="IMG_551CB33EA178-1.jpeg" src="https://qiita-image-store.s3.amazonaws.com/0/170712/863b3c0a-1605-7b34-8ec0-08d5a2c9fb02.jpeg">

本当に全くネイティブコードを書かなくて良いのか？と思う方がいるかもしれません。
実際私達は、今回のアプリリニューアルで、カメラプレビューもフルネイティブでゴリゴリで書き直したり、ネイティブの広告SDKを組み込んだり、LINEログインをできるようにSDKを組み込んだりと、いろいろ行っていますが、今の所、ObjectiveCやJavaは1行も書いていません！

# まとめ

以下のような経験がある方には、NativeScriptは優れたソリューションだと思います。

1. Cordova/WebViewアプリのパフォーマンスの限界を感じている
2. ネイティブの機能を使えないことでCordova/WebViewアプリの制限を感じることがある
3. 社内にiOS/Androidエンジニアがいないのに、両方に対応したアプリを作る必要がある
4. Angular/Vue.jsでの開発経験があるエンジニアがいる

最後の点について補足します。
NativeScriptは、素のJavaScriptのみで開発することも可能ですが、フレームワークとしてAngularを使うことも可能です。私達もそれまでAngular(v1)を使っていたので、NativeScriptがAngularをサポートしているのは、採択した大きな理由の一つです。また、最新のAngularへの追随も非常に早く、5月にAngular6が出て数週間後にはNativeScript AngularのバージョンもAngular6へバージョンアップされています。

そして、4月にはNativeScript Vueも登場しました。まだ少し開発途上の部分はあるようですが、ドキュメントは一番洗練されていて見やすいですし、オープンソースですから、気軽に試してみるのをオススメします。

NativeSciript Angular
https://www.nativescript.org/nativescript-is-how-you-build-native-mobile-apps-with-angular

NativeScript Vue
https://nativescript-vue.org/