37

Heyo! Is there a way how when a user taps a button it can add or update a contact into the actual Apple Contacts Book? Some festivals have email responses include a "name card" that the receiver can download and find in their contact book.

HowbeitGirl
  • 591
  • 3
  • 12
  • 21

3 Answers3

79

If doing this in iOS 9 or later, you should use the Contacts framework:

@import Contacts;

You also need to update your Info.plist, adding a NSContactsUsageDescription to explain why your app requires access to contacts.

Then, when you then want to programmatically add the contact, then you can do something like:

CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
if (status == CNAuthorizationStatusDenied || status == CNAuthorizationStatusRestricted) {
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Access to contacts." message:@"This app requires access to contacts because ..." preferredStyle:UIAlertControllerStyleActionSheet];
    [alert addAction:[UIAlertAction actionWithTitle:@"Go to Settings" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil];
    }]];
    [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
    [self presentViewController:alert animated:TRUE completion:nil];
    return;
}

CNContactStore *store = [[CNContactStore alloc] init];

[store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
    if (!granted) {
        dispatch_async(dispatch_get_main_queue(), ^{
            // user didn't grant access;
            // so, again, tell user here why app needs permissions in order  to do it's job;
            // this is dispatched to the main queue because this request could be running on background thread
        });
        return;
    }

    // create contact

    CNMutableContact *contact = [[CNMutableContact alloc] init];
    contact.familyName = @"Doe";
    contact.givenName = @"John";

    CNLabeledValue *homePhone = [CNLabeledValue labeledValueWithLabel:CNLabelHome value:[CNPhoneNumber phoneNumberWithStringValue:@"312-555-1212"]];
    contact.phoneNumbers = @[homePhone];

    CNSaveRequest *request = [[CNSaveRequest alloc] init];
    [request addContact:contact toContainerWithIdentifier:nil];

    // save it

    NSError *saveError;
    if (![store executeSaveRequest:request error:&saveError]) {
        NSLog(@"error = %@", saveError);
    }
}];

Or, even better, if you want to add the contact using the ContactUI framework (giving the user visual confirmation of the contact and let them tailor it as they see fit), you can import both frameworks:

@import Contacts;
@import ContactsUI;

And then:

CNContactStore *store = [[CNContactStore alloc] init];

// create contact

CNMutableContact *contact = [[CNMutableContact alloc] init];
contact.familyName = @"Smith";
contact.givenName = @"Jane";

CNLabeledValue *homePhone = [CNLabeledValue labeledValueWithLabel:CNLabelHome value:[CNPhoneNumber phoneNumberWithStringValue:@"301-555-1212"]];
contact.phoneNumbers = @[homePhone];

CNContactViewController *controller = [CNContactViewController viewControllerForUnknownContact:contact];
controller.contactStore = store;
controller.delegate = self;

[self.navigationController pushViewController:controller animated:TRUE];

My original answer, using the AddressBook and AddressBookUI frameworks for iOS versions before 9, is below. But if only supporting iOS 9 and later, use the Contacts and ContactsUI frameworks as outlined above.

--

If you want to add a contact to the user's address book, you use AddressBook.Framework to create a contact, and then you use the AddressBookUI.Framework to present the user interface to allow the user to add it to their personal address book using ABUnknownPersonViewController. Thus, you can:

  1. Add AddressBook.Framework and AddressBookUI.Framework to your list under Link Binary With Libraries;

  2. Import the .h files:

    #import <AddressBook/AddressBook.h>
    #import <AddressBookUI/AddressBookUI.h>
    
  3. Write the code to create a contact, e.g.:

    // create person record
    
    ABRecordRef person = ABPersonCreate();
    
    // set name and other string values
    
    ABRecordSetValue(person, kABPersonOrganizationProperty, (__bridge CFStringRef) venueName, NULL);
    
    if (venueUrl) {
        ABMutableMultiValueRef urlMultiValue = ABMultiValueCreateMutable(kABMultiStringPropertyType);
        ABMultiValueAddValueAndLabel(urlMultiValue, (__bridge CFStringRef) venueUrl, kABPersonHomePageLabel, NULL);
        ABRecordSetValue(person, kABPersonURLProperty, urlMultiValue, nil);
        CFRelease(urlMultiValue);
    }
    
    if (venueEmail) {
        ABMutableMultiValueRef emailMultiValue = ABMultiValueCreateMutable(kABMultiStringPropertyType);
        ABMultiValueAddValueAndLabel(emailMultiValue, (__bridge CFStringRef) venueEmail, kABWorkLabel, NULL);
        ABRecordSetValue(person, kABPersonEmailProperty, emailMultiValue, nil);
        CFRelease(emailMultiValue);
    }
    
    if (venuePhone) {
        ABMutableMultiValueRef phoneNumberMultiValue = ABMultiValueCreateMutable(kABMultiStringPropertyType);
        NSArray *venuePhoneNumbers = [venuePhone componentsSeparatedByString:@" or "];
        for (NSString *venuePhoneNumberString in venuePhoneNumbers)
            ABMultiValueAddValueAndLabel(phoneNumberMultiValue, (__bridge CFStringRef) venuePhoneNumberString, kABPersonPhoneMainLabel, NULL);
        ABRecordSetValue(person, kABPersonPhoneProperty, phoneNumberMultiValue, nil);
        CFRelease(phoneNumberMultiValue);
    }
    
    // add address
    
    ABMutableMultiValueRef multiAddress = ABMultiValueCreateMutable(kABMultiDictionaryPropertyType);
    NSMutableDictionary *addressDictionary = [[NSMutableDictionary alloc] init];
    
    if (venueAddress1) {
        if (venueAddress2)
            addressDictionary[(NSString *) kABPersonAddressStreetKey] = [NSString stringWithFormat:@"%@\n%@", venueAddress1, venueAddress2];
        else
            addressDictionary[(NSString *) kABPersonAddressStreetKey] = venueAddress1;
    }
    if (venueCity)
        addressDictionary[(NSString *)kABPersonAddressCityKey] = venueCity;
    if (venueState)
        addressDictionary[(NSString *)kABPersonAddressStateKey] = venueState;
    if (venueZip)
        addressDictionary[(NSString *)kABPersonAddressZIPKey] = venueZip;
    if (venueCountry)
        addressDictionary[(NSString *)kABPersonAddressCountryKey] = venueCountry;
    
    ABMultiValueAddValueAndLabel(multiAddress, (__bridge CFDictionaryRef) addressDictionary, kABWorkLabel, NULL);
    ABRecordSetValue(person, kABPersonAddressProperty, multiAddress, NULL);
    CFRelease(multiAddress);
    
    // let's show view controller
    
    ABUnknownPersonViewController *controller = [[ABUnknownPersonViewController alloc] init];
    
    controller.displayedPerson = person;
    controller.allowsAddingToAddressBook = YES;
    
    // current view must have a navigation controller
    
    [self.navigationController pushViewController:controller animated:YES];
    
    CFRelease(person);
    

See the ABUnknownPersonViewController Class Reference or the Prompting the User to Create a New Person Record from Existing Data section of the Address Book Programming Guide.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • why you put "status == CNAuthorizationStatusDenied || status == CNAuthorizationStatusDenied"? – fechidal89 Apr 16 '16 at 04:58
  • typo. should be denied || restricted. see revised answer. – Rob Apr 16 '16 at 05:00
  • Can you please help me learn why am I unable to use custom identifier when creating labeled value like following: `let app = CNLabeledValue(label: "App", value: appURL); contact.urlAddresses.append(app)` Please note that I am able to use it if the iPhone is only using iCloud for saving contacts. But if I am using Google contacts, it doesn't work. – Kushal Ashok Mar 28 '17 at 21:59
  • Getting the logs: `_BSMachError: port e317; (os/kern) invalid capability (0x14) "Unable to insert COPY_SEND" 2017-03-29 06:05:56.068572 doko[26442:3530002] [Common] _BSMachError: port e317; (os/kern) invalid name (0xf) "Unable to deallocate send right"` – Kushal Ashok Mar 28 '17 at 22:07
  • I can't comment on Google's constraints here and I don't use Google contacts, so I can't easily verify. It sounds like this would be worthy of being posted as its own question on Stack Overflow (assuming that you've searched for that particular error message and found nothing). – Rob Mar 28 '17 at 22:34
  • @Rob great answer and this worked fine for me 2 years ago. However, as I've updated the app, I decided to refactor the `ContactUI` portion to it's own method. However, even w/o refactoring I'm getting a ton of error saying `This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes.` The `CNContactViewController` take about 6 seconds to appear and the back button does not work. Do I have to place all the code within the `dispatch_async(dispatch_get_main_queue(), ^{...});`? – RoLYroLLs Apr 16 '18 at 18:34
  • Yes, all code that presents/dismisses/updates the `ContactsUI`, like all UI code, must be done from the main thread. You might want to edit your scheme's diagnostics to not only turn on the main thread checker (probably on by default), but also "Pause on issues", so you can better identify offending code. – Rob Apr 16 '18 at 20:44
4

To present default contact controller

Step 1: Add ContactUi.framework into project and import

    #import <Contacts/Contacts.h>
    #import <ContactsUI/ContactsUI.h>

Step2: Add this code

 -(void)showAddContactController{
        //Pass nil to show default contact adding screen
        CNContactViewController *addContactVC = [CNContactViewController viewControllerForNewContact:nil];
        addContactVC.delegate=self;
        UINavigationController *navController   = [[UINavigationController alloc] initWithRootViewController:addContactVC];
        [viewController presentViewController:navController animated:NO completion:nil];
    }

Step3:

For getting call back when pressing DONE or CANCEL, Add <CNContactViewControllerDelegate>and implement the delegate method.

- (void)contactViewController:(CNContactViewController *)viewController didCompleteWithContact:(nullable CNContact *)contact{
    //You will get the callback here
}
Hari R Krishna
  • 782
  • 6
  • 20
0
@import Contacts;
-(void)addToContactList
 {
CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];

if (status == CNAuthorizationStatusDenied || status == CNAuthorizationStatusRestricted) {

    UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:@"This app previously was refused permissions to contacts; Please go to settings and grant permission to this app so it can add the desired contact" preferredStyle:UIAlertControllerStyleAlert];

    [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]];

    [self presentViewController:alert animated:TRUE completion:nil];

    return;

}

CNContactStore *store = [[CNContactStore alloc] init];

[store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {

    if (!granted) {

        dispatch_async(dispatch_get_main_queue(), ^{

            // user didn't grant access;

            // so, again, tell user here why app needs permissions in order  to do it's job;

            // this is dispatched to the main queue because this request could be running on background thread

        });

        return;

    }



    // create contact



    CNMutableContact *contact = [[CNMutableContact alloc] init];

    contact.givenName = @"Test";

    contact.familyName = @"User";

    CNLabeledValue *homePhone = [CNLabeledValue labeledValueWithLabel:CNLabelHome value:[CNPhoneNumber phoneNumberWithStringValue:@"91012-555-1212"]];

    contact.phoneNumbers = @[homePhone];



    CNSaveRequest *request = [[CNSaveRequest alloc] init];

    [request addContact:contact toContainerWithIdentifier:nil];



    // save it



    NSError *saveError;

    if (![store executeSaveRequest:request error:&saveError]) {

        NSLog(@"error = %@", saveError);

    }

}];

 }
user2931321
  • 468
  • 1
  • 7
  • 28
Deepak Kumar
  • 199
  • 1
  • 6