UISearchBar in iPhone/iPad application - iphone

I have data like this...(All the data comes from .plist file...)
Searching Array - (
{
FirstName = "Ramesh";
LastName = "Bean";
EmpCode = 1001;
},
{
FirstName = "Rohan";
LastName = "Rathor";
EmpCode = 102;
},
{
FirstName = "Priya";
LastName = "Malhotra";
EmpCode = 103;
},
{
FirstName = "Mukesh";
LastName = "Sen";
EmpCode = 104;
},
{
FirstName = "Priya";
LastName = "Datta";
EmpCode = 105;
}
)
I want implement search data from this array on the basis of FirstName (key).
I am able to search data with the "FirstName(Key)"
but after filtering data suppose i clicked Row( in the data) which is displayed in the TableView. It Navigate me to New-Controller with all the information of that particular employee (like: FirstName,LastName,EmpCode).
How can i get information?
As i gone through the search sample codes.
Here is my search code...
NSString *searchText = searchBar.text;
NSMutableArray *searchArray = [[NSMutableArray alloc] init];
NSInteger TotalNoOfRecords=[self.SearchtableDataSource count];
for (int i=0;i<TotalNoOfRecords;i++)
{ NSDictionary *dictionary = [self.SearchtableDataSource objectAtIndex:i];
NSArray *array = [dictionary objectForKey:#"FirstName"];
[searchArray addObject:array];
}
for (NSString *sTemp in searchArray)
{
NSRange titleResultsRange = [sTemp rangeOfString:searchText options:NSCaseInsensitiveSearch];
if (titleResultsRange.length > 0)
{
[copyListOfItems addObject:sTemp];
}
}
How can i improve this code?....Please guide me... [searchArray release]; searchArray = nil;
How we maintain all the "Keys(FirstName,LastName,EmpCode)" in the searchArray please help me out? Thanks...

I think the best approach would be to make an array of NSDictionary which has value for three keys namely "FirstName", "LastName", "EmpCode"
now to filter the data according to "FirstName" use NSPredicate instead of for loop,
NSString *searchText = searchBar.text;
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"FirstName like[cd] %#",searchText];
NSArray* filteredArray = [self.SearchtableDataSource filteredArrayUsingPredicate:predicate];
In method cellForRowAtIndexPath
NSDictionary* currentEmp = [filteredArray objectAtIndex:inddexPath.row];
display the information in this currentEmp.
Similarly in didSelectRowAtIndexPath
NSDictionary* currentEmp = [filteredArray objectAtIndex:inddexPath.row];
and pass this dictionary in the next ViewController in which u want to dispaly the detail of the current employ.
if u have two table views one that of searchDisplayController (showing the filtered result)
and one which is showing the whole result, then u can track this by having a BOOL variable tacking that is if filtering is active or not.

Here is what i have done and its working fine with me(tested it). I have taken one IBOutlet of UISearchBar so here is the code for ur .h file
#interface SearchForEmp : UIViewController<'UISearchDisplayDelegate,UISearchBarDelegate,UITableViewDelegate,UITableViewDataSource> {
IBOutlet UISearchBar* mySearchBar;
UISearchDisplayController* mySearchDisplayController;
NSMutableArray* allEmp;
NSMutableArray* filteredEmp;
BOOL isFilatering;
}
#end
Now in .m file
-(void)viewDidLoad
{
[super viewDidLoad];
mySearchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:mySearchBar contentsController:self];
mySearchDisplayController.searchResultsDelegate = self;
mySearchDisplayController.searchResultsDataSource = self;
mySearchDisplayController.delegate = self;
isFilatering = NO;
filteredEmp = [[NSMutableArray alloc] init];
allEmp = [[NSMutableArray alloc] init];
NSDictionary *temp;
for (int i = 0; i < 30; i++) {
temp = [NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:#"FirstName_%d",i],#"FirstName",[NSString stringWithFormat:#"LastName_%d",i],#"LastName",[NSNumber numberWithInt:1000+i],#"EmpCode",nil];
[allEmp addObject:temp];
}
}
for e.g purpose i have thake an array of 30 Dictionaries, each with a unique name.For table view data source and delegate .....
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (isFilatering) {
return [filteredEmp count];
}
else {
return [allEmp count];
}
return [allEmp count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] init] autorelease];
}
NSDictionary* temp;
if (isFilatering) {
temp = [filteredEmp objectAtIndex:indexPath.row];
}
else {
temp = [allEmp objectAtIndex:indexPath.row];
}
cell.textLabel.text = [NSString stringWithFormat:#"%# %#",[temp objectForKey:#"FirstName"],[temp objectForKey:#"LastName"]];
return cell;
}
Now as far as searching is concerned here is what u need to do .......
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
NSPredicate* predicate = [[NSPredicate predicateWithFormat:#"self.FirstName contains %#",mySearchBar.text] retain];
filteredEmp = [[allEmp filteredArrayUsingPredicate:predicate] retain];
}
-(void) searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller
{
isFilatering = YES;
}
-(void) searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller
{
isFilatering = NO;
}
I hope that helps you understanding the things, dont forget to add retain in line
filteredEmp = [[allEmp filteredArrayUsingPredicate:predicate] retain];
because method filteredArrayUsingPredicate: is an accessor method and its retain count is handled by the NSArray so have access to the filtered array u need to pass a retain msg to it.

Related

UISearch not showing results

I'm querying from parse and I when I try to search using the search bar it returns with "no results." I'm trying to figure out what I did wrong showing the objects because it does show in the NSLog that it has found objects.
My viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
self.searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 44)];
self.tableView.tableHeaderView = self.searchBar;
self.searchController = [[UISearchDisplayController alloc] initWithSearchBar:self.searchBar contentsController:self];
self.searchController.searchResultsDataSource = self;
self.searchController.searchResultsDelegate = self;
self.searchController.delegate = self;
self.searchResults = [NSMutableArray array];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
The Retrieving for the Table View:
-(void)filterResults:(NSString *)searchTerm {
[self.searchResults removeAllObjects];
PFQuery *query = [PFQuery queryWithClassName:#"New"];
[query whereKeyExists:#"title"]; //this is based on whatever query you are trying to accomplish
[query whereKey:#"title" containsString:searchTerm];
NSArray *results = [query findObjects];
NSLog(#"%u", results.count);
[self.searchResults addObjectsFromArray:results];
}
Finally, The tableview cell that doesn't want to work:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
PFObject * testObject = [self.searchResults objectAtIndex:indexPath.row];
cell.textLabel.text = [testObject objectForKey:#"title"];
return cell;
}
Then:
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
[self filterResults:searchString];
return YES;
}
Then to load from Parse to UTTableview:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithClassName:#"New"];
self = [super initWithCoder:aDecoder];
if (self) {
// The className to query on
self.ClassName = #"New";
//self.textKey = #"title";
// The key of the PFObject to display in the label of the default cell style
// self.keyToDisplay = #"text";
// Whether the built-in pull-to-refresh is enabled
self.pullToRefreshEnabled = NO;
// Whether the built-in pagination is enabled
self.paginationEnabled = YES;
self.loadingViewEnabled = NO;
// The number of objects to show per page
self.objectsPerPage = 50;
}
return self;
}
You can use another way where you can take the help of the search bar delegate method.
e.g - Include UISearchBarDelegate in your header file and also -
#property (strong, nonatomic) IBOutlet UISearchBar *searchSong;
Now in your .m file
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
NSMutableArray *temp = [[NSMutableArray alloc] init];
temp = [self searchSongsData:self.searchSong.text from:`YourArray`];
}
+ (NSMutableArray *) searchSongsData:(NSString *)searchtext from:(NSMutableArray *)array
{
NSMutableArray *arrayToBeReturned = [[NSMutableArray alloc] init];
NSArray *temp = [[NSArray alloc] initWithArray:array];
NSString *textToBeSearch = [searchtext lowercaseString];
SongsMoviesData *songs;
for(int i = 0; i<temp.count; i++)
{
songs = [temp objectAtIndex:i];
NSString *string1 = [songs.song_title lowercaseString];
NSString *string2 = [songs.moview_name lowercaseString];
NSString *string3 = [songs.singer_name lowercaseString];
if ([string1 rangeOfString:textToBeSearch].location != NSNotFound ||[string2 rangeOfString:textToBeSearch].location != NSNotFound || [string3 rangeOfString:textToBeSearch].location != NSNotFound)
{
// NSLog(#"%#", songs.moview_name);
[arrayToBeReturned addObject:songs];
}
}
return arrayToBeReturned;
}
and like this you save this array in your search results.

Access contact image from address book based on name

Err,I have been pulling my hair thinking about a way from quite a few days.I have retrieved all contacts names and placed in an array using dictionary.
What I have is a model class holding a list of names,now I want to search the location of name in contacts list,depending on which I can retrieve the required contact image.
Initially googled and found out an unanswered question not pretty much similar to my requirement,the same can be glanced here
I tried several ways,the below is one way I have implemented:
EDIT
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
[self loadReminders];
ReminderClass *reminderToDisplay = [self.remindersArray objectAtIndex:indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
// Now create the cell to display the reminder data
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellIdentifier] autorelease];
cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;
cell.textLabel.numberOfLines = 0;
cell.textLabel.font = [UIFont fontWithName:#"Helvetica" size:17.0];
cell.textLabel.adjustsFontSizeToFitWidth = YES;
}
tableView.backgroundColor = [UIColor clearColor];
NSDateFormatter *dateFormat = [[[NSDateFormatter alloc]init]autorelease];
[dateFormat setDateFormat:kDateFormat];
NSDate *reminderDate = [dateFormat dateFromString:reminderToDisplay.Date];
[dateFormat setDateFormat:kMinDateFormat];
NSString *dateString = [dateFormat stringFromDate:reminderDate];
NSString *valueString = [NSString stringWithFormat:#"%#'s %#",reminderToDisplay.Name,reminderToDisplay.Event];
NSString *onString = [NSString stringWithFormat:#" on %#",dateString];
NSString *reminderDetailsString = [valueString stringByAppendingString:onString];
//Get the contact image based on name index from contact list
ABAddressBookRef addressBook = ABAddressBookCreate( );
CFStringRef reminderName = (CFStringRef)reminderToDisplay.Name;
CFArrayRef allPeople = ABAddressBookCopyPeopleWithName(addressBook, reminderName);
self.contactsList =[[[NSMutableArray alloc]init]autorelease];
CFIndex nPeople = ABAddressBookGetPersonCount(addressBook);
for ( int i = 0; i < nPeople; i++ )
{
ABRecordRef ref = CFArrayGetValueAtIndex(allPeople,i);
NSString *contactFirstNamePart = (NSString *)ABRecordCopyValue(ref,kABPersonFirstNameProperty);
NSString *contactFirstName = [[[NSString alloc] initWithString:contactFirstNamePart]autorelease];
NSString *contactLastNamePart = (NSString *)ABRecordCopyValue(ref, kABPersonLastNameProperty);
if (contactLastNamePart == nil)
{
self.contactName = contactFirstName;
}
else
{
NSString *contactLastName = [[[NSString alloc] initWithString:contactLastNamePart]autorelease];
NSString *contactLastNameString = [NSString stringWithFormat:#" %#",contactLastName];
self.contactName = [contactFirstName stringByAppendingString:contactLastNameString];
CFRelease(contactLastNamePart);
}
NSDictionary *contactsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:self.contactName, kContactName, [NSNumber numberWithInt:i], kContactIndex, nil];
[self.contactsList addObject:contactsDictionary];
CFRelease(contactFirstNamePart);
}
NSDictionary *contactsDictionary = [self.contactsList objectAtIndex:indexPath.row];
self.contactName = [contactsDictionary objectForKey:kContactName];
int addressIndex = [[contactsDictionary objectForKey:kContactIndex]integerValue];
ABRecordRef recordReference = CFArrayGetValueAtIndex(allPeople, addressIndex);
if (ABPersonHasImageData(recordReference))
{
NSData *imageData = (NSData *)ABPersonCopyImageData(recordReference);
self.reminderImage = [UIImage imageWithData:imageData];
CFRelease(imageData);
}
CFRelease(allPeople);
CFRelease(addressBook);
UIImage *notificationImage = reminderImage;
if (notificationImage != nil)
{
UIImageView *imageView=[[[UIImageView alloc] initWithFrame:CGRectMake(240, 3, 70, 63)]autorelease];
imageView.backgroundColor=[UIColor clearColor];
[imageView setImage:notificationImage];
cell.accessoryView = imageView;
}
else
{
UIImageView *imageView=[[[UIImageView alloc] initWithFrame:CGRectMake(240, 3, 70, 63)]autorelease];
imageView.backgroundColor=[UIColor clearColor];
UIImage *defaultImage = [UIImage imageNamed:kDefaultImage];
[imageView setImage:defaultImage];
cell.accessoryView = imageView;
}
cell.textLabel.text = reminderDetailsString;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
Bad Access Error Screen shot
But I was unable to accomplish the required task.Can any one please guide me.
Thanks all in advance :)
I am sharing the sample code snippet I used in one of my recent app. I have modified to fit it ur requirements and also please note that I have edited this in notepad and may have some typo errors.(Currently I dnt have mac to test it..:P)
Basic idea is to fill the datasource in viewDidLoad method and use that dataSource to update the tableView. Hope this will be an input to solve your problem.
viewDidLoad
contactsToBeAdded=[[NSMutableArray alloc] init];
ABAddressBookRef addressbook = ABAddressBookCreate();
CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeople(addressbook);
CFIndex numPeople = ABAddressBookGetPersonCount(addressbook);
bool hasPhoneNumber = false;
for (int i=0; i < numPeople; i++) {
hasPhoneNumber = false;
ABRecordRef person = CFArrayGetValueAtIndex(allPeople, i);
ABMutableMultiValueRef phonelist = ABRecordCopyValue(person, kABPersonPhoneProperty);
CFIndex numPhones = ABMultiValueGetCount(phonelist);
if(numPhones > 0){
hasPhoneNumber = true;
}
if(hasPhoneNumber){
NSString *firstName=(NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
NSString *lastName=(NSString *)ABRecordCopyValue(person, kABPersonLastNameProperty);
CFTypeRef ABphone = ABMultiValueCopyValueAtIndex(phonelist, 0);
NSString *personPhone = (NSString *)ABphone;
NSMutableDictionary *dictToAdd = [[[NSMutableDictionary alloc]init]autorelease];
if(firstName != nil && firstName != NULL){
[dictToAdd setObject:firstName forKey:#"firstName"];
CFRelease(firstName);
}
else{
[dictToAdd setObject:#"" forKey:#"firstName"];
}
if(lastName != nil && lastName != NULL){
[dictToAdd setObject:lastName forKey:#"lastName"];
CFRelease(lastName);
}
else{
[dictToAdd setObject:#"" forKey:#"lastName"];
}
if(personPhone != nil && personPhone != NULL){
[dictToAdd setObject:personPhone forKey:#"mobile"];
CFRelease(ABphone);
}
else{
[dictToAdd setObject:#"" forKey:#"mobile"];
}
//Get the first name and last name added to dict and combine it to full name
NSString *firstName = [dictToAdd objectForKey:#"firstName"];
NSString *lastName = [dictToAdd objectForKey:#"lastName"];
NSString *fullName = [firstName stringByAppendingString:lastName];
//Now check whether the full name is same as your reminderToDisplay.Name
if(reminderToDisplay.Name isEqualToString:fullName )
{
CFDataRef imageData = ABPersonCopyImageData(person);
UIImage *image = [UIImage imageWithData:(NSData *)imageData];
if(image != nil && image != NULL){
[dictToAdd setObject:image forKey:#"image"];
CFRelease(imageData);
}
else{
[dictToAdd setObject:[UIImage imageNamed:TEMP_IMG] forKey:#"image"];
}
}
[contactsToBeAdded addObject:dictToAdd];
}
CFRelease(phonelist);
}
CFRelease(allPeople);
CFRelease(addressbook);
[self.tableView reloadData];
numberOfRowsInSection
return contactsToBeAdded.count;
cellForRowAtIndexPath
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]autorelease];
}
NSDictionary *contactToAdd;
//This way you can get the data added in viewDidLoad method
contactToAdd = [contactsToBeAdded objectAtIndex:indexPath.row];
NSString *fName = (NSString *)[contactToAdd objectForKey:#"firstName"];
NSString *lName = (NSString *)[contactToAdd objectForKey:#"lastName"];
UIImage *contactImg = (UIImage*)[contactToAdd objectForKey:#"image"];
when you use 'ABAddressBookCopyArrayOfAllPeople' you get an array of persons in the addressbook. I believe they are type of ABPerson.
You can now loop over them list like you are. For each one record call 'ABPersonCopyImageData' and that will give you the image data as a CFDataRef.
And remember CFDataRef is a tool free bridge to NSData.
Just try changing your code like this. You are adding dictionary items to ur contactsList, so get each dictionary and check whether it contains a key matching your reminderToDisplay.Name, if yes then do ur stuff..
for (NSDictionary* dict in contactsList)
{
if(nil != [dict objectForKey:reminderToDisplay.Name])
{
//take image here
}
}
UPDATE:
//This is the reminder's name you want to get contact image
ReminderClass *reminderToDisplay = [self.remindersArray objectAtIndex:indexPath.row];
//Here you are adding your contacts with full name as object and kName as key, but check ur kName here
NSDictionary *contactsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:self.contactName, kName, [NSNumber numberWithInt:i], kIndex, nil];
[self.contactsList addObject:contactsDictionary];
//I dont think this part is needed in ur code
NSDictionary *contactsDictionary = [self.contactsList objectAtIndex:indexPath.row];
self.contactName = [contactsDictionary objectForKey:kName];
int addressIndex = [[contactsDictionary objectForKey:kIndex]intValue];
Now you have your contact names as a dictionary in contactsList array, iterate the array and check whether the dictionary contains your reminderToDisplay.Name as key.
for (NSDictionary* dict in contactsList)
{
//Please note that your dict contains key as kName and object as contact name.
if(nil != [dict objectForKey:reminderToDisplay.Name])
{
}
}
Also, I feel like you can do this in one single loop, like when you are iterating the addressbook itself, you can check whether the contact name is in your reminderlist and if available then extract image.
Hope this helps..all the best...

Dynamic arrays as section headers

I have a main array, that contains a whole bunch of dictionaries, what I want to do is to have all those dictionaries sorted according to their assigned tag. This is how a dictionary might look:
date = "2012-12-04 20:26:04 +0000";
name = H;
tag = "#J";
Heres how the main array looks:
MAIN_ARRAY
- dict1
- dict2
- dict3
I want to sort the main array like this:
MAIN_ARRAY
- tag1
- dict1
- dict2
- tag2
- dict3
Heres my code:
-(NSArray *)returnTagContent {
NSArray *tags = [all valueForKey:#"tag"];
NSMutableArray *adoptTags = [[[NSMutableArray alloc] init] autorelease];
for (NSString *tagQuery in tags) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"tag CONTAINS[cd] %#", tagQuery];
NSArray *roughArray = [all filteredArrayUsingPredicate:predicate];
NSArray *tagContent = [[NSSet setWithArray:roughArray] allObjects];
[adoptTags addObject:tagContent];
}
return adoptTags;
}
It returns the array, but now I want to organize it into section headers. How should I go about this?
I also have another piece of code with problem for returning the section header titles:
-(NSString *)returnTitleForTags {
NSString *uniqueTag = nil;
for (NSArray *tagContent in allTags) {
uniqueTag = [[[tagContent valueForKey:#"tag"] allObjects] lastObject];
}
return uniqueTag;
}
Problem? Well, I know it's because of lastObject but any other ideas to retrieve a NSString object of the array.
UPDATE: New code changes.
I update the array to display the sections when clicked by a button so like this:
isTagFilterOn=YES;
[self loadSectionsArray];
[self.tableView reloadData];
Heres the code for cellForRowAtIndexPath:
if (isTagFilterOn==YES) {
NSDictionary *dict = [[sectionsArray objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
cell.textLabel.text = [dict valueForKey:#"name"];
cell.detailTextLabel.text = [dict valueForKey:#"date"];
}
else {
NSString *object = all[indexPath.row];
cell.textLabel.text = [object valueForKey:#"name"];
cell.detailTextLabel.text = [object valueForKey:#"tag"];
}
The rest
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
if (isTagFilterOn==YES) {
return [sectionsArray count];
}
else {
return 1;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (isTagFilterOn==YES) {
return [[sectionsArray objectAtIndex:section] count];
}
return all.count;
}
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
if (isTagFilterOn==YES) {
NSDictionary *dict = [[sectionsArray objectAtIndex:section] objectAtIndex:0];
return [dict objectForKey:#"tag"];
}
return nil;
}
I think your task becomes easier if you remove duplicate "tags" before you create the array for the table view data source:
// All tags:
NSArray *tags = [mainArray valueForKey:#"tag"];
// Remove duplicates and sort:
tags = [[[NSSet setWithArray:tags] allObjects] sortedArrayUsingSelector:#selector(compare:)];
// Build an "array of arrays (of dictionaries)" as data source:
sectionsArray = [NSMutableArray array];
for (NSString *tag in tags) {
NSPredicate *pred = [NSPredicate predicateWithFormat:#"tag == %#", tag];
NSArray *onesection = [mainArray filteredArrayUsingPredicate:pred];
[sectionsArray addObject:onesection];
}
For example, if the mainArray is
(
{ date = "2012-12-04 20:26:04 +0000"; name = H; tag = "#J"; },
{ date = "2013-12-04 20:26:04 +0000"; name = X; tag = "#J"; },
{ date = "2014-12-04 20:26:04 +0000"; name = Z; tag = "#L"; }
)
then sectionsArray will be
(
(
{ date = "2012-12-04 20:26:04 +0000"; name = H; tag = "#J"; },
{ date = "2013-12-04 20:26:04 +0000"; name = X; tag = "#J"; }
),
(
{ date = "2014-12-04 20:26:04 +0000"; name = Z; tag = "#L"; }
)
)
and you can easily access each section and each row within a section:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [sectionsArray count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[sectionsArray objectAtIndex:section] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = ...;
}
NSDictionary *dict = [[sectionsArray objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
cell.textLabel.text = [dict objectForKey:#"name"];
cell.detailTextLabel.text = [dict objectForKey:#"date"];
return cell;
}
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSDictionary *dict = [[sectionsArray objectAtIndex:section] objectAtIndex:0];
return [dict objectForKey:#"tag"];
}

Problem with releasing of object

I have navigation application with 3 levels of hierarchy. My first level is tabel view controller. The nib file of this controller is "TableView". I have problem here. My code is this:
RootViewController
#import "RootViewController.h"
#import "SubCategory.h"
#import "OffersViewController.h"
#implementation RootViewController
#synthesize subCategories;
#synthesize offersView;
- (void)dealloc
{
NSLog(#"root dealloc");
//[subCategories release];
[super dealloc];
}
- (void)viewDidLoad
{
NSLog(#"root view load");
[super viewDidLoad];
self.title = #"Sub Categories";
NSString *jsonArray = [NSString stringWithFormat:#"{ "
#" \"sub-categories\": { "
#" \"parent\": \"1\", "
#" \"count\": \"2\", "
#" \"sub-category\": [{ "
#" \"id\": \"1\", "
#" \"name\": \"Buy\" "
#" }, "
#" { "
#" \"id\": \"2\", "
#" \"name\": \"Sell\" "
#" }] "
#" } "
#" }"];
SubCategory* categories = [[SubCategory alloc] init];
subCategories = categories;
[subCategories parseJSON:jsonArray];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSLog(#"root sections");
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"root numberOfRows");
return [subCategories.subCategoryName count];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"root didSelectRow");
OffersViewController * offers = [[OffersViewController alloc] initWithNibName:#"OffersView" bundle:nil];
offersView = offers;
// I have exception on this row. exception is:
//-[__NSArrayI objectAtIndex:]: message sent to deallocated instance 0x4b04c00
offersView.title = [NSString stringWithFormat:#"%#", [subCategories.subCategoryName objectAtIndex:indexPath.row]];
[self.navigationController pushViewController:offersView animated:YES];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"root cellForRow");
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cachedCell"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] init] autorelease];
}
cell.textLabel.text = [subCategories.subCategoryName objectAtIndex:indexPath.row];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
#end
SubCategory
#import "SubCategory.h"
#import "JSON.h"
#implementation SubCategory
#synthesize parentId;
#synthesize count;
#synthesize subCategoryId;
#synthesize subCategoryName;
- (void) parseJSON: (NSString*) jsonArray {
NSDictionary *results = [jsonArray JSONValue];
NSString *parent = (NSString*) [[results objectForKey:#"sub-categories"] objectForKey:#"parent"];
parentId = [parent intValue];
NSString *cnt = (NSString*) [[results objectForKey:#"sub-categories"] objectForKey:#"count"];
self.count = [cnt intValue];
NSDictionary *subCategories = [[results objectForKey:#"sub-categories"] objectForKey:#"sub-category"];
NSMutableArray *namesArray = [[NSMutableArray alloc] initWithCapacity:[subCategories count]];
NSMutableArray* idArray = [[NSMutableArray alloc] initWithCapacity:[subCategories count]];
for (NSDictionary *subCategory in subCategories) {
[idArray addObject:[subCategory objectForKey:#"id"]];
[namesArray addObject:[subCategory objectForKey:#"name"]];
}
subCategoryId = [NSArray arrayWithArray:idArray];
subCategoryName = [NSArray arrayWithArray:namesArray];
[idArray release];
[namesArray release];
//[parent release];
//[cnt release];
}
#end
I don't know why my object is released. Can someone help me.
EDIT: Added SubCategory code
Here's the problem:
NSMutableArray *namesArray = [[NSMutableArray alloc] initWithCapacity:[subCategories count]];
NSMutableArray* idArray = [[NSMutableArray alloc] initWithCapacity:[subCategories count]];
...
subCategoryId = [NSArray arrayWithArray:idArray];
subCategoryName = [NSArray arrayWithArray:namesArray];
[idArray release];
[namesArray release];
At this point, subCategoryId and subCategoryName are autoreleased. This means they won't be accessible after the parseJSON: method finishes. You have two options:
// Because idArray and namesArray are already retained (you alloc'd them)
subCategoryId = idArray;
subCategoryName = namesArray;
or:
...
// this is equivalent to what you have now, except these two will be retained.
// this is different from the above because subCategoryId and subCategoryName are now NSArrays, whereas above they were NSMutableArrays.
subCategoryId = [idArray copy];
subCategoryName = [namesArray copy];
...
The scope of SubCategory categories is only your viewDidLoad. When you make a shallow copy, it/or the copy will not be available outside your scope, you need to retain it when assigning it to subCategories like
subCategories = [categories retain];
Also, you can forego using the SubCategory categories at all. Instead do:
subCategories = [[SubCategory alloc] init];
[subCategories parseJSON:jsonArray]; }

Obj-C, iOS, How do I sort by value and not key, sortedArrayUsingSelector, currently #selector(compare:)]

I need to sort by value instead of Key, I think....
Heres where I populate my arrarys
const char *sql = "select cid, category from Categories ORDER BY category DESC";
sqlite3_stmt *statementTMP;
int error_code = sqlite3_prepare_v2(database, sql, -1, &statementTMP, NULL);
if(error_code == SQLITE_OK) {
while(sqlite3_step(statementTMP) == SQLITE_ROW)
{
int cid = sqlite3_column_int(statementTMP, 0);
NSString *category = [[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statementTMP, 1)];
NSArray *arr=[[NSArray alloc]initWithObjects:category,nil];
[arrayTmp setObject:arr forKey:[NSString stringWithFormat:#"%i",cid]];
[self.cidList addObject:[NSString stringWithFormat:#"%i",cid]];
[category release];
[arr release];
}
}
sqlite3_finalize(statementTMP);
sqlite3_close(database);
self.allCategories = arrayTmp;
[arrayTmp release];
Heres the method where the arrays are re-sorted.
- (void)resetSearch {
NSMutableDictionary *allCategoriesCopy = [self.allCategories mutableDeepCopy];
self.Categories = allCategoriesCopy;
[allCategoriesCopy release];
NSMutableArray *keyArray = [[NSMutableArray alloc] init];
[keyArray addObject:UITableViewIndexSearch];
[keyArray addObjectsFromArray:[[self.allCategories allKeys]
sortedArrayUsingSelector:#selector(compare:)]];
self.keys = keyArray;
[keyArray release];
}
This is a problem i've had for some time, last time I looked at this I could find an altervative to sortedArrayUsingSelector compare?
EDIT
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger section = [indexPath section];
NSUInteger row = [indexPath row];
NSString *key = [keys objectAtIndex:section];
NSArray *nameSection = [Categories objectForKey:key];
static NSString *SectionsTableIdentifier = #"SectionsTableIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:
SectionsTableIdentifier ];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier: SectionsTableIdentifier ] autorelease];
}
cell.textLabel.text = [nameSection objectAtIndex:row];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger section = [indexPath section];
NSUInteger row = [indexPath row];
NSString *key = [keys objectAtIndex:section];
NSArray *nameSection = [Categories objectForKey:key];
NSLog(#"the selected cid is = %i",[key intValue]);
selectButton.enabled = YES;
}
Anyone?
Your obviously attempting to construct an array for use in the -[UITableviewDatasource sectionIndexTitlesForTableView:]. As such, you need an array that looks like this (pseudo-code):
[UITableViewIndexSearch, 0_sectionTitle, 1_sectionTitle, 2_sectionTitle, ...]
I think your immediate problem is that you try to add the UITableViewIndexSearch string constant to the array before you sort which makes it impossible for it end up as the first element unless all your other elements sort below U.
The fix is simple, just add the constant after the sort. You can clean the code up while you're at it:
NSMutableArray *secIdx=[NSMutableArray arrayWithCapacity:[[self.allCategories allKeys] count]];
[secIdx addObjectsFromArray:[self.allCategories allKeys]];
[secIdx sortUsingSelector:#selector(compare:)];
[secIdx insertObject:UITableViewIndexSearch atIndex:0];
self.keys=secIdx;
Note that secIdx is autoreleased so you don't have to release it.
Aside from this problem, your code has a lot of unnecessary/dangerous elements that will make your app fragile and hard to maintain.
You are using a lot of init for objects that you could use autoreleased convenience methods for. The 'init`s poise the risk of memory leaks but give you no advantage.
You need to wrap scalar values in objects so they can be easily managed in collections.
You are using an unnecessary array.
You can rewrite the first block like so:
const char *sql = "select cid, category from Categories ORDER BY category DESC";
sqlite3_stmt *statementTMP;
int error_code = sqlite3_prepare_v2(database, sql, -1, &statementTMP, NULL);
if(error_code == SQLITE_OK) {
NSNumber *cidNum; //... move variable declerations outside of loop
NSString *category; //.. so they are not continously recreated
[self.allCategories removeAllObjects]; //... clears the mutable dictionary instead of replacing it
while(sqlite3_step(statementTMP) == SQLITE_ROW){
cidNum=[NSNumber numberWithInt:(sqlite3_column_int(statementTMP, 0))];
category=[NSString stringWithUTF8String:(char *)sqlite3_column_text(statementTMP, 1)];
//... adding the autoreleased category and cidNum to array/dictionary automatically retains them
[self.allCategories addObject:category forKey:cidNum];
[self.cidList addObject:cidNum];
//[category release]; ... no longer needed
//[arr release]; ... no longer needed
}
}
sqlite3_finalize(statementTMP);
sqlite3_close(database);
//self.allCategories = arrayTmp; ... no longer needed
//[arrayTmp release]; ... no longer needed
Use -sortedArrayUsingComparator: (or -sortedArrayUsingFunction:context: if you can't use blocks). Example:
NSDictionary *categories = [self allCategories];
NSArray *keysSortedByValue = [[categories allKeys] sortedArrayUsingComparator:
^(id left, id right) {
id lval = [categories objectForKey:left];
id rval = [categories objectForKey:right];
return [lval compare:rval];
}];
You could make a small model class Category and implement compare inside of it, then sort an array of those objects using that compare:.
Here's some info - How to sort an NSMutableArray with custom objects in it?
Perhaps you're looking for NSSortDescriptor (and the corresponding sort method, -[NSArray sortedArrayUsingDescriptors]) and friends?
If I understood correctly then what you wish to do to get categories from database & display it on a tableView with alphabetical sorting, index on right & search bar on top. Ideally, you would like to display the Contacts application kind of a view. If that's correct, use below code for fetching items from DB & rebuilding (or resetting) it -
const char *sql = "select cid, category from Categories ORDER BY category DESC";
sqlite3_stmt *statementTMP;
NSMutableArray *arrayTmp = [[NSMutableArray alloc] init];
int error_code = sqlite3_prepare_v2(database, sql, -1, &statementTMP, NULL);
if(error_code == SQLITE_OK) {
while(sqlite3_step(statementTMP) == SQLITE_ROW) {
int cid = sqlite3_column_int(statementTMP, 0);
NSString *category = [[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statementTMP, 1)];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:category forKey:#"Category"];
[dict setObject:[NSNumber numberWithInt:cid] forKey:#"CID"];
[arrayTmp addObject:dict];
[dict release];
[category release];
}
}
sqlite3_finalize(statementTMP);
sqlite3_close(database);
self.allCategories = arrayTmp;
[arrayTmp release];
And then rebuild the items using this function -
- (void)rebuildItems {
NSMutableDictionary *map = [NSMutableDictionary dictionary];
for (int i = 0; i < allCategories.count; i++) {
NSString *name = [[allCategories objectAtIndex:i] objectForKey:#"Category"];
NSString *letter = [name substringToIndex:1];
letter = [letter uppercaseString];
if (isdigit([letter characterAtIndex:0]))
letter = #"#";
NSMutableArray *section = [map objectForKey:letter];
if (!section) {
section = [NSMutableArray array];
[map setObject:section forKey:letter];
}
[section addObject:[allCategories objectAtIndex:i]];
}
[_items release];
_items = [[NSMutableArray alloc] init];
[_sections release];
_sections = [[NSMutableArray alloc] init];
NSArray* letters = [map.allKeys sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
for (NSString* letter in letters) {
NSArray* items = [map objectForKey:letter];
[_sections addObject:letter];
[_items addObject:items];
}
}
Now, displaying items in tableView, use below methods -
#pragma mark -
#pragma mark Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView {
if (_sections.count)
return _sections.count;
else
return 1;
}
- (NSInteger)tableView:(UITableView*)tableView sectionForSectionIndexTitle:(NSString *)title
atIndex:(NSInteger)index {
if (tableView.tableHeaderView) {
if (index == 0) {
[tableView scrollRectToVisible:tableView.tableHeaderView.bounds animated:NO];
return -1;
}
}
NSString* letter = [title substringToIndex:1];
NSInteger sectionCount = [tableView numberOfSections];
for (NSInteger i = 0; i < sectionCount; i++) {
NSString* section = [tableView.dataSource tableView:tableView titleForHeaderInSection:i];
if ([section hasPrefix:letter]) {
return i;
}
}
if (index >= sectionCount) {
return sectionCount-1;
} else {
return index;
}
}
- (NSArray*)lettersForSectionsWithSearch:(BOOL)withSearch withCount:(BOOL)withCount {
if (isSearching)
return nil;
if (_sections.count) {
NSMutableArray* titles = [NSMutableArray array];
if (withSearch) {
[titles addObject:UITableViewIndexSearch];
}
for (NSString* label in _sections) {
if (label.length) {
NSString* letter = [label substringToIndex:1];
[titles addObject:letter];
}
}
if (withCount) {
[titles addObject:#"#"];
}
return titles;
} else {
return nil;
}
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return [self lettersForSectionsWithSearch:YES withCount:NO];
}
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
if (_sections.count) {
NSArray* items = [_items objectAtIndex:section];
return items.count;
} else {
return _items.count;
}
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
if (_sections.count)
return [_sections objectAtIndex:section];
return nil;
}
- (id)tableView:(UITableView *)tableView objectForRowAtIndexPath:(NSIndexPath *)indexPath {
if (_sections.count) {
NSArray *section = [_items objectAtIndex:indexPath.section];
return [section objectAtIndex:indexPath.row];
} else {
return [_items objectAtIndex:indexPath.row];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Create your UITableViewCell.
// Configure the cell.
NSDictionary *dict = [self tableView:tableView objectForRowAtIndexPath:indexPath];
cell.textLabel.text = [dict objectForKey:#"Category"];
cell.detailTextLabel.text = [NSString stringWithFormat:%d, [[dict objectForKey:#"CID"] intValue]];
return cell;
}
#pragma mark -
#pragma mark Table view delegate
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if (isSearching)
return nil;
NSString *title = #"";
if (_sections.count) {
title = [[_sections objectAtIndex:section] substringToIndex:1];
} else {
return nil;
}
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 20)];
view.backgroundColor = [UIColor colorWithRed:(58/255.0) green:(27/255.0) blue:(6/255.0) alpha:1.0];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, 1, 50, 18)];
label.textColor = [UIColor whiteColor];
label.backgroundColor = [UIColor clearColor];
label.font = [UIFont boldSystemFontOfSize:17.0];
label.text = title;
[view addSubview:label];
[label release];
return [view autorelease];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *dict = [self tableView:tableView objectForRowAtIndexPath:indexPath];
NSLog(#"selected row id:%d, name:%#", [dict objectForKey:#"Category"], [[dict objectForKey:#"CID"] intValue]);
}
The rest part is implementing the UISearchBarDelegate and implementing searching of tableView which can be done using below code:
- (void)searchBar:(UISearchBar *)searchbar textDidChange:(NSString *)searchText {
[_sections removeAllObjects];
[_items removeAllObjects];
if([searchText isEqualToString:#""] || searchText == nil) {
[self rebuildItems];
return;
}
NSInteger counter = 0;
for(NSDictionary *dict in allCategories) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSRange r = [[dict objectForKey:#"Category"] rangeOfString:searchText options:NSCaseInsensitiveSearch];
if(r.location != NSNotFound) {
if(r.location == 0) {
[_items addObject:dict];
}
}
counter++;
[pool release];
}
[contactList reloadData];
}
Hope this is what you're looking for.
On your sorting function u should try this:
NSArray *cntxt; //im not sure this is the correct type that ur using on keyArray
[keyArray addObjectsFromArray:[self.allCategories allKeys]];
[keyArray sortUsingFunction:compareFunction context:cntxt];
And the compare function you modify to your needs
NSInteger compareFunction(id x, id y, void *context) {
//NSArray *ctxt = context;
NSArray *c1 = x;
NSArray *c2 = y;
if ([c1 value] < [c2 value])
return NSOrderedDescending;
else if ([c1 value] > [c2 value])
return NSOrderedAscending;
else
return NSOrderedSame;
}
Edit: After reading your comments and after relooking at your code, it seems like that your keyArray as objects of the type NSString, so you should change:
NSInteger compareFunction(id x, id y, void *context) {
//NSString *ctxt = context;
NSString *c1 = x;
NSString *c2 = y;
NSComparisonResult result;
result = [c1 compare:c2];
if (result<0)
return NSOrderedAscending;
else if (result>0)
return NSOrderedDescending;
else
return NSOrderedSame;
}