How do I cover the "no results" text in UISearchDisplayController's searchResultTableView? - iphone

I don't want to show the "no results" text while my server is processing a search query.
I figured out the exact coordinates of the table cell that contains the label and attempted to cover it.
self.noResultsCoverView = [[[UIView alloc] initWithFrame:CGRectMake(
0.0,
44.0,
320.0,
43.0
)] autorelease];
self.noResultsCoverView.backgroundColor = [UIColor whiteColor];
[self.searchDisplayController.searchResultsTableView addSubview:self.noResultsCoverView];
To my chagrin, my cover was above the table view, but below the label. I need the cover to be above the label. searchResultsTableView::bringSubviewToFront didn't work, which makes me believe that the label isn't a child of the searchResultsTableView at all.
BTW, this Stack Overflow answer doesn't quite work for me. It works on the very first search, but flashes a weird black cover on subsequent searches.

this should do the work properly. The code to return at least one cell:
BOOL ivarNoResults; // put this somewhere in #interface or at top of #implementation
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (tableView == self.searchDisplayController.searchResultsTableView) {
if (filteredList.count == 0) {
ivarNoResults = YES;
return 1;
} else {
ivarNoResults = NO;
return [filteredList count];
}
}
// {…}
// return the unfiltered array count
}
and for "showing" the clean cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView == self.searchDisplayController.searchResultsTableView && ivarNoResults) {
static NSString *cleanCellIdent = #"cleanCell";
UITableViewCell *ccell = [tableView dequeueReusableCellWithIdentifier:cleanCellIdent];
if (ccell == nil) {
ccell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cleanCellIdent] autorelease];
ccell.userInteractionEnabled = NO;
}
return ccell;
}
// {…}
}

The easiest way to work around this is to return 1 in numberOfRowsInSection while the query is in progress and leave the dummy cell empty or set its hidden property to YES so it is not visible.

Try this it worked for me
In the UISearchDisplayController delegate do this:=
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 0.001);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
for (UIView* v in self.searchDisplayController.searchResultsTableView.subviews) {
if ([v isKindOfClass: [UILabel class]] &&
[[(UILabel*)v text] isEqualToString:#"No Results"]) {
[(UILabel*)v setText:#""];
break;
}
}
});
return YES;
}

You need to realize that when you have a UISearchDisplayController, and the search bar is active, the UITableView argument passed into your UITableView data source and delegate methods is in fact NOT your tableView object, but a tableView managed by the UISearchDisplayController, intended to display "live" search results (perhaps results filtered from your main data source, for example).
You can easily detect this in code, and then return the appropriate result from the delegate/data source method, depending on which tableView object is asking.
For example:
- (NSInteger)tableView:(UITableView *)tv numberOfRowsInSection:(NSInteger)section
{
if (tv == self.searchDisplayController.searchResultsTableView) {
// return the number of rows in section for the visible search results.
// return a non-zero value to suppress "No results"
} else {
// return the number of rows in section for your main data source
}
}
The point is that your data source and delegate methods are serving two tables, and you can (and should) check for which table is asking for data or delegation.
By the way, the "No results" is (I believe) provided by a background image which the UISearchDisplayController displays when the delegate says there are no rows... You are not seeing a 2-row table, the first blank and the second with text "No results". At least, that's what I think is happening there.

Related

UITableView tableView:cellForRowAtIndexPath: not getting called

I have 3 table views in one view and I was wondering why the tableView:cellForRowAtIndexPath: was not getting called.
#pragma mark -
#pragma mark <UITableViewDelegate>
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView == self.mainVoucherTableViewController)
[self setSelectedIndex:indexPath.row];
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
if (tableView == self.mainVoucherTableViewController){
return 10;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView == self.mainVoucherTableViewController){
static NSString *MyIdentifier = #"MyReuseIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier];
}
cell.textLabel.text = #"THISTEXT";
return cell;
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if (tableView == self.mainVoucherTableViewController)
return 1;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
// The header for the section is the region name -- get this from the region at the section index.
if (tableView == self.mainVoucherTableViewController){
NSString * myString = [NSString stringWithFormat:#"HELLLO WORLD"];
return myString;
}
}
WOULD anyone know why this is? Basically this doesn't create any cell or display cells. It just displays the table views. :(
Just to consolidate a few things from above:
From your naming, your tableView is called mainVoucherTableViewController - just want to confirm that this is indeed a UITableView and not a UITableViewController? If it's a UITableViewController then the rest of this won't work for obvious reasons (or not so obvious - let me know and can explain further)
Make sure you have set the current viewController as the tableView's delegate and dataSource, either with the code below or in Interface Builder if you're using a XIB
self.mainVoucherTableViewController.delegate = self;
self.mainVoucherTableViewController.dataSource = self;
Make sure your numberOfRowsInSection function is being called and that you're returning non-zero (put in NSLogs, etc) and do the same for numberOfSections as well (actually numberOfSections isn't required if you're only using 1 section) - see UITableViewDataSource Protocol Reference: http://developer.apple.com/library/ios/#documentation/uikit/reference/UITableViewDataSource_Protocol/Reference/Reference.html
As per previous post, log your cellForRow (if points #1-3 are checked and working) at the beginning to make sure it's triggered, and just before the return. Also do an NSLog of the cell you're returning just to make sure it isn't nil.
Start off by logging inside your tableView:cellForRowAtIndexPath: method to see if it gets called at all outside your if statement as well as inside to help narrow down the issue.
Also try instead of comparing your:
tableView == self.mainVoucherTableViewController
Set the tableViews to have tag values instead. Then you can do:
if(tableView.tag == 100){ // tag number we assigned self.mainVoucherTableViewController via IB
//do your stuff here
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (tableView == self.mainVoucherTableViewController)
{
return 10;
}
else
{retun 5;
}
}
It display row in first table 10, second table show 5 rows.
The order of instance declaration does matter. For example, if you have a ivar called tableView:
WRONG
self.tableView.delegate = self;
self.tableView = [UITableView alloc] init];
CORRECT
self.tableView = [UITableView alloc] init];
self.tableView.delegate = self;
check UITableView Object Frame Size. maybe Frame size is not enough to draw Cell.

How to insert a UITableViewCell at the beginning of UITableView

I am trying to set up a UITableView, with x amount of sections and X number of rows per section.
However I would like to add a single row to the top of my UITableView Is there is a way to hardcode this into the view?
I currently return the number of sections and rows per section based off a NSdictionary like so.
- (NSInteger)numberOfSectionsInTableView: (UITableView *)tableView
{
// Return the number of sections.
return [letterDictionary count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// returns the number of rows per section based off each entry in letterDictionary
currentLetter = [sectionLetterArray objectAtIndex:section];
return [[letterDictionary objectForKey:currentLetter] count];
}
You can add a "header" to the tableview.
In your tableview class:
self.tableView.tableHeaderView = yourView;
You cannot just add a row above your UItable within the table. If you just need a row of text why not use UITextField, UILabel or UITextView depending on your needs and just position it above your UItable whereever you like.
If I missunderstood you and you just like to add one row as the very first one in your first section, all you need to do sth like this:
if (section == 0) {
return [[letterDictionary objectForKey:currentLetter] count]+1;
} else
{
return [[letterDictionary objectForKey:currentLetter] count];
}
and ensure that when returning row for indexpath you have also a similar if-statement and return whatever you need for section == 0 and row == 0.
But this first row will certainly scroll away if you scroll down your table view - as said I am not sure exactly what you need.
You could try to customize your tableview section's header...
For example you can use something like this:
YourController.h
-(UIView *)headerView;
YourController.m
-(UIView *)headerView
{
UIView *header = [[UIView alloc] initWithFrame:CGRectZero];
// Place everything you want in your header
// using [header addSubview:yourSubview];
// Finally set header's frame and return it
header.frame = CGRectMake(0.0, 0.0, 320.0, 44.0);
return header;
}
// Use this to return header's height
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
if (section == yourSection)
{
return [self headerView].frame.size.height;
}
else
{
return [self sectionHeaderHeight];
}
}
// Use this to return your view
-(UIView *)tableView:(UITableVIew *)tableVIew viewForHeaderInSection:(NSInteger)section
{
if (section == yourSection) // To show the header only on a specified section
{
return [self headerView];
}
else
{
return nil;
}
}
If you change your mind and want to customize below the tableView, your can use the same method changing Header with Footer.
Finally take a look at docs about these methods:
- tableView:viewForHeaderInSection:
- tableView:heightForHeaderInSection:
- tableView:viewForFooterInSection:
- tableView:heightForFooterInSection:
Hope this fits your needs!

UITableView per-cell selection mode while editing

In my UITableView, when it enters editing mode, I'd like only a select few cells to be selectable. I know the UITableView class has the property allowsSelectionDuringEditing, but this applies to the whole UITableView. I don't see any relevant delegate methods to set this on a per-cell basis.
The best solution I can come up with is to set allowsSelectionDuringEditing to YES. Then, in didSelectRowAtIndexPath, filter out any unwanted selections if the table view is editing. Also, in cellForRowAtIndexPath, change those cells selectionStyle to None.
The problem with this is that going into editing mode does not reload the UITableViewCells, so their selectionStyle doesn't change until they scroll offscreen. So, in setEditing, I also have to iterate over the visible cells and set their selectionStyle.
This works, but I just wonder if there is a better/more elegant solution to this problem. The basic outline of my code is attached. Any suggestions greatly appreciated! Thank you.
- (void) tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
if (self.editing && ![self _isUtilityRow:indexPath]) return;
// Otherwise, do the normal thing...
}
- (UITableViewCell*) tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {
// UITableViewCell* cell = ...
if (self.editing && ![self _isUtilityRow:indexPath])
{
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
else
{
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
}
return cell;
}
- (void) setEditing:(BOOL)editing animated:(BOOL)animated {
[super setEditing:editing animated:animated];
if (editing)
{
for (UITableViewCell* cell in [self.tableView visibleCells])
{
if (![self _isUtilityRow:[self.tableView indexPathForCell:cell]])
{
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
}
}
else
{
for (UITableViewCell* cell in [self.tableView visibleCells])
{
if (![self _isUtilityRow:[self.tableView indexPathForCell:cell]])
{
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
}
}
}
}
I'm not sure how your app works, but perhaps you could try make use of the following somewhere in your DataSource definition:
// Individual rows can opt out of having the -editing property set for them. If not implemented, all rows are assumed to be editable.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath;
when going into editing mode, use the function to filter the first selection level, then on to your second selection level

uitableview cellforrowatindexpath not called but no:of rows in section is called

in my tableview no of rows in section method is called and it returns value 17,but the cellforrowatindexpath is not getting called.i have put breakpoints in the first line of this method but this point is never shown when debugging,i have followed the tableviewdelegate and datasource.and the tableviews datasource,delegate are properly set in the Int builder.
i am also posting some of the code
in my viewcontroller.m
-(void)viewDidLoad
{
tweets=[[NSMutableArray alloc]init];
[self updateStream:nil];
[self.tweetsTable setDelegate:self];
[self.tweetsTable setDataSource:self];
}
-(NSInteger)tableView:(UITableView *)tweetsTable numberOfRowsInSection:(NSInteger)section {
return [tweets count];
}
-(UITableViewCell *)tableView:(UITableView *)tweetsTable cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"twitCell";
TwitCell *cell = (TwitCell *)[tweetsTable dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = (TwitCell *)[[[TwitCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
cell.tweet = [tweets objectAtIndex:indexPath.row];
[cell layoutSubviews];
return cell;
}
cellForRowAtIndexPath won't get called if your tableview has height of 0
make sure your tableview always has VISIBLE rows
CGRect frame = self.tableView.frame;
frame.size.height = 100;
self.table.frame = frame;
It will not get called if you are returning 0 rows in numberOfRowsInSection method.
Please check the number of rows that you return.
Given the code you've shown us there only a few possibilities. Either self.tweetsTable is nil, tweets is nil, or tweets contains no element and count is returning zero. Now I know you say that everything is correct, but clearly something is up! You can add a bit of defensive code to detect these problems.
-(void)viewDidLoad
{
tweets=[[NSMutableArray alloc]init];
[self updateStream:nil];
NSAssert(self.tweetsTable, #"self.tweetsTable must not be nil.");
[self.tweetsTable setDelegate:self];
[self.tweetsTable setDataSource:self];
}
-(NSInteger)tableView:(UITableView *)tweetsTable numberOfRowsInSection:(NSInteger)section {
NSAssert(tweets, #"tweets must not be nil here");
NSUInteger n = [tweets count];
if(n == 0)
NSLog(#"WARNING: %# returning 0", __PRETTY_FUNCTION__);
return (NSInteger)n;
}
If you do this and one of the asserts fires you'll know where your problem is. If no assert fires then something is going on outside the scope of the code you have shown (e.g. something is getter released to soon or memory getting clobbered). Oh and one final thing -- can you see the empty table view on the screen? If you table is not visible AFAIK cellForRowAtIndexPath won't be called.
check this
or else use viewDidLoad code in viewWillAppear
Here is my function. After I placed "dispatch_async(dispatch_get_main_queue()..." inside of "tweets = [NSJSONSerialization.....", it works.
tweets = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableLeaves error:&jsonError];
if (tweets) {
// We have an object that we can parse
NSLog(#"%#", tweets);
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
else {
// Inspect the contents of jsonError
NSLog(#"%#", jsonError);
}
In my cases I had created a UITableView as a property on my view controller, but I forgot to add it as a subview to self.view
Strangely you will get the symptoms that sujith described: numberOfRowsInSection will be called, but cellForRowAtIndexPath will not!
This was my missing line:
[self.view addSubView:self.myTableViewProperty];
And to defend against it:
NSAssert(self.myTableViewProperty.superview != nil, #"The table view dose not have a superview");
[self.myTableViewProperty reloadData];
You can refer following code to reload your tableView
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
In my case I was using custom subclass of UITableView and I have not called super super.layoutSubviews()
override func layoutSubviews() {
super.layoutSubviews() // Forgotten to call super implementation
self.layer.borderColor = Constants.primaryColor.cgColor
self.layer.borderWidth = Constants.primaryBorderWidth
self.indicatorStyle = .white
}

UITableView : crash when adding a section footer view in empty section

This is the first time I ask a question here, but I have to say this site has been a tremendous help for me over the last couple months (iphone-dev-wise), and I thank you for that.
However, I didn't find any solution for this problem I'm having:
I have a UITableView with 2 sections, and no rows when the app is launched for the first time. The user can fill the sections later on as he wishes (the content is not relevant here). The UITableView looks good when filled with rows, but looks pretty ugly when there is none (the 2 header sections are stuck together with no white space in between). That is why I'd like to add a nice "No row" view in between when there is no row.
I used viewForFooterInSection:
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
if(section == 0)
{
if([firstSectionArray count] == 0)
return 44;
else
return 0;
}
return 0;
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section{
if(section == 0)
{
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(200, 10, 50, 44)];
label.backgroundColor = [UIColor clearColor];
label.textColor = [UIColor colorWithWhite:0.6 alpha:1.0];
label.textAlignment = UITextAlignmentCenter;
label.lineBreakMode = UILineBreakModeWordWrap;
label.numberOfLines = 0;
label.text = #"No row";
return [label autorelease];
}
return nil;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if(section == 0)
{
return [firstSectionArray count];
}
return [secondSectionArray count];
}
This works great : the footer view appears only when there is no row in section 0.
But my app crashes when I enter edit mode and delete the last row in section 0:
Assertion failure in -[UIViewAnimation initWithView:indexPath:endRect:endAlpha:startFraction:endFraction:curve:animateFromCurrentPosition:shouldDeleteAfterAnimation:editing:]
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Cell animation stop fraction must be greater than start fraction'
This does not happen when there are several rows in section 0. It only happens when there is only one row left.
Here's the code for edit mode:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
// If row is deleted, remove it from the list.
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Find the book at the deleted row, and remove from application delegate's array.
if(indexPath.section == 0)
{ [firstSectionArray removeObjectAtIndex:indexPath.row]; }
else
{ [secondSectionArray removeObjectAtIndex:indexPath.row]; }
// Animate the deletion from the table.
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationBottom];
[tableView reloadData];
}
}
Does anyone have any idea why this is happening?
Thanks
It looks like what it happening is that internally Apple's UITableView code is assuming that when you delete a row, the first section of your view will get shorter. By making the section header footer suddenly get taller to compensate for the last row, you appear to be confusing it.
Two ideas for you to try:
1. try making the optional section footer a pixel or two smaller than the table cell that's going away, so that the animation code gets to do a pixel or two of animation
2. instead of deleting the only row of your table, when there are no "real" data rows let the table still have numberOfRowsInSection return 1 and make a fake "no data" cell rather than using a table footer for the "no data" case.
This is one of those cases where you just have to accept that Apple has written half of your program for you and you have to conform to some choices your co-author has made, whether you like them or not.
make empty section
use table header in this section instead of footer in first section
- (UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if (section != OTHER_SECTION_INDEX) {
return nil;
}
//your code here
}
- (CGFloat) tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
if (section != OTHER_SECTION_INDEX) {
return 0;
}
//your code
}
it shouldn't be necessary to call both deleteRowsAtIndexPaths:withAnimation: and reloadData on tableView.
the problem you are seeing is the problem i see manifesting in the call to deleteRowsAtIndexPaths:withAnimation:, and thus for you, it's never getting to reloadData anyway.
a simpler solution to having to create new cells, manage them, deal with all of the places where a different cell has to be dealt with, is to use reloadSections:withAnimation: when dealing with a section that is going from 0 to 1 row or 1 to 0 rows.
i.e. replace the following lines of code:
if(indexPath.section == 0)
{ [firstSectionArray removeObjectAtIndex:indexPath.row]; }
else
{ [secondSectionArray removeObjectAtIndex:indexPath.row]; }
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationBottom];
with the following
BOOL useReloadSection;
if (indexPath.section == 0)
{
[firstSectionArray removeObjectAtIndex:indexPath.row];
useReloadSection = 0 == firstSectionArray.count;
}
else
{
[secondSectionArray removeObjectAtIndex:indexPath.row];
useReloadSection = 0 == secondSectionArray.count;
}
if (useReloadSection)
[tableView reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section]
withRowAnimation:UITableViewRowAnimationBottom];
else
[tableView deleteRowsAtIndexPaths:#[indexPath]
withRowAnimation:UITableViewRowAnimationBottom];
I was experiencing the same crash, looks like it's a bug in the OS.
My specific use case pertains to a UITableView with collapsable / expandable sections. I need section dividers (this is what I was using the section footer for) but not row dividers (can't simply use the cell separator).
I wasn't able to resolve the issue by adjusting the height of the section footer. But switching to using a section header did the trick, the bug/crash is no longer occurring.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
CGRect frame = (CGRect){0, 0, self.view.bounds.size.width, 1};
UIView *view = [[UIView alloc] initWithFrame:frame];
view.backgroundColor = [UIColor commentReplyBackground];
return view;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 1.0f;
}
For those of you who still need the section footer, you can also try changing the TableView style to UITableViewStyleGrouped. This seems to bypass the issue. In order to get the same visual appearance, you just have to set all your other footers and headers to CGFLOAT_MIN.