UISearchBarDelegate on UITableView with Sections Issue - objective-c

I'm having issues with implementing the UISearchBarDelegate on my UITableView with sections issue where every time I try to search for something inside the UITableView, either it doesn't display the desired result or it crashes the app with an error saying:
Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
Here's inside my header file:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UISearchDisplayDelegate, UISearchBarDelegate>
{
UITableView *mainTableView;
NSMutableArray *contentsList;
NSMutableArray *searchResults;
NSString *savedSearchTerm;
NSMutableArray *ivSectionKeys;
NSMutableDictionary *ivSectionContents;
}
#property (nonatomic, retain) IBOutlet UITableView *mainTableView;
#property (nonatomic, retain) NSMutableArray *contentsList;
#property (nonatomic, retain) NSMutableArray *searchResults;
#property (nonatomic, copy) NSString *savedSearchTerm;
#property (nonatomic, retain) NSMutableArray *sectionKeys;
#property (nonatomic, retain) NSMutableDictionary *sectionContents;
- (void)handleSearchForTerm:(NSString *)searchTerm;
#end
while this is inside my implementation file:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize mainTableView;
#synthesize contentsList;
#synthesize searchResults;
#synthesize savedSearchTerm;
#synthesize sectionKeys = ivSectionKeys;
#synthesize sectionContents = ivSectionContents;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSMutableArray *keys = [[NSMutableArray alloc] init];
NSMutableDictionary *contents = [[NSMutableDictionary alloc] init];
NSString *colorKey = #"Colors";
NSString *clothingKey = #"Clothing";
NSString *miscKey = #"Misc";
[contents setObject:[NSArray arrayWithObjects:#"Red", #"Blue", nil] forKey:colorKey];
[contents setObject:[NSArray arrayWithObjects:#"Pants", #"Shirt", #"Socks", nil] forKey:clothingKey];
[contents setObject:[NSArray arrayWithObjects:#"Wankle Rotary Engine", nil] forKey:miscKey];
[keys addObject:clothingKey];
[keys addObject:miscKey];
[keys addObject:colorKey];
self.sectionKeys = keys;
self.sectionContents = contents;
// Restore search term
if (self.savedSearchTerm)
{
self.searchDisplayController.searchBar.text = self.savedSearchTerm;
}
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Save the state of the search UI so that it can be restored if the view is re-created.
self.savedSearchTerm = self.searchDisplayController.searchBar.text;
self.searchResults = nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.mainTableView reloadData];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)handleSearchForTerm:(NSString *)searchTerm
{
self.savedSearchTerm = searchTerm;
if (self.searchResults == nil)
{
NSMutableArray *array = [[NSMutableArray alloc] init];
self.searchResults = array;
array = nil;
}
[self.searchResults removeAllObjects];
if ([self.savedSearchTerm length] != 0)
{
for (NSString *currentString in self.sectionContents)
{
if ([currentString rangeOfString:searchTerm options:NSCaseInsensitiveSearch].location != NSNotFound)
{
[self.searchResults addObject:currentString];
}
}
}
}
#pragma mark -
#pragma mark UITableViewDataSource Methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSInteger sections = [self.sectionKeys count];
return sections;
}
- (NSString *)tableView:(UITableView *)tableView
titleForHeaderInSection:(NSInteger)section
{
NSString *key = [self.sectionKeys objectAtIndex:section];
return key;
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
NSString *key = [self.sectionKeys objectAtIndex:section];
NSArray *contents = [self.sectionContents objectForKey:key];
NSInteger rows;
if (tableView == [[self searchDisplayController] searchResultsTableView])
rows = [self.searchResults count];
else
rows = [contents count];
NSLog(#"rows is: %d", rows);
return rows;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *key = [self.sectionKeys objectAtIndex:indexPath.section];
NSArray *contents = [self.sectionContents objectForKey:key];
NSString *contentForThisRow = [contents objectAtIndex:indexPath.row];
if (tableView == [self.searchDisplayController searchResultsTableView])
contentForThisRow = [self.searchResults objectAtIndex:indexPath.row];
else
contentForThisRow = [contents objectAtIndex:indexPath.row];
static NSString *CellIdentifier = #"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = contentForThisRow;
return cell;
}
#pragma mark -
#pragma mark UITableViewDelegate Methods
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
#pragma mark -
#pragma mark UISearchDisplayController Delegate Methods
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller
shouldReloadTableForSearchString:(NSString *)searchString
{
[self handleSearchForTerm:searchString];
// Return YES to cause the search result table view to be reloaded.
return YES;
}
- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller
{
self.savedSearchTerm = nil;
[self.mainTableView reloadData];
}
#end
I think my issue is inside my for-loop but I'm not really sure.

You need to update all of your table view data source and delegate methods to handle both tables (you only do this in some):
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSInteger sections = [self.sectionKeys count];
return sections;
}
should be:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if (tableView == self.tableView) {
NSInteger sections = [self.sectionKeys count];
return sections;
} else { // assume it's the search table
// replace this with the actual result
return numberOfSectionsForSearchTable;
}
}

Related

NSArray - storing and retrieving data for a table using a button

I am trying to store data into NSArray using a button and then retrieve it to be displayed in a table. However, the table remains empty after the button has been pushed. I think the problem might have to do with how the Car object is being added to the array and then stored in NSData.
CarTableViewController.h
#import <UIKit/UIKit.h>
#import "CarDetailViewController.h"
#import "Car.h"
#interface CarListTableViewController : UITableViewController <UITableViewDataSource, UITableViewDelegate>
#property Car *selectedCar;
#property NSMutableArray *listOfCars;
#property (strong, nonatomic) IBOutlet UITableView *carTableView;
#end
CarTableViewController.m
#import "CarListTableViewController.h"
#import "CarEntryViewController.h"
#implementation CarListTableViewController
#synthesize selectedCar, listOfCars, carTableView;
- (void)viewDidLoad {
[super viewDidLoad];
carTableView.delegate = self;
carTableView.dataSource = self;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#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 listOfCars.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"carCell" forIndexPath:indexPath];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"carCell"];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
cell.textLabel.numberOfLines = 0;
}
Car *tempCar =[listOfCars objectAtIndex:indexPath.row];
cell.textLabel.text =tempCar.make;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:true];
selectedCar = [listOfCars objectAtIndex:indexPath.row];
[self performSegueWithIdentifier:#"viewDetailsSegue" sender:nil];
}
#pragma mark - Navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
CarDetailViewController* vc = [segue destinationViewController];
vc.carObject = selectedCar;
}
- (NSArray*) retrieveDataFromNSUserDefaults {
NSMutableArray *objectArray = [NSMutableArray new];
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:#"savedArray"];
if (dataRepresentingSavedArray != nil) {
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
if (oldSavedArray != nil)
objectArray = [[NSMutableArray alloc]
initWithArray:oldSavedArray];
else
objectArray = [[NSMutableArray alloc] init];
}
return objectArray;
}
- (void)storeDataInNSUserDefaults:(Car *)carToStore {
NSMutableArray *objectArray = [NSMutableArray arrayWithArray:[self retrieveDataFromNSUserDefaults]];
[objectArray addObject:carToStore];
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:objectArray] forKey:#"savedArray"];
}
#end
your code looks fine to me but I think you are not calling method to add data [self storeDataInNSUserDefaults:car] anywhere.

Objective-C use of undeclared identifier 'numberOfSectionsInTableView'

Not sure where I went wrong. My understanding is that importing UIKit will enable use of the numberOfSectionsInTableView method... Any help is appreciated.
//HEADER
#import <UIKit/UIKit.h>
#interface MainScrollingTableVC : UITableViewController <UITableViewDelegate, UITableViewDataSource> {
int selectedIndex;
NSMutableArray *titleArray;
NSArray *subtitleArray;
NSArray *textArray;
}
#end
//IMPLEMENTATION
#import "MainScrollingTableVC.h"
#import "MainExpandingCellTableViewCell.h"
#interface MainScrollingTableVC ()
#property (strong, nonatomic) IBOutlet UITableView *tableView;
#end
#implementation MainScrollingTableVC
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.delegate = self;
self.tableView.dataSource = self;
//index = -1 means no cell is expanded or should expand
selectedIndex = -1;
titleArray = [[NSMutableArray alloc] init];
NSString *string;
//create consecutive array of 8 numbers. 1...2...etc...
for(int ii = 1; ii <= 8; ii++) {
string = [[NSString alloc] initWithFormat:#"Row %i", ii];
[titleArray addObject:string];
}
subtitleArray = [[NSArray alloc] initWithObjects:#"First row", #"Second row", #"Third row", #"Fourth row", #"Fifth row", #"Sixth row", #"Seventh row", #"Eigth row", nil];
textArray = [[NSArray alloc] initWithObjects:#"Apple", #"Orange", #"Grape", #"Banana", #"Kiwi", #"Blueberry", #"Apricot", #"Lemon", nil];
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return titleArray.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"MainExpandingCell";
MainExpandingCell *cell = (MainExpandingCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"MainExpandingCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
if(selectedIndex == indexPath.row) {
//Do expanded cell stuff
} else {
//Do closed cell stuff
}
cell.titleLabel.text = [titleArray objectAtIndex:indexPath.row];
cell.timeLabel.text = [subtitleArray objectAtIndex: indexPath.row]; //CHECK VARS
cell.textLabel.text = [textArray objectAtIndex:indexPath.row];
int etaLabel = (indexPath.row + 1) * 25;
cell.etaLabel.text = [NSString stringWithFormat:#"%i", eta]
cell.clipsToBounds = YES;
return cell;
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
You are declaring your methods inside the viewDidLoad method. Objective-C doesn't support method nesting. Fix your whole code with its formatting and add the missing } accordingly.

NSPredicate custom object pointer

I have interfaces like:
#interface Isle
#property (nonatomic, strong) id index_one; //has to be an id ={
#property (nonatomic, strong) id index_two;
#end
#interface Item
#property (nonatomic, strong) NSString* name; //has to be an NSString
#end
Then I instantiate a search view controller:
#import "TableViewController.h"
#import "Isle.h"
#import "Item.h"
#interface TableViewController () <UISearchBarDelegate, UISearchDisplayDelegate>
#property (nonatomic, strong) NSArray* isles;
#property (nonatomic, strong) NSArray* filter;
#end
#implementation TableViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self)
{
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
Isle* isle_one = [[Isle alloc] init];
Isle* isle_two = [[Isle alloc] init];
Isle* isle_three = [[Isle alloc] init];
isle_one.index_one = [[Item alloc] init];
isle_one.index_two = [[Item alloc] init];
isle_two.index_one = [[Item alloc] init];
isle_two.index_two = [[Item alloc] init];
isle_three.index_one = [[Item alloc] init];
isle_three.index_two = [[Item alloc] init];
((Item*)isle_one.index_one).name = #"ketchup";
((Item*)isle_one.index_two).name = #"hot sauce";
((Item*)isle_two.index_one).name = #"butter";
((Item*)isle_two.index_two).name = #"cheese";
((Item*)isle_three.index_one).name = #"cereal";
((Item*)isle_three.index_two).name = #"rice";
_isles = [[NSArray alloc] initWithObjects: isle_one, isle_two, isle_three, nil];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
NSPredicate* res = [NSPredicate predicateWithFormat:#"(index_one.name contains[cd] %#) OR (index_two.name contains[cd] %#)", #"ketchup", #"hot sauce"];
_filter = [self.isles filteredArrayUsingPredicate: res];
}
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
[self filterContentForSearchText:searchString scope:[[self.searchDisplayController.searchBar scopeButtonTitles] objectAtIndex:[self.searchDisplayController.searchBar selectedScopeButtonIndex]]];
return YES;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (tableView == self.searchDisplayController.searchResultsTableView)
{
return [[self filter] count];
}
return [[self isles] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:#"CellID" forIndexPath:indexPath];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"CellID"];
}
if (tableView == self.searchDisplayController.searchResultsTableView)
{
Isle* isle = (Isle*)[self.filter objectAtIndex: indexPath.row];
NSString* str = [NSString stringWithFormat:#"%#\t\t%#", ((Item*)isle.index_one).name, ((Item*)isle.index_two).name];
cell.textLabel.text = str;
return cell;
}
Isle* isle = (Isle*)[self.isles objectAtIndex: indexPath.row];
NSString* str = [NSString stringWithFormat:#"%# %#", ((Item*)isle.index_one).name, ((Item*)isle.index_two).name];
cell.textLabel.text = str;
return cell;
}
#end
But it says that it is not key-value compliant. Any ideas how I can accomplish this?
EDIT:
My exact error is:
Project1[1688:60b] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<NSSymbolicExpression 0x10e60a650> valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.'
For filtering I did:
[arr filteredArrayUsingPredicate: res];

Problems Populating TableView with NSMutableArray

I'm using SudzC for an iPhone app. I'm succesfully calling my ASP.Net webservice and pulling the needed data into an NSMutable array called tableData. I have a tableView that should display the contents of tableData; however this is not working. I've looked for hours at this problem. I'm very new to the Objective C world so I'm thinking it's a small oversight on my part. I've linked my TableView to the ViewController (delegate/datasource) as well.
Here's is the code I have:
#import "ViewController.h"
#import "DocumentServiceJSONService.h"
#interface ViewController ()
#end
#implementation ViewController
NSMutableArray *tableData;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
-(void)viewWillAppear:(BOOL) animated {
tableData = [[NSMutableArray alloc] init];
DocumentServiceJSONService* service = [[DocumentServiceJSONService alloc] init];
[service GetAllEMSDocumentsXMLAsString: self action:#selector(handleGetAllEMSDocuments:)];
[super viewWillAppear:animated];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void) handleGetAllEMSDocuments:(id) result {
if ([result isKindOfClass:[NSError class]]) {
//if an error has occurred, handle it
return;
}
if([result isKindOfClass: [SoapFault class]]) {
return;
}
NSString* xmlString = result;
CXMLDocument *xmlDoc = [[CXMLDocument alloc] initWithXMLString:xmlString options:0 error:nil];
NSArray *nodes = NULL;
nodes = [xmlDoc nodesForXPath:#"//EMSDocuments" error:nil];
for (CXMLElement *node in nodes) {
NSMutableDictionary *item = [[NSMutableDictionary alloc] init];
int counter;
for (counter = 0; counter < [node childCount]; counter++) {
[item setObject:[[node childAtIndex:counter] stringValue] forKey:[[node childAtIndex:counter] name]];
}
//[item setObject:[[node attributeForName:#"DisplayName"] stringValue] forKey:#"DisplayName"];
NSString *displayName = [item objectForKey:#"DisplayName"];
[tableData addObject:displayName];
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [tableData count];
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
cell.textLabel.text = [tableData objectAtIndex:indexPath.row];
return cell;
}
#end
Any advice is appreciated.
Make sure you have in your .h the delegate and datasource correctly set:
#interface YourViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
And if you are using XIBs for the user interface, that the tableview is connected to the File's Owner as datasource and delegate.
--
EDIT:
Creating a Outlet for you tableview:

Simple twitter program not working

I'm using this tutorial to practice creating an extremely basic twitter app: http://www.codeproject.com/Articles/312325/Making-a-simple-Twitter-app-using-iOS-5-Xcode-4-2#setting-up-the-table-view
The only difference in my app is I'm only using tableView ViewController. I can't seem to get this to work.
ViewController.h
#interface ViewController : UIViewController {
NSArray *tweets;
}
-(void)fetchTweets;
#property (retain, nonatomic) IBOutlet UITableView *tableView;
#end
ViewController.m
#import "ViewController.h"
#import "Twitter/Twitter.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize tableView = _tableView;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self fetchTweets];
}
- (void)fetchTweets
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData* data = [NSData dataWithContentsOfURL:
[NSURL URLWithString: #"https://api.twitter.com/1/statuses/public_timeline.json"]];
NSError* error;
tweets = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions
error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
});
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return tweets.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"TweetCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSDictionary *tweet = [tweets objectAtIndex:indexPath.row];
NSString *text = [tweet objectForKey:#"text"];
NSString *name = [[tweet objectForKey:#"user"] objectForKey:#"name"];
cell.textLabel.text = text;
cell.detailTextLabel.text = [NSString stringWithFormat:#"by %#", name];
return cell;
}
- (void)viewDidUnload
{
_tableView = nil;
[self setTableView:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
} else {
return YES;
}
}
#end
you forgot to define delegate & datasource for your table, and didn't implement the protocols right as far as i see in your code,
try in your .h file:
#interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
{
// your implementation...
}
and in your .m file in viewDidLoad
self.tableView.dataSource = self;
self.tableView.delegate = self;
the numberOfRows, cellForRow, etc... methods wont work until you define your delegate & datasource for this table :)