iOS 6 で連絡先へのアクセス許可を取得する : Objective-C プログラミング

PROGRAM


iOS 6 で連絡先へのアクセス許可を取得する

iOS 6.0 以降からは、iPhone に登録されている連絡先をアプリが使う場合、あらかじめ OS を通してユーザーから使用許可を取得しなければいけなくなりました。

連絡先の使用許可は <AddressBook/AddressBook.h> に定義されている関数を使って取得できます。

連絡先へのアクセス許可を取得する

意図したタイミングで使用許可を確認する

意図したタイミングで使用許可を取得したい場合は ABAddressBookRequestAccessWithCompletion 関数を使用します。

// ユーザーによって連絡先の使用許可が判断された後に実行する処理を Blocks で用意します。

ABAddressBookRequestAccessCompletionHandler handler = ^(bool granted, CFErrorRef error)

{

// ユーザーが連絡先の使用を許可した場合に granted が true になっています。

};

 

// ユーザーに連絡先の使用許可を求めます。完了すると handler で指定した Blocks が呼び出されます。

ABAddressBookRequestAccessWithCompletion(NULL, handler);

この ABAddressBookRequestAccessWithCompletion 関数を実行すると、このアプリが連絡先を使ってもいいかをユーザーが選択していない場合に、連絡先の使用を許可するか禁止するかを選択するメッセージボックスが表示されます。

それに対してユーザーがどちらかを選択すると、第二引数で指定した Blocks が実行されます。

ちなみに第一引数は、許可するかどうかを問い合わせる対象のアドレス帳を渡すことになるのでしょうか。NULL を渡しておけばとりあえず期待通りの動作はしてくれるようでした。

 

許可されたか禁止されたかを知る必要がない場合や、必要になったら ABAddressBookGetAuthorizationStatus 関数を使って状況を判断すれば十分の場合は、次のように第二引数も NULL で呼び出しても大丈夫です。

// ユーザーに連絡先の使用許可を求めます。非同期で実行されます。

ABAddressBookRequestAccessWithCompletion(NULL, NULL);

使用許可の確認は非同期で行われるため、これを実行したからといって、次の行とかですぐ許可状況を判定できる訳ではないので注意してください。

逆に言えばスレッドをブロックしないため、メインスレッドのたとえば viewWillAppear などで実行しても、ビューの初期化処理を邪魔することはありません。

 

ここで、ABAddressBookRequestAccessWithCompletion を使う上での注意点を整理しておきます。

まず、この関数を実行した時に、連絡先へのアクセスを許可するかどうかを選択するメッセージボックスは、ユーザーがまだそれを選んでいない場合に限り表示されます。許可でも禁止でも、既にユーザーによって過去に選ばれたことがあれば表示されません。

そして、メッセージボックスが表示されるかどうかにかかわらず、ABAddressBookRequestAccessWithCompletion 関数の第二引数で指定した Blocks が実行されます。

このとき、許可された場合には grant に YES が、禁止された場合には grant に NO が設定されています。

 

また、第二引数で指定した Blocks は非同期で実行されます。

そのため、たとえば Blocks 内でグローバル変数に何かを代入するようにした場合には、その値が ABAddressBookRequestAccessWithCompletion 関数を実行した後すぐに利用できる訳ではないことに注意します。

そしてこの Blocks は呼び出しスレッドとは別のスレッド(非メインスレッド)で実行されるところにも注意する必要があります。

iOS アプリでは UI 周りの制御はメインスレッドで行わないといけないため、ここでそのまま UILabel のテキストを変更したりとかしても、期待通りの動作はしてくれません。

 

必要になったときに使用許可を確認する

<AddressBookUI/AddressBookUI.h> で定義されている ABPeoplePickerNavigationController を使って連絡先を選択させたい場合は、許可を取得するコードを明示的に記載しなくても大丈夫です。

// ABPeoplePickerNavigationController をインスタンス化したタイミングで、初回だけ連絡先の使用許可が確認されます。

ABPeoplePickerNavigationController* picker = [[ABPeoplePickerNavigationController alloc] init];

このように ABPeoplePickerNavigationController のインスタンスを初期化した時点で、そのアプリがまだユーザーから連絡先の使用許可を取っていない場合には、使用許可を求めるメッセージボックスが表示されます。

ちなみに使用を禁止された場合、ABPeoplePickerNavigationController では、連絡先を利用できない旨を画面に表示してユーザーに知らせるつくりになっています。

 

アクセス許可を取得できたら

連絡先の利用許可が下りたら、後は ABAddressBookCreate 関数や ABAddressBookCreateWithOptions 関数を使ってアドレス帳を取得することができます。

// ユーザーに連絡先の使用許可を求めます。完了すると handler で指定した Blocks が呼び出されます。

ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, NULL);

連絡先の利用を禁止されていた場合は NULL が戻り値として返されるので、それで許可状況に応じた処理を行うこともできます。

ただし、そもそもまだ許可するかどうかをユーザーが判断していない間は、空のアドレス帳が取得されるようなので NULL ではなかったからといって、必ずしも利用許可が下りている訳ではないところに注意します。

 

現在の許可状態を確認する

ユーザーへの問い合わせを行わずに、現在の許可がどのようになっているかを確認したい場合は、次のようにします。

// 連絡先の現在のアクセス許可状態を取得します。

ABAuthorizationStatus status = ABAddressBookGetAuthorizationStatus();

この関数で取得できる状態は次の通りです。

kABAuthorizationStatusNotDetermined 0 許可か不許可か、まだユーザーが選択していない場合
kABAuthorizationStatusRestricted 1 機能制限設定によって連絡先へのアクセスができない場合
kABAuthorizationStatusDenied 2 連絡先へのアクセスをユーザーが禁止している場合
kABAuthorizationStatusAuthorized 3 連絡先へのアクセスをユーザーが許可している場合

いったんユーザーが利用許可・不許可を指定してしまうと、それ以降はアプリ側から許可を選択させるメッセージボックスは表示できません。

そのため、許可しないと使えない機能がある場合はこうして状態を判断して、たとえば iOS のプライバシー設定から許可するよう促すメッセージを表示するなどの対応が必要になります。

 

アクセス許可を求めるときの表示メッセージを変更する

これまでに説明した方法ではアクセス許可を求める時に標準のメッセージしか表示されないので、ともするとユーザーが、何のためにそれの使用を求めているのか判断できず、許可してくれないこともあるかもしれません。

そんな時には iOS 6 でアクセス許可を求めるときに独自のメッセージを表示する で紹介した方法を使うことで、標準のメッセージと併せて独自の補足メッセージを追加で表示することができます。

 

プライバシー設定をリセットしたい場合

連絡先を許可するといったプライバシー設定は、いったんユーザーが指定すると、それ以降はアプリを消しても記憶されるようです。

アプリ制作などで、まっさらのまだユーザーが利用許可を選択していない状況に戻したい場合は、iOS 6 のプライバシー設定をリセットする で記したようにして、プライバシー設定をリセットする必要があります。

 

なお、プライバシー設定をリセットした時、プライバシー設定を行っているアプリが起動中であれば、それらは強制終了される様子でした。

 

プログラムを組む上での要所としては…

連絡先へアクセスできないと機能を提供できない場合

たとえばアドレス帳を管理するアプリなど、連絡先を利用できないと重要な機能が提供できないような場合は、ABAddressBookRequestAccessWithCompletion 関数を使って、ユーザーから連絡先の使用許可を明示的に取る必要があります。

このとき、許可の取得は非同期で行われるため、プログラム的には指定した Blocks が呼び出されるまで、使っていいか判らない状態が続くことに注意します。

ただし、ユーザーへの問い合わせはモーダルダイアログで行われるため、ユーザーがそれに答えないまま別の操作を行うことはありません。

そのため、例えば viewDidAppear で利用許可を尋ねたら、View 上に貼ったボタンを押されたときには、ユーザーが既に使ってもいいかの判断をした後とみなしても大丈夫そうです。

 

原則的には、ユーザーによって判断が下されると ABAddressBookRequestAccessWithCompletion で指定した Blocks が呼び出され、このタイミングで、許可か禁止かが判断できるようになります。

その後は、ABAddressBookGetAuthorizationStatus 関数を使えば許可状態をいつでも確認できます。

通常は、この関数の戻り値が kABAuthorizationStatusAuthorized であるかどうかで処理を分ければ十分でしょう。

 

iOS 5 も考慮しつつ、なるべくシンプルに実装したい場合

ただ、これらの関数は iOS 6 から利用できるようになった機能なので、たとえば iOS 5 以上に対応したアプリを作成する場合、許可状態を確認する度ごとに OS のバージョンを確認するのも面倒です。

そんなときは、プライバシー設定をリセットしたり ON/OFF を切り替えたときに該当するアプリが強制終了される特徴を利用して、ABAddressBookRequestAccessWithCompletion を 1 度実行するだけでシンプルに状況を確認することもできると思います。

 

つまり、たとえば次のような感じです。

@implementation EzViewController

{

// 連絡先の使用許可状態を保存するインスタンス変数です。

BOOL grantContacts;

}

 

// viewDidAppear で許可状況を確認します。必要に応じて利用許可をユーザーに求めます。

- (void)viewDidAppear:(BOOL)animated

{

// ここで iOS 6 以上であれば、ユーザーに連絡先の利用許可を求めます。

if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 6.0)

{

// ユーザーに許可を求め、許可状態をインスタンス変数に格納します。(非同期)

ABAddressBookRequestAccessWithCompletion(NULL, ^(bool granted, CFErrorRef error) { grantContacts = granted; });

}

else

{

// iOS 6 以前であれば許可状態とみなします。

grantContacts = YES;

}

}

 

// 連絡先が必要な処理では、インスタンス変数で確認します。

- (IBAction)pushButton:(id)sender

{

// 連絡先の使用許可が下りてるかどうかをインスタンス変数で確認します。

if (grantContacts)

{

}

}

 

こうすることで、iOS のバージョンを確認するのは ABAddressBookRequestAccessWithCompletion を呼び出す 1 度だけで、それ以降はインスタンス変数を判定するだけなので、コードの見通しも良くなると思います。

ABAddressBookRequestAccessWithCompletion はスレッドをブロックしないので、ここでは viewDidAppear で実行しましたが、viewWillAppear で実行しても問題ありません。

 

ここで ABAddressBookRequestAccessWithCompletion は、ユーザーが既に利用許可を判断していた場合でも Blocks を実行するので、必ず 1 度、この中の処理が実行され、つまり許可か不許可かの状態をインスタンス変数に記録することができます。

ちなみにインスタンス変数は最初に 0 で初期化されるため、許可が判断されるまでの間は、grantContacts の値は不許可(NO)と同等です。

 

気になる点としては ABAddressBookRequestAccessWithCompletion で指定した Blocks はメインスレッド以外で実行されるため、同時アクセスによる値の破壊の心配も考えられますが、BOOL 型であれば CPU が 1 回で読み書きできるはずなので問題ないでしょう。

心配であれば、performSelectorOnMainThread を使ってメインスレッドで grantContacts を書き換えるか、"@property (atomic,readwrite) BOOL grantContacts" として定義してプロパティを通して読み書きすることで、原子性 を保証してもいいでしょう。

 

インスタンス変数による許可状況の記憶と ABPeoplePickerNavigationController を併用する際の留意点

ABPeoplePickerNavigationController を併用している場合には、少しばかり注意が必要です。

viewDidAppear などの必ず実行されるところで ABAddressBookRequestAccessWithCompletion を使って判定していれば、そうそう問題にはならないと思いますが、ABPeoplePickerNavigationController が実行されたときにも、許可をユーザーに求める画面が表示されるタイミングになり得ることだけ、念頭に置いておく必要があります。

ABPeoplePickerNavigationController を実行した時、初回に限って連絡先の利用許可をユーザーに求める場合がありますが、ユーザーがどう判断したかを Blocks で拾うことができません。

 

そのため、大域変数で保存している現状と実際の現状とが違ってくる可能性も、まったく有り得ないとは言えません。

もっとも ABAddressBookRequestAccessWithCompletion を、連絡先を必要とするどの操作よりも必ず先に出すようにプログラミングしておけば、その後アプリが実行され続けている中で許可状態が変更されることはないので、ABPeoplePickerNavigationController を併用していても状態が矛盾することはないはずです。

 

連絡先を使えるなら使うで十分なとき

連絡先を取得できるなら使うといった程度の場合は、許可状況を確認しなくても ABAddressBookCreateWithOptions 関数をいきなり使ってアドレス帳を取得することも可能です。

たとえば、入力された電話番号と同じ番号のデータがあればそれを参考として表示する場合などです。

 

ABAddressBookCreateWithOptions 関数は、連絡先の利用許可が下りていないときには NULL を返すので、それを以て許可されていた場合とそうでない場合の処理を切り替えます。

ただし、まだ許可するかどうかの判断がされていない間は空のアドレス帳が取得されるので、NULL 以外が取得できたからといって、アドレス帳が取得できたとは限らないところにだけは注意する必要があります。

[ もどる ]