1

I am coaching a team of 7th and 8th graders working on developing a small app that displays selections from the address book in a large format. You can check out their general project at callmeapp.org. We are stuck on how to prompt the user for permission so we can access the address book. Basically the user prompt is not displaying correctly. FYI, we already know about clearing permissions through Settings>General>Reset>Reset Location & Privacy.

We are using xCode 4.6 and testing on an iPhone MC918LL/A running version 6.1.2.

We started with code from DavidPhilipOster's response in this thread in our appdelegate.m didfinishlaunchingwithoptions method: How do I correctly use ABAddressBookCreateWithOptions method in iOS 6?. We made a few edits to clear errors we were getting.

Right now the app launches to a black screen and sits there for at least 24 seconds at which point the app appears to close revealing the permission prompt underneath. Accepting sends us to the desktop. When we reopen the app it work as if the permission has been cleared. Alternatively if we hit the home button (square one on the phone) while the screen is black it closes to show the permission prompt as above. The permisison window should display after a very short delay and then leave us in the app when the user gives permission.

We stuck in some NSLog points to see what is happening. I have left them in the code in case that helps. It will show points 1, 2, 5 and then wait. After clearing the prompt 3, 7, and 4 enter even though the phone shows the desktop.

Any help or tips would be greatly appreciated.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSLog(@"Point 1");
    ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL,NULL);

    __block BOOL accessGranted = NO;

    if (ABAddressBookRequestAccessWithCompletion != NULL) { // we're on iOS 6
        NSLog(@"Point 2");
        dispatch_semaphore_t sema = dispatch_semaphore_create(0);

        ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
            NSLog(@"Point 3");
            accessGranted = granted;
            dispatch_semaphore_signal(sema);
            NSLog(@"Point 4");
        });
        NSLog(@"Point 5");

        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        dispatch_release(sema);
    } else { // we're on iOS 5 or older
        NSLog(@"Point 6");

        accessGranted = YES;

    } 
    NSLog(@"Point 7");

    return YES;
}
Community
  • 1
  • 1
EricPerson
  • 11
  • 1
  • 3

1 Answers1

10

The main issue here is that you are doing this in application:didFinishLaunchingWithOptions: . In short, this needs to be moved into another place. iOS places restrictions on what happens in an application startup process - as well as how long it can take. For a simple application you can move this into your main View Controller and check for this before you display any results to the end user.

Currently, because you are using a semaphore in this method, it is blocking the function from returning. iOS has a hard limit on how long it will wait - and then it kills the app. In short, the popup keeps it open - but when you press OK - the app is killed because the application:didFinishLaunchingWithOptions: method didn't finish executing in time.

In addition, I wouldn't recommend the semaphore approach here at all. There are better ways to address this (see below). The code below was just an example.

- (void)setupViewWithContactsData
{
    // Do Something
}

- (void)setupViewWithoutContactsData
{
    // Do Something because Contacts Access has been Denied or Error Occurred
}

- (void)viewDidLoad
{
    [self checkForAddressBookAccess];
}

- (void)checkForAddressBookAccess
{
    if (ABAddressBookRequestAccessWithCompletion == NULL)
    {
        // iOS5 or Below
        [self setupViewWithContactsData];
        return;
    }
    else
    {
        // iOS6
        if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined)
        {
            ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error) {
          if(error)
                  {
                      [self setupViewWithoutContactsData];
                  }
                  else
                  {
                      [self setupViewWithContactsData];
                  }
             });
         }
         else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) 
         {
             [self setupViewWithContactsData];
         }
         else 
         {
             [self setupViewWithoutContactsData];
         }
    }
}
dtuckernet
  • 7,817
  • 5
  • 39
  • 54
  • Thank you for answering so quickly. I tried moving it to the DataViewController didLoad method and had the same trouble. FYI, we based our application on the page view app template. – EricPerson Mar 14 '13 at 19:40
  • First of all - the semaphore approach is not a good one in this case. It is better to use an async approach as opposed to blocking the main thread. Take a look at this answer: http://stackoverflow.com/questions/12648244/programmatically-request-access-to-contacts-in-ios-6 – dtuckernet Mar 14 '13 at 20:17
  • Got it to work. Thank you for the help. The secret was putting it after the [super viewDidLoad] line. – EricPerson Mar 19 '13 at 05:27
  • You are testing "error" to determine whether access was allowed by the user- surely this test should be on "granted"? – Peter Johnson Jul 12 '13 at 10:49