AmazonSNSでiosアプリにプッシュ通知し、通知内容に基づいて表示

## やりたいこと
* iosアプリの通知の処理をAWSにやってもらう。
* iosアプリで通知が来た時、通知内容に基づいて処理を行う。

以前は、通知を自分で構築していたが、届かない端末の処理とかもAmazon先生がかってにやってくれたり、いろいろ良いらしいし、アンドロイド対応した時にもつかえそうだったので。

## 前提
* iosアプリ開発環境持ってる
* AWSアカウント持ってる

## AmazonSNSを使う

参考URL　http://tech4403.hatenablog.com/entry/2013/10/15/193214

ありがとうございました。感謝感謝。
## iosアプリで通知用トークン作成

iOS8からちょっと変わったので注意。
下記を参考にしました。
http://d.hatena.ne.jp/peccu/20140921/ios8_notification

RemoteNotificationの権限のあるプロビジョニング・プロファイリングをセットしておかないと機能しないのでチェックして下さいね。

```objc:AppDelegete.m

// 初回起動時に呼ばれるやつ
// iOS8からちょっと変わったので注意。
- (BOOL)application:(UIApplication *)application
   didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
   // Your Code
   // プッシュ通知の要求
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
    if ([application respondsToSelector:@selector(registerUserNotificationSettings:)]) {
        // use registerUserNotificationSettings
        UIUserNotificationSettings *setting = [UIUserNotificationSettings settingsForTypes: (UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert)  categories:nil];
          [[UIApplication sharedApplication] registerUserNotificationSettings:setting];
          [[UIApplication sharedApplication] registerForRemoteNotifications];
    } else {
        // use registerForRemoteNotifications
        [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
    }
#else
    // use registerForRemoteNotifications
    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
#endif
   // END プッシュ通知の要求
   // Your Code
}

// 通知用のトークンを発行。
// http://mihyaeru21.hatenablog.com/entry/2013/05/17/174910
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
    const char *data = [deviceToken bytes];
    NSMutableString *token = [NSMutableString string];
    for (int i = 0; i < [deviceToken length]; i++) {
        [token appendFormat:@"%02.2hhX", data[i]];
    }
    NSLog(@"device token: %@", token);
    // サーバへの送信が失敗することも想定して保存しておいた方がよいかも。
    NSUserDefaults *udefaults = [NSUserDefaults standardUserDefaults];
    [udefaults setObject:token forKey:@"deviceToken"];
    // トークンをサーバにあげる。
    your_upload_method(token); // 適当に書き換えてね。
}
```
## サーバ側の処理

railsを想定。
AWS SDKを用いたら似たようなコードでいけるのかな？
http://www.absolute-keitarou.net/blog/?p=1206 を参考にしました。

```ruby:push_sample.rb
# -*- coding: utf-8 -*-     
require 'aws-sdk'
require 'json'

AWS.config({
  access_key_id: 'YOUR_ACCESS_KEY_ID',
  secret_access_key: 'YOUR_SECRET_ACCESS_KEY',
  region: 'YOUR_REGION' # like this!! 'ap-northeast-1'
})
APPLICATION_ARN = 'YOUR_APPLICTION_ARN' #like this!! 'arn:aws:sns:ap-northeast-1:012345678901:app/APNS_SANDBOX/YourApp'
# メッセージに加えて、custom_idという追加のパラメータを送るメソッド。
# パラメータは複数追加できるので適当に書き換えてね。
def push(token, message, custom_id)
  sns = AWS::SNS.new
  client = sns.client
  # 存在しない場合は作成する
  response = client.create_platform_endpoint(
    platform_application_arn: APPLICATION_ARN,
    token: token
  )
  # endpointを取得して通知を送る
  endpoint_arn = response[:endpoint_arn]
  # https://github.com/aws/aws-sdk-ruby/issues/336
  apns_payload = { "aps" => { "alert" => message,"sound" => 'default', "custom_id" => custom_id } }.to_json
  # これはSANDBOXのとき。本番のときは書き換えてね。
  message = { "default" => "ok", "APNS_SANDBOX" => apns_payload }.to_json
  client.publish(target_arn: endpoint_arn, message: message, message_structure: 'json')
end

# iosアプリから上がってきたトークンをDBとかに保存しといて使う。
push('TSKSEFWQCYV5MZ5UAWTGT5TPBRDKQ293ZVA3MRXMQZCPA82NAXLPDBRQS9AUFTXD','hello world', 12)
```

## iosアプリの通知時の処理

今回は初回起動時と起動中に通知が来た場合を同じ処理にするため、起動中に通知が来たときにウィンドウを初期化するようにした。

```objc:AppDelegate.m
    
// 初回起動時に呼ばれるやつ
- (BOOL)application:(UIApplication *)application
   didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
   // プッシュ通知の要求
   // ... (上記で書いたコード)
   // END プッシュ通知の要求
   // ウィンドウの初期化
   [self initializeWindow:launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]];
   // Your Code
}
// アプリが起動中に通知が来て、通知をタップしたときに呼ばれるやつ
- (void)application:(UIApplication *)application
   didReceiveRemoteNotification:(NSDictionary *)userInfo {
    // 起動中の画面でなにを表示しているかに関わらず、ここでウィンドウを初期化してしまっている。
    // メモリとかはバゲッジコレクションにおまかせだけど、大丈夫かな・・・
    [self initializeWindow:userInfo];
}
// 画面を初期化＆通知の場合はオプションに基づいて処理
- (void)initializeWindow:(NSDictionary *)options {
    NSLog(@"launch options: %@", options);
    // application:didFinishLaunchingWithOptions:launchOptionsによく書いてあるウィンドウの初期化処理
    // 適当に書き換えてね。
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.mainViewController = [[YourViewController alloc] init];
    self.navController = [[UINavigationController alloc]
                          initWithRootViewController:self.mainViewController];
    self.window.rootViewController = self.navController;    
    [self.window makeKeyAndVisible];
    // ここまでウィンドウ初期化処理

    // ここから通知に基づいた処理
    if (options[@"aps"]) {
        NSDictionary *apsOptions = options[@"aps"];
        // オプションに基づいて処理
        // custom_idのところは適当に書き換えてね。
        your_method(apsOptions[@"custom_id"]);
        // 例えば、得られたIdにもとづいてページを表示。適当に書き換えてね。
        // NSNumber *custom_id =apsOptions[@"custom_id"];
        // [hogehoge showPage:custom_id];
    }
}
```