UITableView scroll index for array of <FBGraphUser> objects - ios

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

Related

How to make up an array with objects for title index uitableview iOS?

I'm making a title indexed event invitation list. I have an array with EventStatus objects. I'm making the _data array for table like,
for (int i = 0; i < eventStatusList.count; i++) {
NSString *firstName = ((OCEventStatus*)eventStatusList[i]).user.firstName;
[_sortedUsers setObject:((OCEventStatus*)eventStatusList[i]).user.firstName forKey:[firstName substringToIndex:1]];
}
_sortedUserTitles = [[[_sortedUsers allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)] mutableCopy];
_data = [eventStatusList mutableCopy];
[self.dataTableView reloadData];
and I think this for loop thing is too slow. Is there a way to do this in a good manner? Following is the title index making up logic with UITablrViewDataSource methods.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return [_sortedUserTitles count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
NSString *sectionTitle = [_sortedUserTitles objectAtIndex:section];
NSArray *sectionUsers = [_sortedUsers objectForKey:sectionTitle];
return [sectionUsers count];
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
// return animalSectionTitles;
return _alphabetArray;
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
return [_sortedUserTitles indexOfObject:title];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [_sortedUserTitles objectAtIndex:section];
}
This is error because of in the _sortedUsers dictionary has a string instead of an array. How may I fix this? And also please suggest a fast, good manner to implement this title index.
If you want to create an List of Firstnames try this perhaps.
At interface:
#property(nonatomic, strong)NSMutableDictionary *dict;
#property(nonatomic, strong)NSMutableArray *alphabet;
_dict = [NSMutableDictionary new];
_alphabet = [NSMutableArray new];
[eventStatusList sortUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
OCEventStatus *ev1 = (OCEventStatus*)obj1;
OCEventStatus *ev2 = (OCEventStatus*)obj2;
return [ev1.firstName compare:ev2.firstName];
}];
for(OCEventStatus *state in eventStatusList){
NSString *firstchar = [[state.firstName substringToIndex:1] lowercaseString];
if([dict objectForKey:firstchar]==nil){
NSMutableArray *tmp = [NSMutableArray new];
[tmp addObject:state];
[_dict setObject:tmp forKey:firstchar];
[_alphabet addObject:firstChar];
}else{
[[dict objectForKey:firstchar] addObject:state];
}
}
Now you have an Array of firstnames in a Dictionary which has the first letter as the key for Example: a -> ["Alfred","Albert",...]
In the Datasource methods you have to return it like this...
-(NSInteger)numberOfSectionsInTableView:(UITableView*)tableView{
return dict.count;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection;(NSInteger)section
{
return [dict objectForKey:[alphabet objectAtIndex:section]].count;
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
OVEventStatus *state = [[dict objectForKey:[alphabet objectAtIndex:indexPath.section]] objectAtIndex:indexPath.row];
cell.textLabel.text = state.firstName;
return cell;
}
Please try if this fits for you
Update:
If you also want to Sort the Arrays I would recommend to sort the eventListBy firstName first! So you have the correct order when you loop over the eventStatusList.

Add Alphabetical Headers to UITableView with a sorted array

I'm sorting my array by last name alphabetically. I'd like to separate this into sections with the appropriate header above each section (A, B, C, etc.).
Here's what I've tried below:
// Here is where I refresh the data and sort it based on last name
- (void)refreshData {
[[PCMSSessionManager sharedSession] refreshPCMSDataWithCompletion:^(BOOL success, NSString *errorMessage, id resultObject) {
if (success) {
NSLog(#"yay!");
self.membersArray = [[PCMSSessionManager sharedSession] memberArr];
// Let's sort the array
self.sortedArray = [self.membersArray sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
NSString *first = [(PCMSMember*)a lastName];
NSString *second = [(PCMSMember*)b lastName];
return [first compare:second];
}];
[self.tableView reloadData];
} else {
NSLog(#"boooo!!!!");
}
}];
}
- (NSDictionary *)indexedMembers
{
NSMutableDictionary *indexedContacts = [NSMutableDictionary new];
for (PCMSMember *member in self.sortedArray)
{
NSString *sortString = member.lastName;
NSString *sortLetter = [sortString substringToIndex:1];
/* see if that letter already exists as an index */
BOOL foundKey = NO;
for (NSString *key in [indexedContacts allKeys])
{
if ([key isEqualToString:sortLetter])
{
foundKey = YES;
}
}
NSMutableArray *valueArray;
if (foundKey)
{
valueArray = [((NSArray *)indexedContacts[sortLetter]) mutableCopy];
}
else
{
valueArray = [NSMutableArray new];
}
[valueArray addObject:member];
indexedContacts[sortLetter] = [valueArray copy];
}
return [indexedContacts copy];
}
// Here's my table data
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [[[self indexedMembers] allKeys] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSDictionary *indexedContacts = [self indexedMembers];
NSArray *myKeys = [indexedContacts allKeys];
NSString *key = myKeys[section];
return [((NSArray *)[self indexedMembers][key]) count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"cellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
// Configure the cell...
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
if (self.isPhysician == YES) {
NSString *key = [[self indexedMembers] allKeys][indexPath.section];
PCMSMember *currentMember = ((NSArray *)[self indexedMembers][key])[indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:#"%# %#", currentMember.firstName, currentMember.lastName];
}
return cell;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [[self indexedMembers] allKeys][section];
}
UPDATE:
This is getting me closer to what I want.
The data is loading, it's being grouped properly and the headers are showing.
But it's not in alphabetical order.
How can I improve this code to show alphabetically?
It's showing in alphabetical order in my console, just not in the app.
The NSMutableDictionary is unordered by definition. It is not the natural choice if you rely on the order of the stored objects. I suggest you to use NSMutableArray instead. To store the tableview data for each section you can use this mini class
#interface MembersWithSameInitial : NSObject
#property (strong) NSString* initial;
#property (strong) NSMutableArray<PCMSMember*>* members;
#end
#implementation MembersWithSameInitial
#end
After you have sorted the members, all the data for the tableview can be produced with this before tableView reload.
NSMutableArray<MembersWithSameInitial*>* groupedMembers = [[NSMutableArray alloc] init];
for (PCMSMember* member in sortedArray) {
NSString* inicial = [member.lastName substringToIndex:1];
MembersWithSameInitial* last = [groupedMembers lastObject];
if (last && [last.initial isEqualToString:inicial]) {
[last.members addObject:member];
} else {
MembersWithSameInitial* newGroup = [[MembersWithSameInitial alloc] init];
newGroup.initial = inicial;
newGroup.members = [[NSMutableArray alloc] initWithObjects:member, nil];
[groupedMembers addObject:newGroup];
}
}
Since the structure of groupedMembers fits to a grouped tableView, the dataSource methods will have trivial implementations. Assuming, that you have stored groupedMembers in a property.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return self.groupedMembers.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.groupedMembers[section].members.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//...
PCMSMember *currentMember = self.groupedMembers[indexPath.section].members[indexPath.row];
//...
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return groupedMembers[section].initial;
}
Suggestion:
Create two properties
#property NSMutableArray *keys; // for the letters in alphabetical order
#property NSMutableDictionary *indexedContacts; // same as your implementation.
In the method refreshData call the method to create the data source and then reload the table view on the main thread.
Actually you don't need the properties memberArray and sortedArray anymore. The sorted array is passed to the method to create the data source.
- (void)refreshData {
[[PCMSSessionManager sharedSession] refreshPCMSDataWithCompletion:^(BOOL success, NSString *errorMessage, id resultObject) {
if (success) {
NSLog(#"yay!");
self.membersArray = [[PCMSSessionManager sharedSession] memberArr];
// Let's sort the array
NSArray *sortedArray = [self.membersArray sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
NSString *first = [(PCMSMember*)a lastName];
NSString *second = [(PCMSMember*)b lastName];
return [first compare:second];
}];
[self indexMembers:sortedArray];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
} else {
NSLog(#"boooo!!!!");
}
}];
}
The method indexMembers initializes the properties keys and indexedContacts and creates the data source.
- (void)indexMembers:(NSArray *)sortedMembers
{
self.keys = [[NSMutableArray alloc] init];
self.indexedContacts = [[NSMutableDictionary alloc] init];
for (PCMSMember *member in sortedMembers)
{
NSString *sortString = member.lastName;
NSString *sortLetter = [sortString substringToIndex:1];
/* see if that letter already exists as an index */
NSArray *keyArray = self.indexedContacts[sortLetter];
NSMutableArray *valueArray;
if (keyArray) {
// array for key exists, use it
valueArray = [keyArray mutableCopy];
} else {
// array for key does not exist, create a new one
valueArray = [NSMutableArray new];
// and add the letter to keys
[self.keys addObject:sortLetter];
}
[valueArray addObject:member];
self.indexedContacts[sortLetter] = [valueArray copy];
}
}
numberOfSectionsInTableView returns the number of keys
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return self.keys.count;
}
numberOfRowsInSection gets the appropriate array for the given section and returns the number of items.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSString *letter = self.keys[section];
NSArray *memberArray = self.indexedContacts[letter];
return memberArray.count;
}
In cellForRowAtIndexPath use the method dequeueReusableCellWithIdentifier: forIndexPath: to get always a valid cell. Then like in numberOfRowsInSection get the actual member array and populate the label.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"cellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
// Configure the cell...
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
if (self.isPhysician == YES) {
NSString *letter = self.keys[indexPath.section];
NSArray *memberArray = self.indexedContacts[letter];
PCMSMember *currentMember = memberArray[indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:#"%# %#", currentMember.firstName, currentMember.lastName];
}
return cell;
}
titleForHeaderInSection simply returns the letter for the section
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return self.keys[section];
}
You're calling indexedMembers too much. This is very expensive.
I couldn't test the code, maybe there is a self or something else missing but you get an impression of the workflow.

Indexed Table View for Facebook Friends

I am trying to sort my Facebook friends list in order to implement an indexed table view, exactly like the one found in Facebook Messenger.
I originally tried to use the UILocalizedIndexedCollation but I am stuck on the "collationStringSelector" because my dataSource array are id<"FBGraphUser"> objects and therefore have no properties I can use for the selector. Any ideas of how to implement this (it does not have to be using this method, I am open to anything!)?
-(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 (id 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;
}
Here is a subclass of UITableViewController that I created some time ago to make an indexed table view. I use this as a base class for the controller whose table view appears on screen, so it's intended to be a reusable class that does the hard work of creating the table. You might be able to use it as is, but I didn't try to make it too universal. I pass in a simple array of custom objects (my objects just had first and last names), and this class creates the sections and the index. So here's the code,
the .h
#interface RDIndexedTableController : UITableViewController
#property (strong,nonatomic) NSArray *inputArray;
#property (strong,nonatomic) NSString *sectionKey;
#property (strong,nonatomic) NSString *secondarySortKey;
#property (nonatomic, retain) NSMutableArray *sectionsArray;
#property (nonatomic, retain) UILocalizedIndexedCollation *collation;
-(void)convertArray:(NSArray *)input usingSectionKey:(NSString *)key secondarySortKey:(NSString *)sorter;
#end
The .m
#interface RDIndexedTableController ()
#property (strong,nonatomic) NSMutableArray *firstLetterArray;
#end
#implementation RDIndexedTableController
-(void)convertArray:(NSArray *)input usingSectionKey:(NSString *)key secondarySortKey:(NSString *)sorter {
self.inputArray = input;
self.sectionKey = key;
self.secondarySortKey = sorter;
[self configureSections];
}
- (void)configureSections {
self.collation = [UILocalizedIndexedCollation currentCollation];
NSInteger sectionTitlesCount = [[self.collation sectionTitles] count];
NSMutableArray *newSectionsArray = [[NSMutableArray alloc] initWithCapacity:sectionTitlesCount];
for (int index = 0; index < sectionTitlesCount; index++) {
NSMutableArray *array = [[NSMutableArray alloc] init];
[newSectionsArray addObject:array];
}
for (id obj in self.inputArray) {
NSInteger sectionNumber = [self.collation.sectionTitles indexOfObject:[[obj valueForKey:self.sectionKey] substringToIndex:1]];
NSMutableArray *sectionForObjects = [newSectionsArray objectAtIndex:sectionNumber];
[sectionForObjects addObject:obj];
}
for (int index = 0; index < sectionTitlesCount; index++) {
NSMutableArray *objectArrayForSection = [newSectionsArray objectAtIndex:index];
NSArray *firstSortedObjectArrayForSection = [self.collation sortedArrayFromArray:objectArrayForSection collationStringSelector:NSSelectorFromString(self.secondarySortKey)];
NSArray *sortedObjectArrayForSection = [self.collation sortedArrayFromArray:firstSortedObjectArrayForSection collationStringSelector:NSSelectorFromString(self.sectionKey)];
[newSectionsArray replaceObjectAtIndex:index withObject:sortedObjectArrayForSection];
}
self.sectionsArray = newSectionsArray;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return self.collation.sectionTitles.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSArray *objectsInSection = [self.sectionsArray objectAtIndex:section];
return objectsInSection.count;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return self.collation.sectionTitles[section];
}
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
if ([[self.sectionsArray objectAtIndex:section] count] == 0) {
return 0;
}else{
return 30;
}
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return #[#"A",#"B",#"C",#"D",#"E",#"F",#"G",#"H",#"I",#"J",#"K",#"L",#"M",#"N",#"O",#"P",#"Q",#"R",#"S",#"T",#"U",#"V",#"W",#"X",#"Y",#"Z"];
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
return [self.collation sectionForSectionIndexTitleAtIndex:index];
}
In the table view controller that appears in the app, I only need this small amount of code to populate the table,
-(void)viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
NSMutableArray *mut = [NSMutableArray new];
// create the Person objects and add them to the array here
[self convertArray:mut usingSectionKey:#"lastName" secondarySortKey:#"firstName"];
[self.tableView reloadData];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
NSString *first = [self.sectionsArray[indexPath.section][indexPath.row] valueForKey:#"firstName"];
NSString *last = [self.sectionsArray[indexPath.section][indexPath.row] valueForKey:#"lastName"];
cell.textLabel.text = [NSString stringWithFormat:#"%# %#",first,last];
return cell;
}

Multi level categories with items on all levels in UITableView

I have to create a UITableView using the JSON response below ( Array ). I have no code for this yet but would love some direction to how i would split this array to accommodate categories and items on all levels.
{
"result":{
"products":[
{
"id":"4",
"product_code":"PR04",
"title":"Product1",
"franchisee_id":"118"
}
],
"categories":[
{
"id":"8",
"name":"Category1"
},
{
"id":"20",
"name":"Category2",
"products":[
{
"id":"9",
"product_code":"PR07",
"title":Product2,
"franchisee_id":"118"
}
]
}
]
}
}
I want to achieve the following result:
items
Category1 > items
Category2 > items
When a category is clicked it would slide to the products in that category. Would really love some direction on this. Some products will not be in categories. Like the example above.
Well....
You need to parse the JSON file. You can easily google for some tutorials but here is a decent one.
Next you are going to need to setup a UITableView to load the items. another good tutorial on UITableViews
Then you are going to need to learn how to pass data between UIViewControllers. Tutorial.
So your steps in the code will be to:
Parse the JSON to separate all the elements.
Setup a UITableView to display the top level elements.
Create a second UITableViewController to push to after a top level item has been selected.
Setup a custom initializer for the second UITableViewController so you can pass it relevant data from the first view controller where you parsed the JSON.
I'm assuming you were looking for a bunch of code on how to do this, but that's no fun :)
Let me know if you run into any troubles and I will be glad to help.
EDIT:
I know I said I wasn't going to dump code but I have some extra time.
Create an NSObject subclass called ProductObject and make the .h look like this:
#import <Foundation/Foundation.h>
#interface ProductObject : NSObject
#property NSString *productCode, *productTitle, *franchiseId, *productId;
#end
Don't do any thing to the .m
Create another NSObject subclass called CategoryObject and make the .h look like this:
#import <Foundation/Foundation.h>
#interface CategoryObject : NSObject
#property NSString *categoryName, *categoryId;
#property NSArray *products;
#end
Again, don't need to do anything to the .m.
Now, in the class that you want to display the UITableView will the Products and Categories (this is all in the .m, the .h is empty):
#import "ViewController.h"
#import "CategoryObject.h"
#import "ProductObject.h"
#interface ViewController ()
//Hooked in from IB
#property (weak, nonatomic) IBOutlet UITableView *table;
//Our UITableView data source
#property NSMutableDictionary *tableObjects;
#end
#implementation ViewController
/**
Parses a the local JSON file
*/
- (void)parseJSON {
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"test" ofType:#"json"];
//création d'un string avec le contenu du JSON
NSString *myJSON = [[NSString alloc] initWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:NULL];
NSError *error;
NSDictionary *topLevleJSON = [NSJSONSerialization JSONObjectWithData:[myJSON dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error];
if (error) {
NSLog(#"Error serializing JSON: %#", error.localizedDescription);
return;
}
NSArray *products = topLevleJSON[#"products"];
NSArray *categories = topLevleJSON[#"categories"];
//Use a NSDictonary so that it contains an NSArray of ProductObjects for the "Products" key, and an array of CategoryObjects for the "Category" key.
self.tableObjects = [NSMutableDictionary new];
//Parse all the products
NSMutableArray *productsInJSON = [NSMutableArray new];
[products enumerateObjectsUsingBlock:^(NSDictionary *productObject, NSUInteger idx, BOOL *stop) {
ProductObject *product = [self createProductObjectFromDictionary:productObject];
[productsInJSON addObject:product];
}];
//Set the array of ProductObjects for the key #"Products"
[self.tableObjects setObject:productsInJSON forKey:#"Products"];
//Parse all the categories
NSMutableArray *categoriesInJSON = [NSMutableArray new];
[categories enumerateObjectsUsingBlock:^(NSDictionary *categoryObject, NSUInteger idx, BOOL *stop) {
CategoryObject *category = [self createCategoryObjectFromDictionary:categoryObject];
[categoriesInJSON addObject:category];
}];
//Set the array of CategoryObjects for key #"Categories"
[self.tableObjects setObject:categoriesInJSON forKey:#"Categories"];
[self.table reloadData];
}
/**
Creates a ProductObject from an NSDictonary.
#param dictionary The dictonary describing the Product parsed from JSON
#return A pretty formatted ProductObject
*/
- (ProductObject*)createProductObjectFromDictionary:(NSDictionary*)dictionary {
ProductObject *product = [ProductObject new];
product.productTitle = dictionary[#"title"];
product.productCode = dictionary[#"product_code"];
product.franchiseId = dictionary[#"franchisee_id"];
product.productId = dictionary[#"id"];
return product;
}
/**
Creates a Category from an NSDictionary
#param dictionary The dictonary describing the Category parsed from JSON
#return A pretty formatted CategoryObject
*/
- (CategoryObject*)createCategoryObjectFromDictionary:(NSDictionary*)dictionary {
CategoryObject *category = [CategoryObject new];
category.categoryId = dictionary[#"id"];
category.categoryName = dictionary[#"name"];
//Check to see if the "products" key exist for the category, if we don't check and just look for it, we will get a crash if it doesn't exist.
if ([[dictionary allKeys] containsObject:#"products"]) {
NSArray *categoryProducts = dictionary[#"products"];
//Parse all the Products for the Category.
NSMutableArray *categoryProductsFormatted = [NSMutableArray new];
[categoryProducts enumerateObjectsUsingBlock:^(NSDictionary *productObject, NSUInteger idx, BOOL *stop) {
ProductObject *product = [self createProductObjectFromDictionary:productObject];
[categoryProductsFormatted addObject:product];
}];
category.products = [NSArray arrayWithArray:categoryProductsFormatted];
}
else {
category.products = nil;
}
return category;
}
#pragma mark -
#pragma mark - UITableView delegate
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [[self.tableObjects allKeys] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
//Get the key for this section
NSString *key = [[self.tableObjects allKeys] objectAtIndex:section];
//Return the number of objects for this key.
return [(NSArray*)[self.tableObjects objectForKey:key] count];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [[self.tableObjects allKeys] objectAtIndex:section];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CellIdentifier"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"CellIdentifier"];
}
//Get all the NSArray associated with this section, which will be an array of ProductObjects or an array of CategoryObjects
NSString *key = [[self.tableObjects allKeys] objectAtIndex:indexPath.section];
NSArray *sectionobjects = (NSArray*)[self.tableObjects objectForKey:key];
id object = [sectionobjects objectAtIndex:indexPath.row];
//Set the cell text based on what kind of object is returned
if ([object isKindOfClass:[ProductObject class]]) {
cell.textLabel.text = [(ProductObject*)object productTitle];
}
else if ([object isKindOfClass:[CategoryObject class]]) {
cell.textLabel.text = [(CategoryObject*)object categoryName];
}
return cell;
}
#pragma mark -
#pragma mark - UITableView delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSString *key = [[self.tableObjects allKeys] objectAtIndex:indexPath.section];
NSArray *sectionobjects = (NSArray*)[self.tableObjects objectForKey:key];
id object = [sectionobjects objectAtIndex:indexPath.row];
//They selected a product
if ([object isKindOfClass:[ProductObject class]]) {
ProductObject *product = (ProductObject*)object;
NSLog(#"%#", product.productTitle);
NSLog(#"%#", product.productCode);
NSLog(#"%#", product.productId);
}
//They selected a Category
else if ([object isKindOfClass:[CategoryObject class]]) {
//Check to see if the CategoryObject has any ProductObjects associated with it
if ([(CategoryObject*)object products]) {
//Now you will need to pass array of ProductObjects this along to your next view controller.
NSArray *cateogryProducts = [(CategoryObject*)object products];
//For demonstration purposes, i'll run through and print out all the Products for this Category
[cateogryProducts enumerateObjectsUsingBlock:^(ProductObject *product, NSUInteger idx, BOOL *stop) {
NSLog(#"%#", product.productTitle);
NSLog(#"%#", product.productCode);
NSLog(#"%#", product.productId);
}];
}
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//Start parsing the JSON
[self parseJSON];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
EDIT:
If you are wanting to open and close parts of the table like an accordion, take a look at Apple's same code: Table View Animations and Gestures.

How to display section header\Tile after sorting uitableview

I am sorting my tableview by Distributor using the code below (it was Alphabetical by product)
NSSortDescriptor *aSort =[[NSSortDescriptor alloc] initWithKey:#"Dis" ascending:YES];
[distribArray sortUsingDescriptors:[NSMutableArray arrayWithObject:aSort]];
NSLog( #"data from table %#", distribArray);
[self.tableView reloadData];
NSLog(#"ok2222222222");
[[NSUserDefaults standardUserDefaults] setValue:#"Dis" forKey:#"ListBy"];
[[NSUserDefaults standardUserDefaults] synchronize];
I would like to know what the easiest way to display the Distributor name as a Title header above all the products for that Distributor. I currently display the Distributor name in the DetailsView of the cell for each product.
I would like to go from.
Product 1
Acme
Product 2
Acme
Product 3
Acme
To this below and keep my UITableView\Cells
Acme
Product 1
Product 2
Product 3
....
Many Thanks for any help.
maybe it is not the fastest way, but i think it is simple
first create a small inner class like this:
#interface ProductSection
#property (strong, nonatomic) NSString* sectionName;
#property (strong, nonatomic) NSMutableArray* products;
#end
then use this instead your sort:
NSSortDescriptor *aSort =[[NSSortDescriptor alloc] initWithKey:#"Dis" ascending:YES];
NSArray* products = [distribArray sortUsingDescriptors:[NSMutableArray arrayWithObject:aSort]];
self.sections = [NSMutableArray array];
NSString* currentDistributor = nil;
for (Product* p in products) {
if (![p.Dis isEqualToString:currentDistributor]) {
ProductSection* section = [[ProductSection alloc] init];
section.sectionName = p.Dis;
section.products = [NSMutableArray array];
[self.sections addObject:section];
currentDistributor = p.Dis;
}
ProductSection* section = [self.sections lastObject];
[section.products addObject:p];
}
[self.tableView reloadData];
where self.sections is a mutable array of ProductSection
next use this in your Table View Data Source:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [[[self.sections objectAtIndex:section] products] count];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [self.sections count];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
[[self.sections objectAtIndex:section] sectionName];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
Product* p = [[[self.sections objectAtIndex:indexPath.section] products] objectAtIndex:indexPath.row];
...
}
hope that will help

Resources