iosxcodecontactswhatsappabpeoplepickerview

Need to create a custom view like ABPeoplePickerNavigationController


I want to create a custom view that is just like contacts in iPhone. Is there any tutorial available to make a custom view just like ABPeoplePickerNavigationController?

Please note that I do not want to open the default people picker controller provided by AddressBookUI framework. Also, I do want to bind this navigation controller into my main view.

For reference that what I exactly want, You can refer contacts tab of Whatsapp on iOS device.

EDIT: I already got a contact list and displayed the first and last name of person in a table view. Now, I want to create an index for alphabets from A-Z and on tapping of that index, the table view should scroll to that contacts. Also, How can I implement search functionality to find user by his / her first or last name?


Solution

  • 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 :

    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 :

    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.