UILocalizedIndexedCollation UITableView Indexed List Sort - ios

I'm creating a UITableView with a list of the users contacts. I've successfully imported the list of contacts and am storing them within an array as a dictionary containing each contact's forename, last name & ID reference. I'm trying to implement the UITableView's indexed list function.
I'm hitting problems when trying to partition the array into the different sections in the table's index UILocalizedIndexedCollation. Specifically by the selector. I want to be able to choose whether to sort the names by the forename or surname.
- (void)getContacts
{
// Do any additional setup after loading the view, typically from a nib.
_addressBook = ABAddressBookCreateWithOptions(NULL, NULL);
ABAddressBookRequestAccessWithCompletion(_addressBook, ^(bool granted, CFErrorRef error) {
NSLog(#"GRANTED");
});
NSArray *rawContactData = (__bridge_transfer NSArray *)ABAddressBookCopyArrayOfAllPeople(_addressBook);
NSMutableArray *newContactList = [[NSMutableArray alloc] init];
for (int i = 0; i < [rawContactData count]; i++) {
ABRecordRef contact = (__bridge ABRecordRef)rawContactData[i];
NSDictionary *contactData = #{ #"name" : (__bridge_transfer NSString *)ABRecordCopyValue(contact, kABPersonFirstNameProperty),
#"surname" : (__bridge_transfer NSString *)ABRecordCopyValue(contact, kABPersonLastNameProperty),
#"recordID" : [NSNumber numberWithInt:ABRecordGetRecordID(contact)]};
[newContactList addObject:contactData];
}
_tableData = [NSMutableArray arrayWithArray:[self partitionObjects:newContactList collationStringSelector:#selector(name)]];
}
-(NSArray *)partitionObjects:(NSArray *)array collationStringSelector:(SEL)selector
{
UILocalizedIndexedCollation *collation = [UILocalizedIndexedCollation currentCollation];
NSInteger sectionCount = [[collation sectionTitles] count]; //section count is take from sectionTitles and not sectionIndexTitles
NSMutableArray *unsortedSections = [NSMutableArray arrayWithCapacity:sectionCount];
//create an array to hold the data for each section
for(int i = 0; i < sectionCount; i++)
{
[unsortedSections addObject:[NSMutableArray array]];
}
//put each object into a section
for (NSMutableDictionary *object in array) {
NSInteger index = [collation sectionForObject:object collationStringSelector:selector];
[[unsortedSections objectAtIndex:index] addObject:object];
}
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
//sort each section
for (NSMutableArray *section in unsortedSections)
{
[sections addObject:[collation sortedArrayFromArray:section collationStringSelector:selector]];
}
return sections;
}

Selector is sent to the Object which you are sorting in array. In your case your object is type Dictionary which does not implement selector "name" or surname etc. You should create a simple class instead of using dictionary. and just declare properties to your liking
#interface Contact : NSObject
#property (strong,nonatomic) NSString *fname;
#property (strong,nonatomic) NSString *lname;
#property (strong,nonatomic) NSNumber *rerordId;
#end
You can then use this object instead of dictionary you are using.
Contact *contact= [[Contact alloc]init];
contact.fname = (__bridge_transfer NSString *)
ABRecordCopyValue(contact, kABPersonFirstNameProperty);
contact.lname = (__bridge_transfer NSString *)
ABRecordCopyValue(contact, kABPersonLastNameProperty);
contact.recrordId = [NSNumber numberWithInt:ABRecordGetRecordID(contact)].stringValue;
[newContactList addObject:contact];
Then you can use any name in or attribute selector. Just like in your example you will write the function call sorting with fname for example like this
_tableData = [NSMutableArray arrayWithArray:
[self partitionObjects:newContactList
collationStringSelector:#selector(fname)]];

Related

Split a single NSMutableArray into two so that I can set each in each of the section in UITableView

I want to use multiple sections feature UITableView. I have a single NSMutableArray which consists of data in dictionary format. In that dictionary there is a key which has values as '0' and '1'.
I want to create two separate NSMutableArray out of that so that I can assign them accordingly in different sections.
For example :
if (indexPath.section==0) {
NSDictionary *Data = [roomList1 objectAtIndex:indexPath.row];
} else {
NSDictionary *Data = [roomList2 objectAtIndex:indexPath.row];
}
Assuming the value in your dictionary is always set you could do something like:
NSMutableArray *firstArray = [NSMutableArray new];
NSMutableArray *secondArray = [NSMutableArray new];
for (NSDictionary *dictionary in yourArray) {
if ([dictionary objectForKey:#"yourValue"] isEqualToString: #"0") {
[firstArray addObject:dictionary];
} else if ([dictionary objectForKey:#"yourValue"]isEqualToString: #"1") {
[secondArray addObject:dictionary];
}
}
You can use this
- (NSDictionary *)groupObjectsInArray:(NSArray *)array byKey:(id <NSCopying> (^)(id item))keyForItemBlock
{
NSMutableDictionary *groupedItems = [NSMutableDictionary new];
for (id item in array) {
id <NSCopying> key = keyForItemBlock(item);
NSParameterAssert(key);
NSMutableArray *arrayForKey = groupedItems[key];
if (arrayForKey == nil) {
arrayForKey = [NSMutableArray new];
groupedItems[key] = arrayForKey;
}
[arrayForKey addObject:item];
}
return groupedItems;
}
Ref: Split NSArray into sub-arrays based on NSDictionary key values
Use like this
NSMutableArray * roomList1 = [[NSMutableArray alloc] init];
NSMutableArray * roomList2 = [[NSMutableArray alloc] init];
for(int i = 0;i< YourWholeArray.count;i++)
{
if([[[YourWholeArray objectAtIndex:i] valueForKey:#"YourKeyValueFor0or1"] isEqualToString "0"])
{
[roomList1 addObject:[YourWholeArray objectAtIndex:i]];
}
else if([[YourWholeArray objectAtIndex:i] valueForKey:#"YourKeyValueFor0or1"] isEqualToString "1"])
{
[roomList2 addObject:[YourWholeArray objectAtIndex:i]];
}
}
NSMutableArray *roomList1 = [[NSMutableArray alloc] init];
NSMutableArray *roomList2 = [[NSMutableArray alloc] init];
for (NSString *key in [myDictionary allKeys]) {
if (myDictionary[key] isEqualToString: #"0") {
[roomList1 addObject: myDictionary[key]];
} else {
[roomList2 addObject: myDictionary[key]];
}
}
try this way :-
NSMutableArray *getdata=[[NSMutableArray alloc]init];
getdata=[results objectForKey:#"0"];
NSMutableArray *getdata2=[[NSMutableArray alloc]init];
getdata2=[results objectForKey:#"1"];
use this two array and populate the section with now of rows at indexpathrow
define number of section 2
if (section==0){
getdata
}
else{
getdata2
}

What is the best way to convert an array with NSStrings to NSDecimalNumber?

I have an NSArray with 10 NSStrings inside:
myArray (
#"21.32",
#"658.47",
#"87.32"...
)
What's the best way to convert all the strings to an NSDecimalNumber?
A very simple Category on NSArray will allow you to use map as seen in other languages
#interface NSArray (Functional)
-(NSArray *)map:(id (^) (id element))mapBlock;
#end
#implementation NSArray (Functional)
-(NSArray *)map:(id (^)(id))mapBlock
{
NSMutableArray *array = [#[] mutableCopy];
for (id element in self) {
[array addObject:mapBlock(element)];
}
return [array copy];
}
#end
Now you can use -map: in your case like
NSArray *array = #[#"21.32",
#"658.47",
#"87.32"];
array = [array map:^id(NSString *element) {
return [NSDecimalNumber decimalNumberWithString:element];
}];
A NSMutableArray in-place variant could be
#interface NSMutableArray (Functional)
-(void)map:(id (^) (id element))mapBlock;
#end
#implementation NSMutableArray (Functional)
-(void)map:(id (^)(id))mapBlock
{
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
self[idx] = mapBlock(obj);
}];
}
#end
NSMutableArray *array = [#[#"21.32",
#"658.47",
#"87.32"] mutableCopy];
[array map:^id(NSString *element) {
return [NSDecimalNumber decimalNumberWithString:element];
}];
It would be quite simple with a for loop:
myArray (
#"21.32",
#"658.47",
#"87.32"...
)
NSMutableArray *numberArray = [NSMutableArray arrayWithCapacity: myArray.count];
for (aString in myArray)
{
NSDecimalNumber *aNumber = [NSDecimalNumber decimalNumberWithString: aString];
[numberArray addObject: aNumber];
}

UITableview always loads the last object for all cells

I am trying to retrieve all the contacts from the AddressBook and store the following details in a Mutable array.
The properties are
#property (nonatomic, assign) ABAddressBookRef addressBook;
#property (nonatomic, strong) NSMutableArray *contactList;
#property (nonatomic, strong) IBOutlet UITableView *contactsTableView;
Method to retrieve all contacts
- (void)getAllContacts {
//line moved inside For loop as per Amar's answer.
//NSMutableDictionary *personModel = [[NSMutableDictionary alloc]initWithCapacity:0];
self.addressBook = ABAddressBookCreateWithOptions(NULL, NULL); //iOS 6 and above
CFArrayRef cList = ABAddressBookCopyArrayOfAllPeopleInSourceWithSortOrdering(self.addressBook, NULL, kABPersonSortByFirstName);
CFIndex nPeople = ABAddressBookGetPersonCount(self.addressBook);
for (int i=0; i<nPeople; i++) {
//Moving this line here per Amar's answer below. The code works perfectly now.
NSMutableDictionary *personModel = [[NSMutableDictionary alloc]initWithCapacity:0];
ABRecordRef personRef = CFArrayGetValueAtIndex(cList, i); // Person will have name, phone number, address, email id and contact image
//Get the name
NSString *firstName = (__bridge NSString *)(ABRecordCopyValue(personRef, kABPersonFirstNameProperty));
NSString *lastName = (__bridge NSString *)(ABRecordCopyValue(personRef, kABPersonLastNameProperty));
NSString *name = nil;
if(firstName!=nil && lastName!=nil) { //both names are available
name = [NSString stringWithFormat:#"%# %#",firstName,lastName];
} else if(firstName!=nil && lastName==nil) { //last name not available
name = [NSString stringWithFormat:#"%#",firstName];
} else if(firstName==nil && lastName!=nil) { //first name not available
name = [NSString stringWithFormat:#"%#",lastName];
} else {
name = #"Unnamed Contact"; //both names not available
}
//Get the phone numbers
ABMultiValueRef phoneRef = ABRecordCopyValue(personRef, kABPersonPhoneProperty);
NSMutableArray *phoneNumbers = [NSMutableArray new];
CFIndex ctr = ABMultiValueGetCount(phoneRef);
if(ctr!=0) {
NSString *phoneNumber = nil;
for (CFIndex i=0; i<ctr; i++) {
phoneNumber = (__bridge NSString *) ABMultiValueCopyValueAtIndex(phoneRef, i);
[phoneNumbers addObject:phoneNumber];
}
} else {
[phoneNumbers addObject:#"Phone not available"];
}
//Get the contact address
ABMultiValueRef addrRef = ABRecordCopyValue(personRef, kABPersonAddressProperty);
NSMutableArray *addresses = [NSMutableArray new];
ctr = ABMultiValueGetCount(addrRef);
if(ABMultiValueGetCount(addrRef)!=0) {
for(CFIndex i=0; i<ABMultiValueGetCount(addrRef); i++) {
CFDictionaryRef addr = ABMultiValueCopyValueAtIndex(addrRef, i);
NSString *street = (__bridge NSString *)CFDictionaryGetValue(addr, kABPersonAddressStreetKey);
NSString *city = (__bridge NSString *)CFDictionaryGetValue(addr, kABPersonAddressCityKey);
NSString *state = (__bridge NSString *)CFDictionaryGetValue(addr, kABPersonAddressStateKey);
NSString *zip = (__bridge NSString *)CFDictionaryGetValue(addr, kABPersonAddressZIPKey);
NSString *address = [NSString stringWithFormat:#"%#, %#, %# %#",street,city,state,zip];
[addresses addObject:address];
}
} else {
[addresses addObject:#"Address not available"];
}
//Get the email address
ABMultiValueRef emailRef = ABRecordCopyValue(personRef, kABPersonEmailProperty);
NSMutableArray *emailAddresses = [NSMutableArray new];
ctr = ABMultiValueGetCount(emailRef);
if(ctr!=0) {
for(CFIndex i=0; i<ctr; i++) {
NSString *eId = (__bridge NSString*)ABMultiValueCopyValueAtIndex(emailRef, i);
[emailAddresses addObject:eId];
}
} else {
[emailAddresses addObject:#"EmailID not available"];
}
//Get the contact image
UIImage *image = nil;
if(ABPersonHasImageData(personRef)) image = (__bridge UIImage *)(ABPersonCopyImageDataWithFormat(personRef, kABPersonImageFormatThumbnail));
//Append the values to a dictionary
[personModel setValue:name forKey:#"cName"];
[personModel setValue:phoneNumbers forKey:#"cPhone"];
[personModel setValue:addresses forKey:#"cAddresses"];
[personModel setValue:emailAddresses forKey:#"cEmailID"];
[personModel setValue:image forKey:#"cImage"];
[self.contactList addObject: personModel];
}
}
In tableView's datasource method cellForRowAtIndexPath
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ContactsCell forIndexPath:indexPath];
cell.textLabel.font = [UIFont fontWithName:#"Helvetica" size:16];
cell.textLabel.textColor = [UIColor blackColor];
cell.detailTextLabel.font = [UIFont systemFontOfSize:14.0];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
NSDictionary *model = [self.contactList objectAtIndex:indexPath.row];
NSLog(#"Name:%#",[model valueForKey:#"cName"]);
cell.textLabel.text =[model valueForKey:#"cName"];
return cell;
}
There are four contacts in my address book. However, my tableView always returns the last contact's name (which is "Unnamed Contact" as it has no first/last name).
Unnamed Contact
Unnamed Contact
Unnamed Contact
Unnamed Contact
Any idea why?
That's because this line
NSMutableDictionary *personModel = [[NSMutableDictionary alloc]initWithCapacity:0];
is outside the for-loop. You are creating the dictionary just once before iterating your contact list and modifying the same dictionary. Hence it will always store the last contact info.
Instead, move the above line of code inside the for loop, it will create a new dictionary for storing each contact in your list.
Hope that helps!
every time you store you data in dictionary with same KEY 'cName' "
[personModel setValue:name forKey:#"cName"];" Thats why every time
value was overwrite with same key and last record was store in
dictionary thats why the issue was raised, you need to store your data
with different key or get data directly from array rather then
dictionary

UITableView scroll index for array of <FBGraphUser> objects

Really stumped with this need some help! I'm creating a subclass of a UITableViewController to display a list of FB Friends (FBFriendPickerViewController has several limitations for me). I'm able to retrieve an array of id and sort them alphabetically.
However, still can't figure out a way from here to create a separate dictionary to section the FB users into alphabetical sections for indexing.
-(void)captureFacebookFriendUsers
{
//Issue a Facebook Graph API request
NSLog(#"%#", NSStringFromSelector(_cmd));
[FBRequestConnection startForMyFriendsWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error){
if (!error) {
NSLog(#"No error requesting friends");
friendsObjects = [result objectForKey:#"data"]; //Objects are id<FBGraphUser>
friendsNames = [NSMutableArray arrayWithCapacity:friendsObjects.count];
NSMutableArray *friendIds = [NSMutableArray arrayWithCapacity:friendsObjects.count];
//Create a list of friends' Facebook IDs
NSSortDescriptor *firstNameDescriptor = [[NSSortDescriptor alloc]initWithKey:#"first_name" ascending:YES];
friendsObjects = [friendsObjects sortedArrayUsingDescriptors:#[firstNameDescriptor]];
for (NSDictionary *friendObject in friendsObjects) {
[friendIds addObject:[friendObject objectForKey:#"id"]];
[friendsNames addObject:[friendObject objectForKey:#"first_name"]];
}
}
Thanks for taking time to read through this!
Andris's code is excellent, but you don't need to create a separate Person class for this to work. Simply use NSDictionary in place of Person class as follows:
First, create your dictionary - I do this in the View Controller from which I am about to call the table view as part of my button action. You'll also need to declare an NSArray *friends and an NSNumber *friendsCount property in both of your .h files (for your initial view controller and for your table view controller) and synthesize as _friends _friendCount.
- (IBAction)btnAddFriendsTapped:(UIBarButtonItem *)sender {
if (FBSession.activeSession.isOpen){
__block NSArray *friendsArray = [[NSArray alloc]init];
__block NSNumber *friendsArrayCount = [[NSNumber alloc]init];
FBRequest* friendsRequest = [FBRequest requestForMyFriends];
[friendsRequest startWithCompletionHandler: ^(FBRequestConnection *connection,
NSDictionary* result,
NSError *error) {
friendsArray = [result objectForKey:#"data"];
friendsArrayCount = [NSNumber numberWithInt:friendsArray.count];
NSLog(#"Found: %i friends", [friendsArrayCount intValue]);
for (NSDictionary<FBGraphUser>* friend in friendsArray) {
NSLog(#"I have a friend named %# with id %#", friend.name, friend.id);
_friends = [NSArray arrayWithArray:friendsArray];
_friendCount = [NSNumber numberWithInt:[friendsArrayCount intValue]];
}
[self performSegueWithIdentifier:#"friendListSegue" sender:self];
}];
}
Then in the prepareForSegue method pass your dictionary to the Table View Controller not forgetting to import your table view controller header file first.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"friendListSegue"]){
BJFriendListTVC* listOfFriends = segue.destinationViewController;
listOfFriends.friends = _friends;
listOfFriends.friendCount = _friendCount;
}
}
Finally, taking Andris's table code replace the Person class
// Put friends into the appropriate sections
for (NSDictionary<FBGraphUser> *friend in self.friends) {
// Ask the collation which section number the friend name belongs in
NSInteger sectionNumber = [self.collation sectionForObject:friend collationStringSelector:#selector(name)];
// Get the array for that section.
NSMutableArray *sectionFriends = [newSectionsArray objectAtIndex:sectionNumber];
// Add the friend to the section.
[sectionFriends addObject:friend];
}
then when you configure the cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSDictionary<FBGraphUser> *person = [[self.sectionsArray objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
cell.textLabel.text = person.name;
return cell;
}
You'll need to use UILocalizedIndexedCollation for this and call [self.collation sectionForObject:friend collationStringSelector:#selector(name)] to get the index of the section that corresponds to friend name for the locale of the device.
To do that you'll need to store the friend data in a class that has a property "name" (there might be a way to keep using NSDictionary for friend data that I am not aware of).
Here is some code:
// View Controller code
- (void)viewDidLoad
{
[super viewDidLoad];
[self condigureSections];
}
- (void)configureSections
{
// UILocalizedIndexedCollation
self.collation = [UILocalizedIndexedCollation currentCollation];
NSInteger index, sectionTitlesCount = [[self.collation sectionTitles] count];
// new sections with data
NSMutableArray *newSectionsArray = [[NSMutableArray alloc] initWithCapacity:sectionTitlesCount];
// allocate data array for each of the sections
for (index = 0; index < sectionTitlesCount; index++) {
NSMutableArray *array = [[NSMutableArray alloc] init];
[newSectionsArray addObject:array];
}
// Put friends into the appropriate sections
for (Person *friend in self.friends) {
// Ask the collation which section number the friend name belongs in
NSInteger sectionNumber = [self.collation sectionForObject:friend collationStringSelector:#selector(name)];
// Get the array for that section.
NSMutableArray *sectionFriends = [newSectionsArray objectAtIndex:sectionNumber];
// Add the friend to the section.
[sectionFriends addObject:friend];
}
// Now that all the data's in place, each section array needs to be sorted.
for (index = 0; index < sectionTitlesCount; index++) {
NSMutableArray *friendsArrayForSection = [newSectionsArray objectAtIndex:index];
NSArray *sortedFriendsArrayForSection = [self.collation sortedArrayFromArray:friendsArrayForSection collationStringSelector:#selector(name)];
// Replace the existing array with the sorted array.
[newSectionsArray replaceObjectAtIndex:index withObject:sortedFriendsArrayForSection];
}
self.sectionsArray = newSectionsArray;
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [self.sectionsArray count];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [[self.collation sectionTitles] objectAtIndex:section];
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
return [self.collation sectionIndexTitles];
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
return [self.collation sectionForSectionIndexTitleAtIndex:index];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[self.sectionsArray objectAtIndex:section] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
....
Person *person = [[self.sectionsArray objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
....
cell.textLabel.text = person.name;
....
return cell;
}
// Store friend data in a class Person so that we could pass the object to
// - (NSInteger)sectionForObject:(id)object collationStringSelector:(SEL)selector
// Example Person.h
#interface Person : NSObject
#property (nonatomic, copy) NSString *id;
#property (nonatomic, copy) NSString *name;
#property (nonatomic, copy) NSString *pictureUrl;
- (id)initWithId:(NSString *)id name:(NSString *)name picture:(NSString *)picUrl;
#end

Can't access the instance variable

I have an UTableView grouped. I want to split my data into "categories" group. I have an instance variable which contains all groups in my database.
#interface TableViewController : UITableViewController {
// Response from server
NSString* serverResponse;
NSMutableArray *categories; // This NSMutableArray contains all category's server
}
#property (strong) NSString *serverResponse;
#property (strong) NSMutableArray *categories;
I fill my NSMutableArray with the requestFinished method (where all works perfectly)
- (void)requestFinished:(ASIHTTPRequest *)request
{
NSString *result = request.responseString;
NSArray *jsonArray = (NSArray*)[result JSONValue];
int count = [jsonArray count];
int i;
NSDictionary *jsonDict = [[NSDictionary alloc] init];
for (i = 0; i < count; i++) {
jsonDict = [jsonArray objectAtIndex:i];
NSString *current = [jsonDict objectForKey:#"categoriy"];
[categories addObject:current];
NSLog(#"%d", [categories count]) // Shows 4 -> ok !
}
}
But when I call it in this method :
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSLog(#"%d", [categories count]) // Show 0 -> bad !
return [categories count];
}
It shows on the consol "0" !
I really dont understand why !
Perhaps someone could help me with this?
The problem might be that numberOfSectionsInTableView: is called before the requestFinished: is called.
I think you have to reload the tableview.
Try putting [self.tableView reloadData], or something like that, at the end of requestFinished:.

Resources