NativeScriptでプッシュ通知を受信する(iOS編)

NativeScriptでプッシュを受信する方法としては以下の２つの方法があります。

- Pushプラグインを使う (https://github.com/NativeScript/push-plugin)
- 自前で実装する

Pushプラグインは簡単で良いのですが、Androidの実装がFirebase利用を前提に作られており、また画像付きなどのリッチなプッシュの受信には対応していないようです(2017.7月現在)。プッシュの実装を通してNativeScriptの挙動を理解したいという思いもあるため、自前で実装をしてみることにしました。まずはiOS編から。

# 環境

NativeScript 3.1 with Angular4
XCode8.3
iOS 10.3.2で動作確認済

# 証明書、プロビジョニングの準備

http://qiita.com/natsumo/items/d5cc1d0be427ca3af1cb

この辺を見て証明書の登録、プロビジョニングファイルの作成と登録をします。
iOSの普通のプッシュの実装と何ら変わることはありません。
わかりにくい概念が多く、ハマりどころが多いので頑張ってください！

# XCodeの設定

XCodeのプロジェクト `/platforms/ios/xzshowcase.xcodeproj` を開き、「Capabilities」→「Push Notifications」をONにします。

http://qiita.com/Takumi_Mori/items/11b8d00f73163d890b24#%E5%AF%BE%E5%BF%9C
などを参考にしてみてください。

# カスタムAppDelegateの作成と登録

iOSではアプリがアクティブになった、閉じた、というようなアプリ全体のライフライクルイベントはAppDelegateを継承したクラスに通知されるようです。通知のイベントもAppDelegateクラスに通知されます。NativeScriptではカスタムのAppDelegateクラスを定義して、アプリケーションに登録することができます。

- AppDelegateの作成

`app/appdelegate.ts` を作成し中身は以下のようにしてください。

```ts:app/appdelegate.ts
/**
 * カスタムのAppDelegateクラス(for iOS)
 */
export class AppDelegate extends UIResponder implements UIApplicationDelegate {

	static ObjCProtocols = [UIApplicationDelegate];
}
```

- アプリケーションへの登録

`app/main.ts` に作成したAppDelegateクラスを登録します。
iOSで実行されているときのみ動作させたいコードなので、 application.ios プロパティの存在確認をしています。　`platformNativeScriptDynamic().bootstrapModule(AppModule);` の前に実行されるようにしてください。

```ts:main.ts
import application = require("application");
import { AppDelegate } from "./ios-appdelegate";

// iOSのAppDelegateをセット
if (application.ios) {
	application.ios.delegate = AppDelegate;
}
platformNativeScriptDynamic().bootstrapModule(AppModule);
```


# 通知許可をユーザーに求めて、通知登録

AppDelegateに、以下のメソッドを追加します。
**application:didFinishLaunchingWithOptions** はiOSの起動時に呼ばれるイベントだそうです。今回は起動時に通知許可のダイアログを出したいので、ここに記述していますが、別の画面から呼ぶのもできるはずです。

```ts:appdelegate.ts
	applicationDidFinishLaunchingWithOptions(application: UIApplication, launchOptions: NSDictionary<any, any>): boolean {
		console.log('applicationDidFinishLaunchingWithOptions');

		// 通知許可を求める
		let notificationSettings = UIUserNotificationSettings.settingsForTypesCategories(UIUserNotificationType.Alert|UIUserNotificationType.Badge|UIUserNotificationType.Sound, null);

		// 通知登録
		application.registerUserNotificationSettings(notificationSettings);
		UIApplication.sharedApplication.registerForRemoteNotifications();

		return true;
	}
```

# デバイストークン取得

プッシュ登録をすると数秒ほどでデバイストークンが飛んできますので、以下のメソッドで受け取ります。トークンを受け取ったら加工するなりサーバに送るなり、好きにしてください。

```ts:appdelegate.ts
	applicationDidRegisterForRemoteNotificationsWithDeviceToken(application: UIApplication, deviceToken: NSData){
		console.log('TOKEN = ' + deviceToken.description );
		// トークンの保存などを行う
	}
```

# appdelegate.ts の全体

```appdelegate.ts
/**
 * カスタムのAppDelegateクラス(for iOS)
 */
export class AppDelegate extends UIResponder implements UIApplicationDelegate {

	static ObjCProtocols = [UIApplicationDelegate];

	/**
	 * デバイストークン受信時
	 * @param application
	 * @param deviceToken
	 */
	applicationDidRegisterForRemoteNotificationsWithDeviceToken(application: UIApplication, deviceToken: NSData){
		console.log('TOKEN = ' + deviceToken.description );
		// トークンの保存などを行う
	}

	/**
	 * プッシュ登録でエラー発生したとき
	 * @param app
	 * @param error
	 */
	applicationDidFailToRegisterForRemoteNotificationsWithError(app: UIApplication, error: NSError){
		console.log('applicationDidFailToRegisterForRemoteNotificationsWithError');
		console.dir(error);
	}

	/**
	 * アプリ起動時
	 * @param application
	 * @param launchOptions
	 * @returns {boolean}
	 */
	applicationDidFinishLaunchingWithOptions(application: UIApplication, launchOptions: NSDictionary<any, any>): boolean {
		console.log('applicationDidFinishLaunchingWithOptions');

			// 通知許可を求める
		let notificationSettings = UIUserNotificationSettings.settingsForTypesCategories(UIUserNotificationType.Alert|UIUserNotificationType.Badge|UIUserNotificationType.Sound, null);
		application.registerUserNotificationSettings(notificationSettings);
		UIApplication.sharedApplication.registerForRemoteNotifications();

		console.log('done');
		return true;
	}

}
```

# ビルド＆プッシュ送信テスト

以上でiOSのプッシュ受信準備は完了です。
アプリをビルドして、実機で実行してみましょう。

```shell-session
tns run ios
```

アプリの初回起動時に、「プッシュ通知の許可をしますか」というダイアログが出ますので「許可」を押しましょう。その後、コンソールログにデバイストークンが表示されますので、そちらをメモし、プッシュ送信サービス(Amazon SNSなど）を使ってそのトークンに宛ててメッセージなどを送ってみてください。確実に受信するため、アプリはバックグラウンドか電源を切っておきましょう。

# 補足) XCodeのCapabilities設定を自動化する

XCodeの設定を、毎回XCodeを開いて設定するのは面倒という方もいると思います。また、CI環境などでGUIなしでビルドをしたい場合もあるでしょう。そういう場合には以下のプラグインを使うと便利です。

https://github.com/Essent/nativescript-custom-entitlements

上記のページを参考にインストールしたら
/app/App_Resources/iOS/　に app.entitlements ファイルを置いておき
中身は以下のようにしておきます。（適宜変えてください）

```xml:app.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>aps-environment</key>
	<string>development</string>
</dict>
</plist>
```

この状態で tns run|build を実施すると、hookが効いて platforms/ios の中に entitlements ファイルをコピーしてくれます。