I have an iPad app using a split-view controller. Within the master view, I have a list of items displayed using a custom table cell defined in the storyboard.
The cells display as expected complete with the dark background I selected in Xcode.
I am using a UISearchBar and UISearchDisplayController.
When I begin a search, the list changes to standard, white table cells.
How can I get the SearchDisplayController to use my custom cells?
As I type in the search field, the callback updates a list of filtered results:
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller
shouldReloadTableForSearchString:(NSString *)searchString
{
[self filterAvailableChannelsForSearchText:searchString scope:[[self.searchDisplayController.searchBar scopeButtonTitles] objectAtIndex:[self.searchDisplayController.searchBar selectedScopeButtonIndex]]];
[self.searchDisplayController.searchBar selectedScopeButtonIndex]]];
return YES;
}
This is used by my table view handling to present either the full list or the filtered list. The CellIdentifier matches the identifier in the storyboard for the cell:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (tableView == self.searchDisplayController.searchResultsTableView)
return [self.filteredAvailableChannel count];
else
return [[self availableChannelList] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"AvailableChannelCell";
FVAvailableChannelCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
FVChannel *channel = nil;
int row = [indexPath row];
if (tableView == self.searchDisplayController.searchResultsTableView)
channel = [self.filteredAvailableChannel objectAtIndex:row];
else
channel = [[self availableChannelList] objectAtIndex:row];
cell.idLabel.text = channel.channelID;
cell.nameLabel.text = channel.channelName;
cell.unitLabel.text = channel.unitName;
return cell;
}
Why isn't my searchDisplayController using my custom cells?
UPDATED 31Jul12:
After double and triple checking all the storyboard connections (which seem to be correct), I noticed something…
I can see that I'm actually GETTING a custom cell - it just LOOKS like a standard cell.
After I fixed tableView:heightForRowAtIndexPath: I noticed something. When I select a cell, I can see that the custom cell is there, but since it seems to ignore the background color of my custom cell, I'm getting my custom white text on top of the standard white background making it appear like an empty cell.
Breakpoints in initWithStyle:reuseIdentifier: in the custom cell class never get hit so something is not right there.
Any pointers on:
What am I missing to get my custom background color on the cells in a SearchResultsController?
Why does the initWithStyle for my custom cell not get hit? It's set as the class for the cell in the storyboard.
Feedback on the Apple Developer Forums along with posts like the following were helpful:
How to customize the background color of a UITableViewCell?
In short, override tableView:willDisplayCell: and you can set the background color appropriately.
Related
I have taken over an iOS project and have to refactor a list of views into a UITableView. I am using Storyboards and have subclassed UITableViewCell. One subclass is called MenuItemCell and has a headerLabel, detailLabel, and priceLabel which are properties set up in the Storyboard and configured in MenuItemCell. I am able to manipulate these via cellForAtIndexPath like this:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *MenuItemCellIdentifier=#"MenuItemCell";
id dic=self.tmpMenu.listItems[indexPath.row];
if([dic isKindOfClass:[MenuItem class]]){
MenuItemCell *cell = [self.menuTV dequeueReusableCellWithIdentifier:MenuItemCellIdentifier];
MenuItem *menuItem=(MenuItem *)dic;
cell.menuItem=menuItem;
cell.headerLabel.text=menuItem.header;
cell.headerLabel.numberOfLines=0;
cell.priceLabel.text=menuItem.price;
// how to handle this custom spotView
if([menuItem hasInstoreImage]){
UIView *instoreImageDot=[self circleWithColor:[UIColor redColor] radius:4];
[cell.spotView addSubview:instoreImageDot]; // ON SCROLLING, this populates to all the different table cells
}
return cell;
}
return nil;
}
The last piece is that there is a custom UIView called spotView. Currently, I am creating this circle in code in my controller via circleWithColor and trying to add to [cell.spotView] but scrolling causes this to populate on different table cells. How should I set this up? I have added a method to my custom view but this suffers from the same problem.
Cells get reused, you will need to tell the tableView to remove the custom View
if([menuItem hasInstoreImage]){
UIView *instoreImageDot=[self circleWithColor:[UIColor redColor] radius:4];
[cell.spotView addSubview:instoreImageDot];
}else{
//remove it if condition is not met
//or You can add a place holder view instead
}
What is happening is that iOS is reusing cells as you scroll and some of the reused cells already have the instoreImageDot view added as a subview.
You really shouldn't do layout stuff in the cellForRowAtIndexPath method. It should only ever be used to dequeue a reusable cell and then set the data for the cell. All the layout stuff should be handled by the cell itself.
Don't create the instoreImageDot in the controller. Add a method in your custom cell - something like (written in C#, but should be easy to translate):
UpdateCell(MenuItem item, bool hasInstoreIamge)
{
menuItem = item;
headerLabel.text = item.header;
priceLabel.text = item.price;
headerLabel.numberOfLines=0;
if (hasInstoreImage)
{
// code to add the instoreImageDot as a subview of the cell
}
}
Also in the Custom Cell, Implement the prepareForReuse method and inside this method, remove the instoreImageDot view from the cell - so that it can only ever be added once.
- (void)prepareForReuse {
if([self.subviews containsObject:instoreImageDot])
{
[instoreImageDot removeFromSuperview];
}
[super prepareForReuse];
}
Now your cellForRowAtIndexPath method can look like:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *MenuItemCellIdentifier=#"MenuItemCell";
id dic=self.tmpMenu.listItems[indexPath.row];
if([dic isKindOfClass:[MenuItem class]]){
MenuItemCell *cell = [self.menuTV dequeueReusableCellWithIdentifier:MenuItemCellIdentifier];
MenuItem *menuItem=(MenuItem *)dic;
cell.UpdateCell(menuItem, [menuItem hasInstoreImage]);
return cell;
}
return nil;
}
In my application I have a TableView full of cells. Everything works just fine -- When I tap on a cell, it calls tableView:DidSelectRowAtIndexPath: right away and executes a segue, bringing me to the next screen. I also have a search bar, using UISearchDisplayController, allowing users to search through the items in the tableView. When I type some text into the search bar, the cells that match the search display in the table.
Now, my problem is when I tap on one of the cells displayed in this search results table... On one initial tap, the table view does not respond in any way. If the tap is held just briefly, the cell turns gray, as if it were selected, however tableView:DidSelectRowAtIndexPath: is still not called, and the cell turns back to normal after releasing the tap. But if I do a long press for a few seconds, then tableView:DidSelectRowAtIndexPath: is finally called, and I am brought to the correct screen.
Has anyone encountered this problem before? As far as I know I have implemented my UISearchDisplayController the same exact way as I always have, and have never had this problem.
Thank You, and let me know if I can give any additional information that may be helpful
EDIT
I am not certain wherein the problem lies exactly, so I'm not sure which methods to show, but here is some code...
I am bringing up the search bar upon clicking an icon in the UINavigationBar, then removing it from the superview once the editing has finished.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSArray *contentArray;
if (tableView == self.tableView) {
contentArray = self.postArray;
} else if (tableView == self.searchDisplayController.searchResultsTableView) {
contentArray = self.searchResults;
}
// This is pretty hackish, but it wasn't working before for some reason. So I send the PFObject I want as the sender
[self performSegueWithIdentifier:#"ShowPostDetails" sender:contentArray[indexPath.row]];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"PostCell";
PostTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[PostTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
[self updateSubviewsForCell:cell inTableView:tableView atIndexPath:indexPath];
/*
if (tableView == self.searchDisplayController.searchResultsTableView) {
[cell addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(cellTapped:)]];
} */
return cell;
}
#pragma mark - Search Bar Delegate
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"text contains[c] %#", searchText];
self.searchResults = [self.postArray filteredArrayUsingPredicate:resultPredicate];
}
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
[self filterContentForSearchText:searchString
scope:[[self.searchDisplayController.searchBar scopeButtonTitles]
objectAtIndex:[self.searchDisplayController.searchBar
selectedScopeButtonIndex]]];
return YES;
}
- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller
{
[self.view addSubview:self.searchDisplayController.searchBar];
self.searchDisplayController.searchBar.center = CGPointMake(self.view.window.center.x, 42);
}
- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller
{
[self.searchDisplayController.searchBar removeFromSuperview];
}
I found my problem...
The specific view controller that was experiencing this problem is subclassed from the view controller containing these delegate methods, and contains a UITextField for entering information. I watch for a keyboardDidAppear notification, and when it appears I add a UITapGestureRecognizer to the view to close the keyboard by resigning first responder of the UITextField when the view is tapped. When I added this I had not yet implemented the search feature, so I knew the only reason the keyboard would pop up is for the UITextField. The problem is that this extra TapGestureRecognizer added when the keyboard popped up for the search bar prevented the TapGestureRecognizer built into the UITableView cell from firing. I was looking in the wrong spot for the problem.
To fix the problem I simply made a check that the UITextField is indeed the first responder before adding the gesture recognizer. Now all works as it is supposed to.
So for anyone else experiencing a similar problem, I'd say go back and make sure you don't have any other UIGestureRecognizers that might be conflicting with the gesture recognizers of your tableView.
Thanks to everyone who commented. Helped lead me to where the problem was.
I have a table view which display the contacts from array. I setup the table view delegates by follows.
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [contactArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"ContactCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:simpleTableIdentifier];
}
cell.textLabel.text = [contactArray objectAtIndex:indexPath.row];
return cell;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 60.0;
}
But the first cell in the table view always empty. It starts display only from second cell. I thought it may be header view. So I removed the header using the following delegate methods.
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 0.0;
}
But still have the problem. I attached the screenshot about this issue.
Any help will be appreciated.
Your TableView is fine and it is working correctly, this is due to some other problem that is included in iOS 7, that automatically scroll insets. To solve this problem, go to your storyboard and select the viewcontroller in which your TableView is and select the ViewController and select the Properties of that ViewController, and uncheck this checkbox, which is read as Adjust ScrollView Insets. See this screen shot,
Your table is correct.Just your table was auto adjusted by the viewController.
You can write self.automaticallyAdjustsScrollViewInsets = NO;
Your Deduction is wrong your first cell isn't missing, but your tableview has started by 64 points down. So change your frame of your tableview or your tableview constraints accordingly.
Tip : Try setting a background colour when you have to debug things like this to clear your doubts.
i got ios application with UITableView
I customize cells in this table, but when number of objects lower than visible cells on screen, all other cells look like simple UITableViewCells.
It should look like this
How should i customize all cells?
UPD Code for dataSource:
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 [dataBase.showMaps count];
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 70;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
ShowMapCell *cell = (ShowMapCell*) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[ShowMapCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the cell...
ShowMap *sM = [dataBase.showMaps objectAtIndex:indexPath.row];
//cell.textLabel.text = sM.name;
[cell initShowMapCell:sM index:indexPath.row];
return cell;
}
You could have just set background color of the tableview. However that solution wont work because you are using different backgrounds for each cell.
So according to me to achieve what you want you need to add some extra (blank) rows to the table and for that you need to return your record + extra cell (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section method. And above that you need to set the background color of table view to one of the cell background because if you are using bouncing effect in the tableview you will find white color when you scroll the tableview up/down.
If you want your UITableView not to have 'empty cells', you will need to hide the cell separators and change the background colour of the tableview to match your colour scheme.
As katzenhut said in the comments, they aren't cells, they are placeholders for content. So if you hide the separators and add them in the xib file for your UITableViewCell ShowCell, then they will no longer be seen when there isn't a cell to place in the table.
I am trying to implement a "Load More" cell into my UITableView.
How can I change content, height, etc. of the cell "Load More" programmatically? I assume that this can be done with getting the selected cell like shown below. When I want to change the background color for instance - it has no effects.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if(indexPath.row == 2){
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
}
call this function:
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation (UITableViewRowAnimation)animation
Something like this:
NSIndexPath* rowToReload = [NSIndexPath indexPathForRow:3 inSection:0];
NSArray* rowsToReload = [NSArray arrayWithObjects:rowToReload, nil];
[UITableView reloadRowsAtIndexPaths:rowsToReload withRowAnimation:UITableViewRowAnimationNone];
more info here.
I think it is not a good approach to call the table view delegate messages directly... Maybe you would want to set a flag when you have to display one extra row for this "load more" cell view and at that point call the reloadData message of your table view (or insert/reload a single cell view).
if you want to change height, do it like this:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.row == 0){
return 25;
} else {
return 120;
}
}
if you want to change color, do it like this
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.row == 0){
cell.backgroundColor = [[UIColor alloc]initWithRed:50.0/255.0 green:50.0/255.0 blue:100.0/255.0 alpha:1];
} else {
cell.backgroundColor = [UIColor whiteColor];
}
//or use cell.textLabel.textColor to change the text color
}
If your load more is at the very bottom of the table, implement it as a table footer.
You won't screw up your datasource with extra indices and you'll be free of flyweight to modify it/resize it as you see fit.
Something like:
tableView.tableFooterView = theFooterView;
After loading the next page, if it was the last one, just remove the footer by setting the property to nil on the table.
About your snippiet:
-cellForRowAtindexPath: (on the table view) will not return anything if the cell is not visible. Also, tableView:cellForRowAtIndexPath (the datasource version) will undo what you might have done when it gets called for that cell again.
Put all cell modification code in your datasource, and call the appropriate reload cell method on the table when you want them to change.