iOS7から画面ロック中の音楽再生/停止ボタンのイベントはUIEventSubtypeRemoteControlTogglePlayPauseではありません


iOS7になってから公式以外の色々な音楽再生アプリで画面ロック中の再生/停止ボタンを押しても反応しなくなったので調べました。なんか間違ってたらコメントを貰えると助かります。

# 説明

具体的に次のスクリーンショットで説明すると、音楽再生中にiPhoneをロックし、停止ボタンを押しても停止しないので不愉快です！ってな感じでした。

![IMG_1091.jpg](https://qiita-image-store.s3.amazonaws.com/0/16400/af2817d1-a029-d768-0111-77b7c2577a69.jpeg "IMG_1091.jpg")

# 原因

原因は、ロック画面での再生/停止ボタン押下によるUIEventSubtypeのイベントがUIEventSubtypeRemoteControlTogglePlayPauseではなく、UIEventSubtypeRemoteControlPlayとUIEventSubtypeRemoteControlPauseに別れたことだと思います。

（公式のドキュメントに書かれていないけど、後述する内容から理にかなっていると思えなくもないのと、実装上そのように変わっているのでまあそうなのかなと）

## そもそもどうやってこのイベントをフックしているか

まず、ロック画面の再生/停止ボタンなどはUIResponderクラスで定義されている以下のメソッドをオーバーライドします。

```objectivec:
- (void)remoteControlReceivedWithEvent:(UIEvent *)receivedEvent;
```

上記メソッドの引数としてわたってくる UIEvent オブジェクトにリモコンからの情報(再生、停止、次送りなど)が送られてきます（参考：http://d.hatena.ne.jp/glass-_-onion/20120405/1333611664 )。

このメソッドはdelegateの受け渡しによる実行ではなく、まずアプリがリモートコンロールイベントを受け取れるということを設定することと、オブジェクトがファーストレスポンダになることによって動作します。

```objecrivec:
//アプリがリモートコントロールイベントを受け取れる旨を通知
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];

//ファーストレスポンダになる
[self becomeFirstResponder];
```

リモートコントロールイベントを受け取れるというのは、iPhoneなどのイヤフォン（4極端子）からの再生、停止、次送り操作も出来るようになります。

## iOS7からのremoteControlReceivedWithEvent:メソッドのイベント

iOS7より前は、ロック中の再生/停止ボタン押下時はUIEventSubtypeRemoteControlTogglePlayPauseが発生していたようですが、iOS7端末で再生中に画面をロックし、remoteControlReceivedWithEvent:メソッドを調べたところ、再生させようとした際はUIEventSubtypeRemoteControlPlayイベントが発生し、停止させようとした際はUIEventSubtypeRemoteControlPauseイベントが発生するようになってました。

ロック画面での動作をまとめると

- iOS7より前はUIEventSubtypeRemoteControlTogglePlayPauseが発生していたが
 - iOS7から、再生はUIEventSubtypeRemoteControlPlayが発生
 - iOS7から、停止はUIEventSubtypeRemoteControlPauseが発生

ちなみに、Appleイヤフォンからの再生/停止操作はUIEventSubtypeRemoteControlTogglePlayPauseが発生しています。

これは勘ですが、Appleイヤフォンなどいわゆる4極端子は再生/停止に同じ信号を送っていてiOS側が状態に応じ再生するか停止するかを判断しているからではないかと思います。つまりアプリ側はイベントとしてトグル操作的なUIEventSubtypeRemoteControlTogglePlayPauseを受け取るのは正しく思えます。

また、ロック画面からのイベントがPlayとPauseに別れたことに対しても画面上で再生か停止かの状態がわかるので操作側はトグル操作ではないため、理にかなっているとも思えます。

ただ、この変更は既存のアプリで対応ができていないものが正常に動作しなくなるし、細かくてわかりづらいというデメリットのほうが大きすぎ、流石Apple俺たちにできないことを平然とやってのけるッ！...という感じですかね。

# 対応方法

remoteControlReceivedWithEvent:メソッドの中で、イベントの種類を
UIEventSubtypeRemoteControlTogglePlayPauseだけで判断せず、UIEventSubtypeRemoteControlPlayとUIEventSubtypeRemoteControlPauseも使うのが良いでしょう。

```objectivec:
- (void) remoteControlReceivedWithEvent: (UIEvent *) receivedEvent {
    
    if (receivedEvent.type == UIEventTypeRemoteControl) {
        
        switch (receivedEvent.subtype) {
                
            case UIEventSubtypeRemoteControlPlay:
            case UIEventSubtypeRemoteControlPause:
            case UIEventSubtypeRemoteControlTogglePlayPause:
                //再生だったら再生するし停止だったら停止する
                [self playOrPause];
                break;
                
            case UIEventSubtypeRemoteControlPreviousTrack:
                [self prev];
                break;
                
            case UIEventSubtypeRemoteControlNextTrack:
                [self next];
                break;
                
            default:
                break;
        }
    }
}
```

実際の検証は、iRSSさんのGitHubリポジトリにあるMusicStoreLiteをcloneして確かめて、上記の変更点をpull requestをしました。

MusicStoreLite
https://github.com/funami/MusicStoreLite