Apple Watchもくるし、iOS8のプッシュ通知には今からでも対応しておくべきだと思います。

## iOS8からInteractive Notificationができます

- プッシュ通知にメニューがでてきてから何かしらのアクションを起こせるもの
 - 例：メールの返信、いいね!する
- アプリの起動画面に遷移するより便利(なはず)

ということで、Interactive Notificationの実装方法と、

それをApple Watchでも試す方法を説明していきます。

## 今からでも対応するべき理由

- 通知が「何かをお知らせしてくれる」以上の役割をもったので機能としてプッシュ通知が活用できそう
- **Apple WatchでもInteractive Notificationを使って通知にメニューを表示させる模様**

## Interactive Notificationの始め方

- 必要なもの
 - A. APNs(アップルのプッシュ通知配信システム)用の証明書
 - B. プッシュ通知をAPNsに登録してくれるスクリプト or mBaaS

A.の証明書取得は、自分が前に書いた以下の記事を参考にしてください。
(ちょっと長いけど頑張ろう)

[ニフティクラウドmobile backend(mBaaS)などでAPNsとの連携に必要な準備(2014年3月時点)](http://qiita.com/yuki_rbthbl/items/c8a10ff458c917a21211)

B.のプッシュ通知をAPNsに登録してくれるものとして、無料でプッシュ通知が配信できるmBaaS([ニフティクラウド mobile backend](http://mb.cloud.nifty.com))を利用します
**ニフティクラウド mobile backendが4/1のリリースでInteractive Notificationに対応しています。**


[クイックスタート](http://mb.cloud.nifty.com/doc/quickstart_ios.html)を読めばSDKのセットアップとかはできるはず

実装方法は[ドキュメント](http://mb.cloud.nifty.com/doc/sdkguide/ios/push.html)に書いてありますが
必要なサンプルコードだけは貼っていきます。

AppDelegateの実装の流れとしては以下のようになります。

- プッシュ通知から実行するアクションを定義
- アクションをカテゴリごとにグルーピング
- プッシュ通知配信時にそのカテゴリを指定すれば、そのカテゴリのアクションが通知から実行できる
- フォローリクエストが来たていで、FRIEND_REQUESTカテゴリを定義する
 - リクエストの承認と拒否ができるもの

```objective-c
//起動時に実行されるデリゲートメソッド
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    //ニフティクラウド mobile backendのAPIキー設定
    [NCMB setApplicationKey:@"YOUR_APP_KEY"
                  clientKey:@"YOUR_CLIENT_KEY"
    ];
    
    //iOS8以降と以前でプッシュ通知に必要なデバイストークンを取得する方法が違うので分ける
    if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_7_1){

        //Interactive Notifications用に承認するアクションを作成
        UIMutableUserNotificationAction *acceptAction =
        [[UIMutableUserNotificationAction alloc] init];
        acceptAction.identifier = @"ACCEPT_IDENTIFIER";
        acceptAction.title = @"Accept";
        acceptAction.activationMode = UIUserNotificationActivationModeForeground;
        
        //Interactive Notifications用に拒否アクションを作成
        UIMutableUserNotificationAction *denyAction =
        [[UIMutableUserNotificationAction alloc] init];
        denyAction.identifier = @"DENY_IDENTIFIER";
        denyAction.title = @"Deny";
        denyAction.activationMode = UIUserNotificationActivationModeForeground;

        //カテゴリを作成
        UIMutableUserNotificationCategory *friendReqCategory =
        [[UIMutableUserNotificationCategory alloc] init];
        
        //プッシュ通知で指定するカテゴリ名を設定
        friendReqCategory.identifier = @"FRIEND_REQUEST";
        
        //表示するアクションを設定
        [friendReqCategory setActions:@[acceptAction, denyAction]
                        forContext:UIUserNotificationActionContextDefault];
        
        //カテゴリを集めたNSSetを作成
        NSSet *categories = [NSSet setWithObject:friendReqCategory];
        
        //プッシュ通知のタイプとカテゴリを設定
        UIUserNotificationSettings *settings =
        [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeAlert |
                                                      UIUserNotificationTypeBadge |
                                                      UIUserNotificationTypeSound)
                                          categories:categories];
        [[UIApplication sharedApplication] registerUserNotificationSettings:settings];

        //プッシュ通知許可画面を表示させる(許可してもらえるとデバイストークンが取得できる)
        [[UIApplication sharedApplication] registerForRemoteNotifications];
    } else {
        [[UIApplication sharedApplication] registerForRemoteNotificationTypes:
         (UIRemoteNotificationTypeAlert |
          UIRemoteNotificationTypeBadge |
          UIRemoteNotificationTypeSound)];
    }
    
    return YES;
}

//プッシュ通知を許可してもらってデバイストークンを取得したあとに呼び出されるデリゲートメソッド
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
    
    //ニフティクラウド mobile backendにデバイストークンを含む端末情報を保存する
    NCMBInstallation *installation = [NCMBInstallation currentInstallation];
    [installation setDeviceTokenFromData:deviceToken];
    [installation saveInBackgroundWithBlock:^(NSError *error) {
        if(!error){
            //端末情報の登録が成功した場合の処理
        } else {
            //端末情報の登録が失敗した場合の処理
            if (error.code == 409001){
                //失敗した原因がdeviceTokenの重複だった場合
                [self updateExistInstallation:installation];
            } else {
                //deviceTokenの重複以外のエラーが返ってきた場合
            }
        }
    }];
}

//deviceTokenの重複で端末情報の登録に失敗した場合に上書き処理を行う
-(void)updateExistInstallation:(NCMBInstallation*)currentInstallation{
    NCMBQuery *installationQuery = [NCMBInstallation query];
    [installationQuery whereKey:@"deviceToken" equalTo:currentInstallation.deviceToken];
    
    NSError *searchErr = nil;
    NCMBInstallation *searchDevice = [installationQuery getFirstObject:&searchErr];
    
    if (!searchErr){
        //上書き保存する
        currentInstallation.objectId = searchDevice.objectId;
        [currentInstallation saveInBackgroundWithBlock:^(NSError *error) {
            if (!error){
                //端末情報更新に成功したときの処理
            } else {
                //端末情報更新に失敗したときの処理
            }
        }];
    } else {
        //端末情報の検索に失敗した場合の処理
    }
}

//プッシュ通知のボタンが押された場合に呼び出されるデリゲートメソッド
- (void)application:(UIApplication *) application
    handleActionWithIdentifier: (NSString *) identifier
         forRemoteNotification: (NSDictionary *) notification
             completionHandler: (void (^)()) completionHandler {

    //アクションのIdnetifierごとに処理を分けて実装する
    if ([identifier isEqualToString: @"ACCEPT_IDENTIFIER"]) {
        [self handleAcceptActionWithNotification];
    } else {
        [self handleDenyActionWithNotification];
    }

    //ハンドリングが終了する場合に必ず以下を実行する
    completionHandler();
}

//Acceptが実行された場合の処理
- (void)handleAcceptActionWithNotification{
    NSLog(@"Request accepted.");
}

//Denyが実行された場合の処理
- (void)handleDenyActionWithNotification{
    NSLog(@"Request denied.");
}

```

- acttionのactivationModeプロパティを設定することで、フォアグラウンドで処理するかバックグラウンドで処理するか選べたりする

## プッシュ通知の配信

試しにニフティクラウド mobile backendのダッシュボード(ブラウザからアクセスするやつ)から
プッシュ通知を配信してみる
iOSへのプッシュ通知設定としてカテゴリの項目があるのでそこにFRIEND_REQUESTを指定
![スクリーンショット 2015-04-09 2.20.23.png](https://qiita-image-store.s3.amazonaws.com/0/38157/31b11c5c-96a9-83ae-decb-29a807904b37.png)


実際のプッシュ通知は以下のようになる
![IMG_0009.PNG](https://qiita-image-store.s3.amazonaws.com/0/38157/8d6c2e5c-b867-bd4e-b219-3f3479e0846b.png)


## WatchでのInteractive Notificationを試す

Xcodeが6.3になっていないとこの先は試せません。

- Targetを追加する
- Apple WatchのWatichKit Appを選択
 - 通知を試すので、Include Notification Sceneにチェックを入れる
- プッシュ通知テスト用のJSONファイルを用意
 - WatchKit Simulator用にアクションを指定する必要がある
 - WatchKit Simulator Actions
 
```
{
    "aps": {
        "alert": {
            "body": "Test message",
            "title": "Follow Request!"
        },
        "category": "FRIEND_REQUEST"
    },
    
    "WatchKit Simulator Actions": [
        {
            "title": "ACCEPT",
            "identifier": "ACCEPT_IDENTIFIER"
        },
        {
            "title": "DENY",
            "identifier": "DENY_IDENTIFIER"
        }
                                
    ],
    
    "customKey": "Use this file to define a testing payload for your notifications. The aps dictionary specifies the category, alert text and title. The WatchKit Simulator Actions array can provide info for one or more action buttons in addition to the standard Dismiss button. Any other top level keys are custom payload. If you have multiple such JSON files in your project, you'll be able to select them when choosing to debug the notification interface of your Watch App."
}
```

- YOURAPPNAME WatchKit Extention内にあるInterfaceControllerにてアクションが実行されたときのハンドリングを実装する
 - AppDelegateに書いたInteractive Notificationと基本は同じ
 - デリゲートメソッド名がhandleActionWithIdentifier:forRemoteNotification:に変わる
- アクションのactivateModeがBackgroundの場合は、Extention内のInterfaceControllerに書かれたデリゲートではなく、AppDelegateのデリゲートが呼び出される

```objective-c
@implementation InterfaceController

- (void)awakeWithContext:(id)context {
    [super awakeWithContext:context];

    // Configure interface objects here.
}

- (void)willActivate {
    // This method is called when watch view controller is about to be visible to user
    [super willActivate];
}

- (void)didDeactivate {
    // This method is called when watch view controller is no longer visible
    [super didDeactivate];
}

//activateModeがforegroundに指定されているアクションをApple Watchから実行するとよびだされる
- (void)handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)remoteNotification{
    //アクションのIdnetifierごとに処理を分けて実装する
    if ([identifier isEqualToString: @"ACCEPT_IDENTIFIER"]) {
        [self handleAcceptActionWithNotification];
    } else {
        [self handleDenyActionWithNotification];
    }
}

//Acceptが実行された場合の処理
- (void)handleAcceptActionWithNotification{
    NSLog(@"Request accepted.");
}

//Denyが実行された場合の処理
- (void)handleDenyActionWithNotification{
    NSLog(@"Request denied.");
}

@end
```

## アプリを実行してみる

- Notification用のschemeを選択してWatch Kit Appを実行
- Apple Watchの画面は、iOS Simulatorのメニューバーから、Hardware > External Displays > Apple Watchを選択

シミュレーションではテスト用のJSONファイルでしか通知は試せませんが、実際にはmBaaSで配信されたプッシュ通知にカテゴリが指定されていればアクションが実行できることでしょう...

## 追記(2014/4/24)

- Watch AppのDeployment targetが8.2じゃないとビルド時にエラーになる
- StoryBoardでカテゴリを設定する必要があった
- シミューレータだとcategoryがforegroundでもbackgroundでもInterfaceControllerに書いたハンドリングが実行されている
- NotificationController.mでコメントアウトされているメソッドがきになる
 - プッシュ通知のペイロードを届けてくれるためのものらしい

```
/*
- (void)didReceiveRemoteNotification:(NSDictionary *)remoteNotification withCompletion:(void (^)(WKUserNotificationInterfaceType))completionHandler {
    // This method is called when a remote notification needs to be presented.
    // Implement it if you use a dynamic notification interface.
    // Populate your dynamic notification interface as quickly as possible.
    //
    // After populating your dynamic notification interface call the completion block.
    completionHandler(WKUserNotificationInterfaceTypeCustom);
}
*/
```


## 最後に

英語が苦手なのでドキュメントを読み間違えているかもしれないです。。。
間違った情報があればご指摘ください。