アプリを iOS 8 に対応させたら連絡先を選択したときに電話を発信するようになってしまった話
Cocoa プログラミング
iOS 5 を想定したアプリを iOS 7 以上でビルドしていたところ、アプリ内で連絡先を選択したときに画面に番号が入力されるはずが電話発信されるようになっているのに気がつきました。
どうやら iOS 8 から動作が変わっていたみたいです。
かれこれ 2 年前にリリースした iOS 5 を想定したアプリを、先日に iOS 7 以上でビルドしたのですけど、そうしたところ、アプリ内で連絡先を選択したときに画面に番号が入力されるはずが、電話発信されるようになっているのに気がつきました。
調べてみると、どうやら ABPeoplePickerNavigationController
まわりの挙動が iOS 8 から変更されたため、そのような動きになってしまったようです。
iOS 8 で使えなくなった機能
ABPeoplePickerNavigationController
に関係する iOS 8 で使えなくなった機能は、具体的には ABPeoplePickerNavigationControllerDelegate
に用意されていた次のメソッドです。
- -peoplePickerNavigationController:shouldContinueAfterSelectingPerson:
- -peoplePickerNavigationController:shouldContinueAfterSelectingPerson:property:identifier:
これらの機能が廃止されて、代わりに次のメソッドが追加されていました。
- -peoplePickerNavigationController:didSelectPerson:
- -peoplePickerNavigationController:didSelectPerson:property:identifier:
iOS 7 までは選択されたときにどう動くかに焦点が当たった機能になっていたのが、iOS 8 からは選択されたこと自体に焦点が当たった機能に変わった様子です。
これによって iOS 8 だと、連絡先から ABPeoplePickerNavigationController
で選択したときに、これまで使用していた shouldContinue... といったメソッドが呼び出されなくなったことが、即電話発信の挙動の原因になった様子でした。
iOS 8 で選択時に独自の処理を行うには
iOS 8 で、連絡先を選択したときに独自の処理を行いたいときは、次のメソッドを実装する必要があるようです。
- peoplePickerNavigationController:didSelectPerson:property:identifier:
なお、このメソッドは戻り値を返さないので、これまでのように状況に応じて発信するかの動作を変えるということは気軽にはできない様子です。
いちおう ABPeoplePickerNavigationController
の方に、選択時の挙動を変えるプロパティが実装されているようなので、必要であればそれを使うと良いのでしょう。
predicateForSelectionOfPerson
ここに設定した NSPredicate が真を返すとアプリに処理が任されるそうです。偽を返すと連絡先の詳細情報が表示される画面に移動するそうです。nil を設定すると peoplePickerNavigationController:didSelectPerson: が実装されている場合に限り、真を返したのと同じ動きになるそうです。
predicateForSelectionOfProperty
ここに設定した NSPredicate が真を返すとアプリに処理が任されるそうです。偽を返すと既定の動作、つまり電話発信になるそうです。nil を設定すると peoplePickerNavigationController:didSelectPerson:property:identifier: が実装されている場合に限り、真を返したのと同じ動きになるそうです。
iOS 8 に対応する
この仕様変更を踏まえて、コードを iOS 8 に対応してみました。
- 新しく
-peoplePickerNavigationController:didSelectPerson:property:identifier:
を実装します。 - これまで使っていた
-peoplePickerNavigationController:shouldContinueAfterSelectingPerson:property:identifier:
の実装をほぼまるごと、上で実装したメソッドに移動します。新しいメソッドでは戻り値はとらないので削除します。 - これまでのメソッド内から、新しいメソッドを呼び出します。
つまり、概要だけを簡単に書くと、次のような形に収まりました。
// このメソッドを新設して、ここで連絡先を選択したときの処理を行います。
// 内容は、これまでに使っていたメソッドの内容を写してきます。戻り値はないので注意です。
- (void)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker didSelectPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier
{
...
}
// 以前に使っていたメソッドは iOS 7 以下のために残しておきます。
// 実装内容は、新しく設置した iOS 8 用のデリゲートメソッドを呼ぶ形にしておきます。戻り値の値だけ、この中で決めて返します。
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier
{
[self peoplePickerNavigationController:peoplePicker didSelectPerson:person property:property identifier:identifier];
return NO;
}
今回の場合は、新しく ABPeoplePickerNavigationController
に用意されたプロパティを使うことなく、このように新しいデリゲートメソッドを実装してあげるだけで、選択したものを使って処理をするということが iOS 8 でもできるようになりました。
以前のメソッドの内容をそのままに、新しいメソッド内で以前のメソッドを呼ぶ方法もありますが、比べれば iOS 8 用の実装より先に iOS 7 用の実装が不要になるので、要らなくなって消すときにサクッと消せるように、新しい方へ内容を移しておくことにしました。