How to Select a Contact with Abpeoplepickernavigationcontroller in Swift

How to select a contact with ABPeoplePickerNavigationController in Swift?

A couple of thoughts:

  1. Have you set the peoplePickerDelegate property of the people picker controller? If you don't do that, it won't know to try to call these methods in your class. Thus:

    people.peoplePickerDelegate = self
    presentViewController(people, animated: true, completion: nil)
  2. Your example method is referencing people when you call ABRecordCopyValue. That's your picker controller. I assume you meant to reference person, the ABRecordRef! that was passed as a parameter.

    You might also want to make sure you actually have an email address before trying to access it. You can use ABMultiValueGetCount.

    I also think you can also eliminate that fromOpaque/toOpaque dance.

    This yields:

    func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController, didSelectPerson person: ABRecord) {
    let emails: ABMultiValueRef = ABRecordCopyValue(person, kABPersonEmailProperty).takeRetainedValue()
    if ABMultiValueGetCount(emails) > 0 {
    let index = 0 as CFIndex
    let emailAddress = ABMultiValueCopyValueAtIndex(emails, index).takeRetainedValue() as! String

    print(emailAddress)
    } else {
    print("No email address")
    }
    }
  3. If you need to support iOS 7, too, use:

    func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController, shouldContinueAfterSelectingPerson person: ABRecord, property: ABPropertyID, identifier: ABMultiValueIdentifier) -> Bool {
    let multiValue: ABMultiValueRef = ABRecordCopyValue(person, property).takeRetainedValue()
    let index = ABMultiValueGetIndexForIdentifier(multiValue, identifier)
    let email = ABMultiValueCopyValueAtIndex(multiValue, index).takeRetainedValue() as! String

    print("email = \(email)")

    peoplePicker.dismissViewControllerAnimated(true, completion: nil)

    return false
    }
  4. You might, though, rather than assuming the user only wanted the first email address, instead, let them click through and pick one of the possible multiple email addresses the contact had. So, first, you might want to eliminate some of the "noise", by telling the picker that you only want to see email addresses:

    people.peoplePickerDelegate = self
    people.displayedProperties = [NSNumber(int: kABPersonEmailProperty)]
    presentViewController(people, animated: true, completion: nil)

    And then, remove the prior method we've been discussing, and instead implement:

    func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!, didSelectPerson person: ABRecordRef!, property: ABPropertyID, identifier: ABMultiValueIdentifier) {
    let multiValue: ABMultiValueRef = ABRecordCopyValue(person, property).takeRetainedValue()
    let index = ABMultiValueGetIndexForIdentifier(multiValue, identifier)
    let email = ABMultiValueCopyValueAtIndex(multiValue, index).takeRetainedValue() as String

    println("email = \(email)")
    }

    And to support iOS 7,0, too, you'd add:

    func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController, shouldContinueAfterSelectingPerson person: ABRecord, property: ABPropertyID, identifier: ABMultiValueIdentifier) -> Bool {
    let multiValue: ABMultiValueRef = ABRecordCopyValue(person, property).takeRetainedValue()
    let index = ABMultiValueGetIndexForIdentifier(multiValue, identifier)
    let email = ABMultiValueCopyValueAtIndex(multiValue, index).takeRetainedValue() as! String

    print("email = \(email)")

    peoplePicker.dismissViewControllerAnimated(true, completion: nil)

    return false
    }
  5. By the way, iOS 8 offers a feature to control whether a contact is enabled or not. Since you're supporting iOS 7 and 8, you'd want to employ that conditionally, such as:

    if people.respondsToSelector(Selector("predicateForEnablingPerson")) {
    people.predicateForEnablingPerson = NSPredicate(format: "emailAddresses.@count > 0")
    }

    This gives the user visual indication whether there is even an email address for the individual, and prevents them from selecting entry without email address.

Obviously, if using iOS 9 and later, you should retire all of this and use the ContactsUI framework, which simplifies the code further.

how to select multiple contacts with ABPeoplePickerNavigationController in Swift

It's not supported by the built in controller. Try this: https://github.com/tristanhimmelman/THContactPicker

How to tell ABPeoplePickerNavigationController to list only contacts that have an email address?

I do not believe there is a way to get iOS to do this filtering. I do it in code too. Note that you need to look for all kinds of email addresses - you have to iterate through the dictionary that you can get. Working with this is a PITA for sure - I've done it before - and you have to be careful to not have memory leaks.

What I do is as you suggest - iterate through all contact myself, then I pop a view with a table and let then select the names of the people they want. I keep an association around so I know what address is associated with what name, then use the system email framework and then populate the send-to addresses.

Need to create a custom view like ABPeoplePickerNavigationController

I have the exact same thing in the app i'm currently building and we also got our inspiration from apps like Whatsapp when it came to contacts.

I used a simple tableview with custom cells for visuals, with simply name & picture.

My process was the following :

  • Create a contact object in coredata (or another persistent way of keeping your data)
  • Through the ABAddressbook framework you can browse all your contacts and transform them in your new Contact objets. Keep a reference of your ABPerson in your Contact object, this will allow you to find-and-update your Contacts later just using references. If you don't do that you will have to browse to all your ABPersons every time you want to update your Contacts.
    You could use the ABPersons directly but it would just be really painful to code.

  • Once you've extracted all your contacts, make sure to save your context if you use core data, or store them in .sqlite.

  • You can then simply extract them in an array of Contacts and display those in a custom cell of your choosing.

This appcoda tutorial is a decent custom cell for tableview tutorial. You can find a thousand more just by googling " tableview custom cell ios" and finding different things that you might like. In the end, you'll just have a cell with a label and a picture, you COULD use the simple UITableViewCell which I used for another tableview of "contact" type.

Keeping that contact list up to date (getting the right numbers, pictures, names, etc) and making sure they exist before updating, checking if a contact has been deleted, added, etc. All that has to be done in order for your list to be accurate, and it's a pretty long/annoying process.

I could share my Contact class but it includes a lot of irrelevant code for you which might confuse you because :
- I'm also checking if those contacts are already users of my app to move them in specific sections of my tableview
- I split my tableview in 27 sections (users, then alphabet letters).

Also, and I'll stop it with that last piece of general programming advice : It would be a good idea to write down first exactly what you need and what you'll need, get all the possibilities on paper, etc. I bumped into a lot of simple problems that it took me a while to resolve, either because I didn't plan properly or because it was hidden.

For example :

  • Some of your contacts don't have names, at all.
  • Some of your contacts have a name in the "Title" field (where you write Doctor, or Mister)
  • Some of your contacts don't have phone numbers (if you're using phone numbers as identifiers)
  • Some of your contacts have international formatting and some not (+32 46556555 or 0032 46556555)
  • Some of your contacts have pictures taken with camera, some others from Gmail, which have different formats
  • You might have duplicate contacts (same name, same everything) due to poor sync from the user
  • You need to make sure the firstname/lastname is in the right section, case sensitive coding can cause trouble
  • You need to find a solution for a contact that start with a smiley or non alphanumeric characters
  • Your users will want an index-list on the side
  • Of course you'll need to add a search bar because, some people have way more than 1000 contacts.
  • Many more to come, I guarantee that.

Because this is very app-specific i'm not gonna go over every problem that I had and what I did for it, but you get the idea :) Feel free to ask any very specific questions though and I might already have a very specific solution, since I pretty much had to copy whatsapp's contacts from scratch and, hell, I made it. (I actually got the exact same as Anonymess and iOS)

EDIT : Here are some methods of my ABPerson extracting methods ; the ContactDAO mostly interact with my persistent model (CoreData) and I believe their names are clear enough for you to understand what's happening. I'm kind of happy with the comments and variablenames so you should be to read that without too much trouble.

Here comes a massive block of code.

- (NSMutableArray *)getAllRecordsWithPeople:(CFArrayRef)allPeople andAddressBook:(ABAddressBookRef)addressbook{
NSMutableArray *newRecords = [[NSMutableArray alloc]init];
CFIndex nPeople = ABAddressBookGetPersonCount(addressbook);

for (int i=0;i < nPeople;i++){

ABRecordRef ref = CFArrayGetValueAtIndex(allPeople,i);
ABRecordID refId = ABRecordGetRecordID(ref);
NSNumber *recId = [NSNumber numberWithInt:refId];
[newRecords addObject:recId];
}

return newRecords;
}

- (void)getValidContactsFromAddressBookWithCompletionBlock:(void (^)(NSError *error))completion{

ABAddressBookRef addressBook = ABAddressBookCreateWithOptions(NULL, nil);

__block BOOL accessGranted = NO;

if (&ABAddressBookRequestAccessWithCompletion != NULL) {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
accessGranted = granted;
dispatch_semaphore_signal(semaphore);
});

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}

if (accessGranted) {

NSMutableArray *newRecords = [[NSMutableArray alloc]init];
NSMutableArray *updatedRecords = [[NSMutableArray alloc]init];
NSMutableArray *unchangedRecords = [[NSMutableArray alloc]init];

CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeople(addressBook);
CFIndex nPeople = ABAddressBookGetPersonCount(addressBook);

//Checking the last time we updated
NSTimeInterval lastSyncTime;
if ([[NSUserDefaults standardUserDefaults]objectForKey:@"LastSyncTime"] == nil){
//This is the first time we update.
lastSyncTime = 0;
}else{
//Setting the last update in variable
lastSyncTime = [[[NSUserDefaults standardUserDefaults]objectForKey:@"LastSyncTime"]doubleValue];
}

if (lastSyncTime == 0){
//We have to insert everyone, this is the first time we do this.
newRecords = [self getAllRecordsWithPeople:allPeople andAddressBook:addressBook];
}else{
//We have to manually compare everyone to see if something has changed.

for (int i=0;i < nPeople;i++) {

ABRecordRef ref = CFArrayGetValueAtIndex(allPeople,i);
ABRecordID refId = ABRecordGetRecordID(ref);
NSNumber *recId = @(refId);

CFDateRef recordCreation = ABRecordCopyValue(ref, kABPersonCreationDateProperty);
NSDate *recCreDate = (__bridge NSDate *)(recordCreation);
NSTimeInterval creDateInterval = [recCreDate timeIntervalSince1970];

if(creDateInterval > lastSyncTime){
//This object was created after my lastSync, this must be a new record
[newRecords addObject:recId];

}else{

//Checking the last update of the given record
CFDateRef recordUpdate = ABRecordCopyValue(ref, kABPersonModificationDateProperty);
NSDate *recUpDate = (__bridge NSDate*)(recordUpdate);

if ([recUpDate timeIntervalSince1970] > lastSyncTime){
//The record was somehow updated since last time, we'll update it
[updatedRecords addObject:recId];

}else{
//The record wasn't updated nor created, it is therefore unchanged.
//We still need to keep it in a separate array to compare deleted contacts
[unchangedRecords addObject:recId];
}
}

}
if(allPeople)
CFRelease(allPeople);
}

[self manageNewContacts:newRecords updatedContacts:updatedRecords andUnchangedContacts:unchangedRecords inAddressBook:addressBook andBlock:^(NSError *error) {
completion(error);
}];
}else{
NSError *error = [NSError errorWithDomain:@"ABAccess access forbidden" code:403 userInfo:nil];
completion(error);
}
}

- (void)manageNewContacts:(NSMutableArray*)newRecords updatedContacts:(NSMutableArray*)updatedRecords andUnchangedContacts:(NSMutableArray*)unchangedRecords inAddressBook:(ABAddressBookRef)addressbook andBlock:(void (^)(NSError *error))completion{

AppDelegate *app = [UIApplication sharedApplication].delegate;
NSManagedObjectContext *context = app.managedObjectContext;

//Getting all the CoreData contacts IDs to have something to compare
NSArray *coreDataContactsIds = [ContactDAO getAllContactIdsInManagedObjectContext:context];

for (NSDictionary *rec in coreDataContactsIds){
NSNumber *recId = rec[@"record_id"];
if (![unchangedRecords containsObject:recId]){
//The unchanged record doesn't exist locally

if (![updatedRecords containsObject:recId]){
//The updated record doesn't exist locally

if (![newRecords containsObject:recId]){
//The new record doesn't exist locally
//That means the ongoing record has been deleted from the addressbook,
//we also have to delete it locally

[ContactDAO deleteContactWithID:recId inManagedObjectContext:context];

}

}
}

}

for (NSNumber *recId in updatedRecords){
ABRecordID recordID = (ABRecordID)recId.intValue;
ABRecordRef person = ABAddressBookGetPersonWithRecordID(addressbook, recordID);

NSDictionary *personDict = [self getPersonDictionaryFromABRecordRef:person];
if (personDict){
[ContactDAO updateContactWithFirstName:personDict[@"firstName"] lastName:personDict[@"lastName"] compositeName:personDict[@"compositeName"] picture:personDict[@"picture"] phoneNumbers:personDict[@"phoneNumbers"] recordID:recId inManagedObjectContext:context];
}
}

for (NSNumber *recId in newRecords){
ABRecordID recordID = (ABRecordID)recId.intValue;
ABRecordRef person = ABAddressBookGetPersonWithRecordID(addressbook, recordID);

NSDictionary *personDict = [self getPersonDictionaryFromABRecordRef:person];
if (personDict){
[ContactDAO createContactWithFirstName:personDict[@"firstName"] lastName:personDict[@"lastName"] compositeName:personDict[@"compositeName"] picture:personDict[@"picture"] phoneNumbers:personDict[@"phoneNumbers"] recordID:recId inManagedObjectContext:context];
}

}

NSError *dbError;
[context save:&dbError];

NSTimeInterval lastSyncTime = [[NSDate date]timeIntervalSince1970];

[[NSUserDefaults standardUserDefaults]setObject:@(lastSyncTime) forKey:@"LastSyncTime"];
completion(dbError);
}

- (NSDictionary*)getPersonDictionaryFromABRecordRef:(ABRecordRef)person{

//Get name
NSString * firstName, *lastName;
firstName = CFBridgingRelease(ABRecordCopyValue(person, kABPersonFirstNameProperty));
lastName = CFBridgingRelease(ABRecordCopyValue(person, kABPersonLastNameProperty));

firstName = (firstName == nil) ? @"" : firstName;
lastName = (lastName == nil) ? @"" : lastName;
NSString *compositeName;

if ([firstName isEqualToString:@""] && [lastName isEqualToString:@""]){
return nil;
}

if ([lastName isEqualToString:@""]){
compositeName = [NSString stringWithFormat:@"%@", firstName];
}
if ([firstName isEqualToString:@""]){
compositeName = [NSString stringWithFormat:@"%@", lastName];
}
if (![lastName isEqualToString:@""] && ![firstName isEqualToString:@""]){
compositeName = [NSString stringWithFormat:@"%@ %@", firstName, lastName];
}

//Get picture
CFDataRef imageData = ABPersonCopyImageData(person);
NSData *data = CFBridgingRelease(imageData);

//Get phone numbers
NSMutableSet *phoneNumbers = [[NSMutableSet alloc]init];

ABMultiValueRef phones = ABRecordCopyValue(person, kABPersonPhoneProperty);
for(CFIndex i = 0; i < ABMultiValueGetCount(phones); i++) {

CFStringRef str = ABMultiValueCopyValueAtIndex(phones, i);
NSString *num = CFBridgingRelease(str);
[phoneNumbers addObject:num];
/*if(str)
CFRelease(str);*/
}

//Save it in dictionary
NSDictionary *personDict = [[NSDictionary alloc]initWithObjectsAndKeys:firstName, @"firstName",lastName , @"lastName",phoneNumbers,@"phoneNumbers", compositeName, @"compositeName", data, @"picture", nil];

//Release everything.
if(phones)
CFRelease(phones);

return personDict;

}

When it comes to indexes, this tutorial should do fine.

Getting data like name or image of the contact out of ABRecord using ABPeoplePickerNavigationControllerDelegate

After a long search I found an answer that worked for me:

    func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!, didSelectPerson person: ABRecord!) {

var firstName: String?
if ABRecordCopyValue(person, kABPersonFirstNameProperty) != nil{
firstName = ABRecordCopyValue(person, kABPersonFirstNameProperty).takeRetainedValue() as? String
}
var lastName: String?
if ABRecordCopyValue(person, kABPersonLastNameProperty){
lastName = ABRecordCopyValue(person, kABPersonLastNameProperty).takeRetainedValue() as? String
}

var pImage: UIImage?
if person != nil && ABPersonHasImageData(person){
pImage = UIImage(data: ABPersonCopyImageData(person).takeRetainedValue())

}

}

How to extract name from a contact in iOS?

Use takeUnretainedValue() and takeRetainedValue() to get CFString object and cast it to NSString

func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!, didSelectPerson person: ABRecord!) {
let nameCFString : CFString = ABRecordCopyCompositeName(person).takeRetainedValue()
let name : NSString = nameCFString as NSString
}

Swift: Prevent ABPeoplePickerNavigationController from Closing

I'm not sure there is a way to remove the cancel button, or prevent it from working, but you could respond to the func peoplePickerNavigationControllerDidCancel(_ peoplePicker: ABPeoplePickerNavigationController!) delegate to handle the case where the cancel button is pressed.

I would recommend rather than immediately reopening the picker, you open an alert telling the user they need to pick someone, then open it back up from there. It may feel broken if they cancel and it immediately opens back up.

Reference

edit:

Presenting an alert or the picker probably needs to be delayed long enough for the previous picker to close. dispatch_after

How to use CNContactPickerViewController to get address AND contact?

Note that there is no CNContact object returned. I get the
CNPostalAddress in the contactProperty, but I don't receive the
record of the owning contact.

The CNContact object can be retrieved from the contact property of the CNContactProperty, so…

However, when using CNContactPickerViewController, only this
relevant delegate function is available:

func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty)

… implementing this delegate method would allow you to do what you want. But you want to make sure you're not implementing other delegate methods, such as:

func contactPicker(CNContactPickerViewController, didSelect: CNContact)

which will dismiss the picker the moment a contact (and not its property) is selected.



Related Topics



Leave a reply



Submit