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;
}
Related
I can't seem to find too much on this. I'm currently trying to create a swipable delete button that will delete the row that is swiped, and if that row is now empty from the section header it will delete the section header as well. For example, "Bread" is swiped to delete, and there is nothing else under the section header "B". Then this will delete both Bread, and the "B" section header. My code is below.
#interface ChoicesTableViewController () <UITableViewDelegate, UITableViewDataSource>
#property (weak, nonatomic) IBOutlet UITableView *myTableView;
#property (strong, nonatomic) NSMutableArray *items;
#property (strong, nonatomic) NSMutableDictionary *alphabetizedItems;
#end
#implementation ChoicesTableViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.myTableView.delegate = self;
self.myTableView.dataSource = self;
self.items = [[NSMutableArray alloc] init];
[self.items addObject:#"Apples"];
[self.items addObject:#"Bread"];
self.alphabetizedItems = [self alphabetizeItems:self.items];
}
//Segue if the item is tapped
//- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
//{
// MyDataChoices *currentRow = self.arrayNames[indexPath.row];
// self.mySelectedCell = currentRow.myNameChoices;
//
// [self performSegueWithIdentifier:#"unwindSegueAction" sender:self];
//
//}
////unwind segue from add choice
- (IBAction)unwindSegueToChoices:(UIStoryboardSegue *)segue
{
AddChoiceViewController *sourceVC = segue.sourceViewController;
NSString *myNewItem = sourceVC.myTextField.text;
//NSString *myFinalString = [[myNewItem substringToIndex:1] capitalizedString];
NSString *stringCapitalized = [myNewItem capitalizedString];
[self.items addObject:stringCapitalized];
self.alphabetizedItems = [self alphabetizeItems:self.items];
//[self.arrayNames addObjectsFromArray:#[[MyDataChoices itemWithNewName:stringCapitalized]]];
[self.tableView reloadData];
}
//titles for talble view
#pragma mark Helper Methods
- (NSMutableDictionary *)alphabetizeItems:(NSArray *)items {
NSMutableDictionary *buffer = [[NSMutableDictionary alloc] init];
// Put Fruits in Sections
for (int i = 0; i < [items count]; i++) {
NSString *fruit = [items objectAtIndex:i];
NSString *firstLetter = [[fruit substringToIndex:1] uppercaseString];
if ([buffer objectForKey:firstLetter]) {
[(NSMutableArray *)[buffer objectForKey:firstLetter] addObject:fruit];
} else {
NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithObjects:fruit, nil];
[buffer setObject:mutableArray forKey:firstLetter];
}
}
// Sort Fruits
NSArray *keys = [buffer allKeys];
for (int j = 0; j < [keys count]; j++) {
NSString *key = [keys objectAtIndex:j];
[(NSMutableArray *)[buffer objectForKey:key] sortUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
}
NSMutableDictionary *result = [NSMutableDictionary dictionaryWithDictionary:buffer];
return result;
}
#pragma mark title indexing
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSArray *keys = [[self.alphabetizedItems allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
NSString *key = [keys objectAtIndex:section];
return key;
}
# pragma mark main table view
-(NSInteger) numberOfSectionsInTableView:(UITableView *) tableView
{
NSArray *keys = [self.alphabetizedItems allKeys];
return [keys count];
}
-(NSInteger) tableView:(UITableView *) tableView numberOfRowsInSection:(NSInteger)section
{
//return self.arrayNames.count;
NSArray *unsortedKeys = [self.alphabetizedItems allKeys];
NSArray *sortedKeys = [unsortedKeys sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
NSString *key = [sortedKeys objectAtIndex:section];
NSArray *fruitsForSection = [self.alphabetizedItems objectForKey:key];
return [fruitsForSection count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//MyDataChoices *currentRow = self.arrayNames[indexPath.row];
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:#"mainCell2" forIndexPath:indexPath];
//cell.textLabel.text = currentRow.myNameChoices;
NSArray *unsortedKeys = [self.alphabetizedItems allKeys];
NSArray *sortedKeys = [unsortedKeys sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
NSString *key = [sortedKeys objectAtIndex:[indexPath section]];
NSArray *fruitsForSection = [self.alphabetizedItems objectForKey:key];
NSString *fruit = [fruitsForSection objectAtIndex:[indexPath row]];
[cell.textLabel setText:fruit];
return cell;
}
# pragma Mark delete slide button
//Delete Swipe Button
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
return YES;
}
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
int index = indexPath.row;
//[self.items removeObjectAtIndex:index];
[self.alphabetizedItems removeObjectForKey:indexPath];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
The basic approach is to see how many rows are in the section for the row being deleted. If the section has two or more rows, simply delete the row as you are doing now. If the section only has one row (the one being deleted), then remove the section from the data model and then delete the section from the table instead of deleting the row.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
NSArray *unsortedKeys = [self.alphabetizedItems allKeys];
NSArray *sortedKeys = [unsortedKeys sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
NSString *key = [sortedKeys objectAtIndex:[indexPath section]];
NSArray *fruitsForSection = [self.alphabetizedItems objectForKey:key];
if (fruitsForSection.count == 1) {
// Delete the whole section
[self.alphabetizedItems removeObjectForKey:key];
[tableView deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationFade];
} else {
// Delete the row from the data source
NSInteger index = indexPath.row;
//[self.items removeObjectAtIndex:index];
[self.alphabetizedItems removeObjectForKey:indexPath];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
I have a plist with contacts: the root is an array, items 0-150 are dictionaries, each dictionary is a single contact with a "name", "number", and "email" string.
The code below sorts the contacts alphabetically into sections based upon the "name" string. Then uses the inner array to populate the cells for each section. I then pass the name from the inner array to my detail view.
However, I can not figure out how to pass the correct number and email for each contact into the detail view. I've been working on this issue for a long while and can not find a solution.
#interface ContactsViewController ()
-(void)configureSectionData;
#end
#implementation ContactsViewController
#synthesize tableData;
#synthesize collation;
#synthesize outerArray;
#synthesize indexTitlesArray, namesDictionary;
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - Table view methods
-(void)configureSectionData {
NSUInteger sectionTitlesCount = [collation.sectionTitles count];
self.outerArray = [NSMutableArray arrayWithCapacity:sectionTitlesCount];
for (NSUInteger index = 0; index < sectionTitlesCount; index++) {
NSMutableArray *array = [NSMutableArray array];
[self.outerArray addObject:array];
}
for (NSString *nameString in tableData)
{
NSInteger sectionNumber = [collation sectionForObject:nameString collationStringSelector:#selector(lowercaseString)];
NSMutableArray *sectionNames = [outerArray objectAtIndex:sectionNumber];
[sectionNames addObject:nameString];
}
for (NSUInteger index = 0; index < sectionTitlesCount; index++) {
NSMutableArray *namesForSection = [outerArray objectAtIndex:index];
NSArray *sortedNamesForSection = [collation sortedArrayFromArray:namesForSection collationStringSelector:#selector(lowercaseString)];
[self.outerArray replaceObjectAtIndex:index withObject:sortedNamesForSection];
}
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [self.collation.sectionTitles count];
}
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
NSString *theLetter = [self.collation.sectionTitles objectAtIndex:section];
if (![theLetter isEqualToString:#"#"]) {
NSString *titleString = [NSString stringWithFormat:#"%#", theLetter];
return titleString;
}
return nil;
}
-(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return self.collation.sectionTitles;
}
-(NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
return [self.collation sectionForSectionIndexTitleAtIndex:index];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSArray *innerArray = [self.outerArray objectAtIndex:section];
return [innerArray count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"cellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
// Get the inner array for this section
NSArray *innerArray = [self.outerArray objectAtIndex:indexPath.section];
// Get the name from the inner array
NSString *theName = [innerArray objectAtIndex:indexPath.row];
cell.textLabel.text = theName;
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainiPhoneStoryboard" bundle:nil];
DetailViewController *detailView = (DetailViewController *)[storyboard instantiateViewControllerWithIdentifier:#"DetailViewController"];
// Get the inner array for this section
NSArray *innerArray = [self.outerArray objectAtIndex:indexPath.section];
// Get the name from the inner array
NSString *tmpname = [innerArray objectAtIndex:indexPath.row];
detailView.lblname = tmpname;
[self presentViewController:detailView animated:YES completion:nil];
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.namesDictionary = [NSMutableArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"contacts" ofType:#"plist"]];
self.tableData = [namesDictionary valueForKey:#"name"];
self.collation = [UILocalizedIndexedCollation currentCollation];
[self configureSectionData];
}
Since you're populating your table from an array of just the names from your plist, you'll have to search that array using the name to find the dictionary that it belongs to, so you can pass that to the detail view controller (you would need to create a property in your detail view controller, passedInDictionary in my example):
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainiPhoneStoryboard" bundle:nil];
DetailViewController *detailView = (DetailViewController *)[storyboard instantiateViewControllerWithIdentifier:#"DetailViewController"];
// Get the inner array for this section
NSArray *innerArray = [self.outerArray objectAtIndex:indexPath.section];
// Get the name from the inner array
NSString *tmpname = [innerArray objectAtIndex:indexPath.row];
NSInteger indx = [self.namesDictionary indexOfObjectPassingTest:^BOOL(NSDictionary *dict, NSUInteger idx, BOOL *stop) {
return [dict[#"name"] isEqualToString:tmpname];
}];
NSDictionary *dict = self.namesDictionary[indx];
detailView.passedInDictionary = dict;
[self presentViewController:detailView animated:YES completion:nil];
}
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
I cant find results using my search bar. See my code attached:
// ViewController.m
#import "ViewController.h"
#implementation ViewController
#synthesize names;
#synthesize keys;
#pragma mark - View lifecycle
- (void)viewDidLoad
{
NSString *path = [[NSBundle mainBundle] pathForResource:#"sortednames"ofType:#"plist"];
NSDictionary *dict = [[NSDictionary alloc]initWithContentsOfFile:path];
self.names = dict;
NSArray *array = [[names allKeys] sortedArrayUsingSelector:#selector(compare:)];
self.keys = array;
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
#pragma mark -
#pragma mark Table View Data Source Methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [keys count];
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
NSString *key = [keys objectAtIndex:section];
NSArray *nameSection = [names objectForKey:key];
return [nameSection count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger section = [indexPath section];
NSUInteger row = [indexPath row];
NSString *key = [keys objectAtIndex:section];
NSArray *nameSection = [names objectForKey:key];
static NSString *SectionsTableIdentifier = #"SectionsTableIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:
SectionsTableIdentifier ];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier: SectionsTableIdentifier ];
}
cell.textLabel.text = [nameSection objectAtIndex:row];
return cell;
}
- (NSString *)tableView:(UITableView *)tableView
titleForHeaderInSection:(NSInteger)section
{
NSString *key = [keys objectAtIndex:section];
return key;
}
http://i.stack.imgur.com/ZBzJZ.png
http://i.stack.imgur.com/ktevQ.png
here is my "h" file:
//
// ViewController.h
// Sections
//
// Created by t r on 3/17/12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
<UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate>
{
NSDictionary *names;
NSArray *keys;
}
#property (nonatomic, retain) NSDictionary *names;
#property (nonatomic, retain) NSArray *keys;
#end
The UIsearchBar does not automatically do a search on the tabelview content. You have to implement the UISearchbarDelegate methods to detect the entered text, then reload the table view and return values in the table view delegate methods according to the new array filtered with the search criteria.
What is the best way to rollup similar rows in UITableView like the Phone's Recents tab. I'm using Core Data and currently displaying data chronologically based on a "timestamp" field in my NSManagedObject. The iPhone Recents table groups like rows into one row to compress redundant data.
What is the best way to accomplish this?
I'm sure that Apple has some easier way they do it in their code, but here is what I came up with. There is 1 NSArray (with some raw data) that I instantiate in the viewDidLoad method, and then 2 NSMutableArray's that are declared as properties (and lazily instantiated) in the .h file. Here is my code.
.h
#interface RollUpTableViewController : UITableViewController
#property (strong, nonatomic) IBOutlet UITableView *rollUpTableView;
#property (strong, nonatomic) NSMutableArray *names;
#property (strong, nonatomic) NSMutableArray *countOfNames;
#end
.m
-(NSMutableArray *)names{
if(!_names) _names = [[NSMutableArray alloc]init];
return _names;
}
-(NSMutableArray *)countOfNames{
if (!_countOfNames) _countOfNames = [[NSMutableArray alloc] init];
return _countOfNames;
}
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *rawData = [[NSArray alloc] initWithObjects:#"Jack", #"Jill", #"Jill", #"Ryan", #"Bill", #"Ryan", #"Ryan", #"Ryan", #"Steve", #"Katie", #"Jill", #"Ryan", #"Ryan", nil];
int countOfLikeNames = 0;
int i=0;
for (i = 0; i < [rawData count]; i++){
if (i == 0) {
//Putting the first name in the names NSMutableArray no matter what
[self.names addObject:[rawData objectAtIndex:i]];
countOfLikeNames = countOfLikeNames + 1;
} else {
//Checking if the current name is the same as the previous
if ([rawData objectAtIndex:i] == [rawData objectAtIndex:(i-1)]) {
countOfLikeNames = countOfLikeNames + 1;
} else {
//Once it runs into a difference in names, add the final count to the countOfNames NSMutableArray
[self.countOfNames addObject:[NSNumber numberWithInt:countOfLikeNames]];
//Starting the count over
countOfLikeNames = 1;
//Adding the next name
[self.names addObject:[rawData objectAtIndex:i]];
}
}
//if the for loop is on its last iteration, add what will be the last object for countOfNames
if (i == ([rawData count] - 1)) {
[self.countOfNames addObject:[NSNumber numberWithInt:countOfLikeNames]];
}
}
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [self.names count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.textLabel.text = [self.names objectAtIndex:indexPath.row];
cell.detailTextLabel.text = [[self.countOfNames objectAtIndex:indexPath.row] stringValue];
return cell;
}
#end
I was a bit lazy and just used Apple's built in 'Right Detail' style on the UITableViewCell and this is what resulted from the raw data.
You would have to compare the time stamps that you have from your CoreData, but hopefully this helps you with the concept.