Properly setting up willSelectRowAtIndexPath and didSelectRowAtIndexPath to send cell selections - iphone

Feel like I'm going a bit nutty here. I have a detail view with a few stand-alone UITextFields, a few UITextFields in UITAbleViewCells, and one single UITableViewCell that will be used to hold notes, if there are any. I only want this cell selectable when I am in edit mode. When I am not in edit mode, I do not want to be able to select it. Selecting the cell (while in edit mode) will fire a method that will init a new view. I know this is very easy, but I am missing something somewhere.
Here are the current selection methods I am using:
-(NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (!self.editing) {
NSLog(#"Returning nil, not in edit mode");
return nil;
}
NSLog(#"Cell will be selected, not in edit mode");
if (indexPath.section == 0) {
NSLog(#"Comments cell will be selected");
return indexPath;
}
return nil;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (!self.editing) {
NSLog(#"Not in edit mode. Should not have made it this far.");
return;
}
if (indexPath.section == 0)
[self pushCommentsView];
else
return;
}
My problem is really 2 fold;
1) Even when I'm not in edit mode, and I know I am returning nil (due to the NSLog message), I can still select the row (it flashes blue). From my understanding of the willSelectRowAtIndexPath method, this shouldn't be happening. Maybe I am wrong about this?
2) When I enter edit mode, I can't select anything at all. the willSelectRowAtIndexPath method never fires, and neither does the didSelectRowAtIndexPath. The only thing I am doing in the setEditing method, is hiding the back button while editing, and assigning firstResponder to the top textField to get the keyboard to pop up. I thought maybe the first responder was getting in the way of the click (which would be dumb), but even with that commented out, I cannot perform the cell selection during editing.

Good lord I am an idiot. I never added these lines:
self.tableView.allowsSelection = NO; // Keeps cells from being selectable while not editing. No more blue flash.
self.tableView.allowsSelectionDuringEditing = YES; // Allows cells to be selectable during edit mode.
Sorry for the garbage question.

The documentation notes that tableView:willSelectRowAtIndexPath: isn't called when in editing mode. In addition, the blue flash will happen even if you cancel the selection. From the documentation:
This method is not called until users touch a row and then lift their finger; the row isn't selected until then, although it is highlighted on touch-down. You can use UITableViewCellSelectionStyleNone to disable the appearance of the cell highlight on touch-down. This method isn’t called when the editing property of the table is set to YES (that is, the table view is in editing mode).

Related

how to remove button in Uitableview when table is int edit mode

I creating the one UITable view with the 2 sections in with when i load the data into the uitablew it's working fine,
But my problem is how to resolve one sorting button is disable in section 0 and row 0
which more discribe into the following image.
please help to solve my problem
in advance thanking to your valuable time spend on my problem.
It's quite easy.
Listen on the UITableViewDataSource call tableView:canMoveRowAtIndexPath:
Like this:
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
if(indexPath.section == 0) {
return NO;
}
return YES;
}
If you don't not where to place the method, place it next to tableView:cellForRowAtIndexPath:
Set showsReorderControl to NO property of cell
cell.showsReorderControl = NO;
In addition, if the data source implements tableView:canMoveRowAtIndexPath: to return NO, the reordering control does not appear in that designated row. You need to check indexPath and return NO for it.

Why do my table view cells disappear when reloaded using reloadRowsAtIndexPaths?

I have a bare-bones sample project here:
http://dl.dropbox.com/u/7834263/ExpandingCells.zip
In this project, a UITableView has a custom UITableViewCell. In each cell are 3 UIViews containing a label.
The goal is to expand the cell when tapped, then collapse it when tapped again. The cell itself must change it’s height to expose the subviews. Inserting or removing rows is unacceptable.
The demo project works almost as expected. In fact, in iOS 4.3 it works perfect. Under iOS 5, however, when the rows collapse, the previous cells magically disappear.
To re-create the problem, run the project in the simulator or device with iOS 5 and tap the first cell to expand it. Then tap the cell again to collapse it. Finally, tap the cell directly underneath it. The previous one disappears.
Continuing the tapping for each cell in the section will cause all the cells to disappear, to where the entire section is missing.
I’ve also tried using reloadData instead of the current setup, but that ruins the animations and feels a bit like a hack anyway. reloadRowsAtIndexPaths should work, but the question is why doesn’t it?
See images of what's happening below:
Table appears:
Cell expands:
Cell collapses:
Cell disappears (when tapping the cell underneath):
Keep repeating until the entire section disappears:
EDIT:
Overriding the alpha is a hack, but works. Here is another 'hack' that fixes it as well but WHY does it fix it?
JVViewController.m line 125:
if( previousIndexPath_ != nil )
{
if( [previousIndexPath_ compare:indexPath] == NSOrderedSame ) currentCellSameAsPreviousCell = YES;
JVCell *previousCell = (JVCell*)[self cellForIndexPath:previousIndexPath_];
BOOL expanded = [previousCell expanded];
if( expanded )
{
[previousCell setExpanded:NO];
[indicesToReload addObject:[previousIndexPath_ copy]];
}
else if( currentCellSameAsPreviousCell )
{
[previousCell setExpanded:YES];
[indicesToReload addObject:[previousIndexPath_ copy]];
}
//[indicesToReload addObject:[previousIndexPath_ copy]];
}
EDIT 2:
Made a few minor changes to demo project, worth checking out and reviewing JVViewController didSelectRowAtIndexPath method.
Your problem is in setExpanded: in JVCell.m, you are directly editing the frame of the target cell in that method.
- (void)setExpanded:(BOOL)expanded
{
expanded_ = expanded;
CGFloat newHeight = heightCollapsed_;
if( expanded_ ) newHeight = heightExpanded_;
CGRect frame = self.frame;
frame.size.height = newHeight;
self.frame = frame;
}
Update it to:
- (void)setExpanded:(BOOL)expanded
{
expanded_ = expanded;
}
Then remove the call to -reloadRowsAtIndexPaths:withRowAnimation: at line 163 of JVViewController.m and it will animate as expected.
-reloadRowsAtIndexPaths:withRowAnimation: expects different cells to be returned for the provided indexPaths. Since you are only adjusting sizes -beginUpdates & -endUpdates is sufficient to layout the table view cells again.
May be I am missing a point, but why dont you just use:
UITableViewRowAnimationNone
I mean instead of :
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
use
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationNone];
To animate the height changes of a tableView just call.
[tableView beginUpdates];
[tableView endUpdates];
Don't call reloadRowsAtIndexPaths:
See Can you animate a height change on a UITableViewCell when selected?
The cell that is fading out is the previous cell that is not changing size. As the documentation of reloadRowsAtIndexPaths:withRowAnimation: states:
The table animates that new cell in as it animates the old row out.
What happens is the opacity is set to 1 then immediately set to 0 and so it fades out.
If both the previous and the new cell change size then it works as intended. This is because the begin/end Updates notice the height changes and create new animations on those cells overriding the reloadRowsAtIndexPaths:withRowAnimation: ones.
Your problem is due to abusing reloadRowsAtIndexPaths:withRowAnimation: to resize the cells when it's intended for loading new cells.
But you don't need reloadRowsAtIndexPaths:withRowAnimation: at all. Just change the expanded state of the cells and do the begin/end updates. That will handle all the animation for you.
As a side note I found the blue selection a little annoying, in JVCell set the selectedBackgroundView to the same image as the backgroundView (or create a new image that has the correct look of a selected cell).
EDIT:
Move the statement adding previousIndexPath_ to indicesToReload to the if statement (at line 132) so that it is only added if the previous cell was expanded and needs to resize.
if( expanded ) {
[previousCell setExpanded:NO];
[indicesToReload addObject:[previousIndexPath_ copy]];
}
This removes the case where the previous collapsed cell would disappear.
Another option would be to set previousIndexPath_ to nil when the current cell is collapsed and only set it when a cell expands.
This still feels like a hack. Doing both the reloadRows and the begin/end Updates causes the tableView to reload everything twice but both seem to be needed to animate correctly. I suppose if the table is not too large this won't be a performance problem.
Short, pragmatic answer: Changing UITableViewRowAnimationAutomatic to UITableViewRowAnimationTop solves the issue. No more disappearing rows! (tested on iOS 5.1)
Another short, pragmatic answer, since UITableViewRowAnimationTop is said to cause its own issues: Create a new cell view instead of modifying the existing one's frame. In a real app the data displayed in the cell view is supposed to be in the Model part of the app anyway, so if properly designed it shouldn't be a problem to create another cell view which displays the same data only in a different manner (frame in our case).
Some more thoughts regarding animating the reload of the same cell:
UITableViewRowAnimationAutomatic seems to resolve to UITableViewRowAnimationFade in some cases, which is when you see the cells fading away and disappearing. The new cell is supposed to fade in while the old one fades out. But here the old cell and the new one are one and the same - So, could this even work? In the Core Animation level, Is it possible to fade out a view AND fade it in at the same time? Sounds dubious. So the result is that you just see the fade out. This could be considered an Apple bug, since an expected behavior could be that if the same view has changed, the alpha property wouldn't be animated (since it can't animate both to 0 and to 1 at the same time), but instead just the frame, color etc. would be animated.
Note the problem is just in the animation's display - if you scroll away and back, everything will appear correctly.
In iOS 4.3 the Automatic mode might have been resolved to something other than Fade which is why things work there (as you write they do) - I didn't dig into that.
I don't know why iOS chooses the Fade mode when it does. But one of the cases it does is when your code asks reloads a previously tapped cell, which is collapsed, and is different than the current tapped cell. Note the previously tapped cell is always reloaded, this line in your code is always called:
[indicesToReload addObject:[previousIndexPath_ copy]];
This explains the magic disappearing cells scenario you have described.
By the way, the beginUpdates/endUpdates seem like a hack to me. This pair of calls is just supposed to contain animations, and there aren't any animations you are adding in addition to the rows you already asked to reload. All it did in this case is magically cause the Automatic mode to not choose Fade in some cases - But this just obscured the problem.
A final note: I played around with the Top mode and found it can also cause problems. For example plugging the following code makes cells disappear funkily:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
}
Not sure if there is a real issue here (similar to the one with fading a view in and out at the same time), or maybe an Apple bug.
I just downloaded your project & found this section of code in didSelectRowAtIndexPath delegate where reloadRowsAtIndexPaths is used.
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView beginUpdates];
[tableView endUpdates];
instead of the above why don't you try this?
[tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];
The reason i am suggesting this is that I believe reloadRowsAtIndexPaths:... only works when wrapped inbetween calls to:
- (void)beginUpdates;
- (void)endUpdates;
Outside of that, behavior is undefined and as you've discovered, fairly unreliable. Quoting relevant part of "Table View Programming Guide for iPhone OS":
To animate a batch insertion and deletion of rows and sections, call the insertion and deletion methods within an animation block defined by successive calls to beginUpdates and endUpdates. If you don’t call the insertion and deletion methods within this block, row and section indexes may be invalid. beginUpdates...endUpdates blocks are not nestable.
At the conclusion of a block—that is, after endUpdates returns—the table view queries its data source and delegate as usual for row and section data. Thus the collection objects backing the table view should be updated to reflect the new or removed rows or sections.
The reloadSections:withRowAnimation: and reloadRowsAtIndexPaths:withRowAnimation: methods, which were introduced in iPhone OS 3.0, are related to the methods discussed above. They allow you to request the table view to reload the data for specific sections and rows instead of loading the entire visible table view by calling reloadData.
There could be other valid reason but let me mull on this a bit, since i have your code too I could muck around with it. Hopefully we should figure it out...
When you use this method, you have to be sure that you are on the main thread.
Refreshing a UITableViewCell as follow should do the trick :
- (void) refreshTableViewCell:(NSNumber *)row
{
if (![[NSThread currentThread] isMainThread])
{
[self performSelector:_cmd onThread:[NSThread mainThread] withObject:row waitUntilDone:NO];
return;
}
/*Refresh your cell here
...
*/
}
I think you should not retain the previous indexPath when the cell is not expanded,
try by modifying you did select method like the below, its working fine..
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
BOOL currentCellSameAsPreviousCell = NO;
NSMutableArray *indicesToReload = [NSMutableArray array];
if(previousIndexPath_ != nil)
{
if( [previousIndexPath_ compare:indexPath] == NSOrderedSame ) currentCellSameAsPreviousCell = YES;
JVCell *previousCell = (JVCell*)[self cellForIndexPath:previousIndexPath_];
BOOL expanded = [previousCell expanded];
if(expanded)
{
[previousCell setExpanded:NO];
}
else if (currentCellSameAsPreviousCell)
{
[previousCell setExpanded:YES];
}
[indicesToReload addObject:[previousIndexPath_ copy]];
if (expanded)
previousIndexPath_ = nil;
else
previousIndexPath_ = [indexPath copy];
}
if(currentCellSameAsPreviousCell == NO)
{
JVCell *currentCell = (JVCell*)[self cellForIndexPath:indexPath];
BOOL expanded = [currentCell expanded];
if(expanded)
{
[currentCell setExpanded:NO];
previousIndexPath_ = nil;
}
else
{
[currentCell setExpanded:YES];
previousIndexPath_ = [indexPath copy];
}
// moving this line to inside the if statement blocks above instead of outside the loop works, but why?
[indicesToReload addObject:[indexPath copy]];
}
// commenting out this line makes the animations work, but the table view background is visible between the cells
[tableView reloadRowsAtIndexPaths:indicesToReload withRowAnimation:UITableViewRowAnimationAutomatic];
// using reloadData completely ruins the animations
[tableView beginUpdates];
[tableView endUpdates];
}
This problem is caused by returning cached cells in cellForRowAtIndexPath.
The reloadRowsAtIndexPaths is expecting to get fresh new cells from cellForRowAtIndexPath.
If you do that you will be ok ... no workarounds required.
From Apple doc:
"Reloading a row causes the table view to ask its data source for a new cell for that row."
I had a similar issue where I wanted to expand a cell when a switch is activated to display and extra label and button in the cell that is normally hidden when the cell is at its default height (44). I tried various versions of reloadRowsAtPath to no avail. Finally I decided to keep it simpler by adding a condition at heightForRowAtIndexPath like so:
override func tableView(tableView: UITableView,heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if ( indexPath.row == 2){
resetIndexPath.append(indexPath)
if resetPassword.on {
// whatever height you want to set the row to
return 125
}
}
return 44
}
And in whatever code you want to trigger the expanding of the cell just insert tableview.reloadData(). In my case it was when a switch was turned on to indicate the desire to reset a password.
#IBAction func resetPasswordSwitch(sender: AnyObject) {
tableView.reloadData()
}
There is no lag with this approach, no visible way to see that the table reloaded and the expansion of the sell is done gradually like you'd expect. Hope this helps someone.
#Javy, i noticed some weird behavior while testing your app.
While running on iPhone 5.0 simulator the previousIndexpth_ variable is of
class NSArray (it looks like.) here is the debugger output
(lldb) po previousIndexPath_
(NSIndexPath *) $5 = 0x06a67bf0 <__NSArrayI 0x6a67bf0>(
<JVSectionData: 0x6a61230>,
<JVSectionData: 0x6a64920>,
<JVSectionData: 0x6a66260>
)
(lldb) po [previousIndexPath_ class]
(id) $7 = 0x0145cb64 __NSArrayI
Whereas in iPhone 4.3 simulator it is of type NSIndexPath.
lldb) po [previousIndexPath_ class]
(id) $5 = 0x009605c8 NSIndexPath
(lldb) po previousIndexPath_
(NSIndexPath *) $6 = 0x04b5a570 <NSIndexPath 0x4b5a570> 2 indexes [0, 0]
Are you aware of this issue? Not sure whether this will help but thought of letting you know.
Try this hope it will help u Cell Expansion

TableView swipe-to-delete doesn't work

I have an editable, grouped tableView, but swipe to delete doesn't work. It looks like swipe-to-delete is supposed to come along with UITableViewCellEditingStyleDelete, but it doesn't seem to for me. This is my editing style method:
- (UITableViewCellEditingStyle)tableView:(UITableView *)table editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 1)
{
if (indexPath.row < [[device_coordinator getChannelList] count]) return UITableViewCellEditingStyleDelete;
else if (indexPath.row < [[device_coordinator getChannelList] count]+2) return UITableViewCellEditingStyleInsert;
}
return UITableViewCellEditingStyleNone;
}
This makes the table look right. Some cells have insert icons to the left, some have delete icons, as they should. Pressing a delete icon makes the delete confirmation button appear. But a swipe doesn't!
Even if I just return blank, newly allocated cells from my cellForRowAtIndexPath method, it still doesn't work, so it appears that nothing in my cell is causing the problem. The same problem happens on the 4.3 simulator and on my iPod touch 2g.
I think your code is correct but if I understand you right there is a logical error. You want swipe to delete in editing mode which is not possible. Swipe to delete only works when the user is NOT in editing mode.
So you just need to hide/show the editing icons by calling this method.
-(IBAction)edit:(id)sender//action connected to edit button
{
if(yourTableView.editing) [yourTableView setEditing:NO animated:YES];
else [yourTableView setEditing:YES animated:YES];
}
Then swipe to delete will work when there are no icons next to the cells. Hope this helps.

UITableViewCell selector setSelected:animated: gets called many times?

I've discovered a strange behavior with setSelected:animated: in my custom UITableViewCell class. I discovered that this function gets called multiple times if I click on a cell in my table. I am wondering if this is normal behavior or a bug in my code.
To help with debugging, I've modified the setSelected:animated: function in my custom UITableViewCell class implementation as such:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state.
if (selected)
NSLog(#"Yes %X", &self);
else
NSLog(#"No %X", &self);
}
If I click on a cell in the simulator, here is what I get in the console:
2011-03-22 22:05:26.963 marketPulse[3294:207] Yes BFFFDDD0
2011-03-22 22:05:26.964 marketPulse[3294:207] Yes BFFFDE30
You would think that I would get only 1 entry, since I only clicked on 1 cell.
And if I click on a different cell after that:
2011-03-22 22:07:11.014 marketPulse[3294:207] No BFFFD890
2011-03-22 22:07:11.016 marketPulse[3294:207] No BFFFDD00
2011-03-22 22:07:11.017 marketPulse[3294:207] Yes BFFFDDD0
2011-03-22 22:07:11.017 marketPulse[3294:207] Yes BFFFDE30
If I click on the same cell 2 times in a row, I get more than 2 Yes:
2011-03-22 22:08:41.067 marketPulse[3294:207] Yes BFFFDDD0
2011-03-22 22:08:41.068 marketPulse[3294:207] Yes BFFFDE30
2011-03-22 22:08:41.069 marketPulse[3294:207] Yes BFFFDE30
The more times I click the same cell, the more Yes I will get, and if I click on a different cell after that, I'll get a lot of No
I put a breakpoint before the NSLog, and looking at the debugger, it seems that all the repeated calls are coming from the same object.
Here is a part of my tableView:cellForRowAtIndexPath: function so you can see how my cells are being treated:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *ContentCellIdentifier = #"newsTableCellContent";
UITableViewCell *cell;
//index of cell data in tableData
NSUInteger index = indexPath.row / 2;
...
//content of story
else if( [indexPath row] % 2 == 1 ) {
cell = [tableView dequeueReusableCellWithIdentifier:ContentCellIdentifier];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle]
loadNibNamed:#"newsTableCells"
owner:nil options:nil];
for (id currentObject in topLevelObjects) {
if ( [currentObject isKindOfClass:[newsTableCellContent class]] ) {
cell = currentObject;
break;
}
}
}
((newsTableCellContent *)cell).content.text = [[tableData objectAtIndex:index] description];
}
return cell;
}
Everything works fine so its hard to tell if the repeat calls to setSelected:animated: are intentional or not. If this is normal operation, I can make do with another method, but I would just like to know if this is suppose to happen or not.
Thanks
What's going on is simply that the UITableView keeps track of which cells are selected in the table.
Since cells are reused when you scroll through a large table view, the table view has to keep the list of selected cells separate. Not only that, but whenever it reuses a cell it has to set its selected property, because it may be using an old, invalid selected state from a previous incarnation.
When you tap a cell, several things happen: the previously selected cell is deselected (using setSelected:). The new cell is highlighted. It's de-highlighted (at least if you tap, instead of holding your finger down), and the setSelected: method is called because the new cell was selected. That's one.
The second call is a delayed perform call, possibly from a point where the table view didn't yet know what the final state of the table would be. This call goes to _selectAllSelectedRows, which, as the name suggests, calls 'setSelected:animated:' on all selected rows. That's the second call. The reason for this is most likely to address potential issues due to the the table view being in a "transition", but who knows.
Whether it's a bug or not is up for interpretation. A fix for the duplicate calls is to simply do:
if (self.selected == selected) return;
right before the call to super (you do not have to call super if self.selected == selected).
This is a normal behavior if you're using iPad. (it is only called once on iPhone).
In order to stop getting multiple "setSelected:YES" or multiple "setSelected:NO", all you have to do is this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Now, 1 click on any cell gives you:
1 entry of setSelected:YES animated:NO
1 entry of tableView: didSelectRowAtIndexPath:
1 entry of setSelected:NO animated:YES
So, calls are now stable regardless of what you do.
Ideally you should not be calling setSelected from anywhere in your code.
UIKit will take care of calling it.
If you want to show a cell/row as selected in cellForRowAtIndexPath method simply call
tableView.selectRowAtIndexPath(indexPath, animated: true, scrollPosition: .None)
for that specific indexPath.
Again never ever call setSelected explicitly unless you really mean to.
It should definitely be called when table is scrolled. Cells are reused, that means, if you scroll cells in invisible areas will be reused and reinitialized, including the call to setSelected, which is basically a lightweight property setter.
If you really want to see what's happening, add a NSLog to tableView:cellForRowAtIndexPath: which will log indexPath and the returned cell.
The entire log should give you a good understanding what happens inside and why.
I suppose it will be something like this (Clicked on IndexPath 1:1)
Give me cell on 1:0 (previously selected cell).
Deselect 1:0
Give me cell on 1:0 again (updated after deselection)
Deselect 1:0 (update selected flag on this cell and trigger animation)
Give me cell on 1:1
Select 1:1
Give me cell on 1:1 again (updated after selection)
Select 1:1 (update selected flag on this cell and trigger animation)
Clicking on a selected cell again is only slightly different - instead of triggering unselecting, it triggers another update.

UISearchDisplayContoller – can't prevent table reload on typing in search bar

I'm trying to set up a search display controller to handle async results from a web service. I've got the basic bits in place but have run into a really strange issue that I can't figure out.
Seems like to rig up the search display controller for async you really just need to do two things:
return NO for
searchDisplayController:shouldReloadTableForSearchString,
and
handle searchBarSearchButtonClicked
and fire off the table reload
myself.
I'm doing both of these but what I'm seeing is that the search display controller is reloading the table on the first character typed into the search bar even though I'm returning NO as per #1. It doesn't reload on subsequent characters entered.
So, my question is: how do I keep the search display controller from trying to reload the table while the user is typing? (specifically on that first character entered)
I've seen this issue mentioned as part of a couple of other questions but I have not seen a direct answer to the problem. I'd like to understand what's going on or what I'm doing wrong before I resort to a bunch of UI mangling to work around it.
Here's a quick distillation of my code to show the issue. When I run this and type "abcde" into the search bar, after I type "a" the results display as "a #0", "a #2", etc. They don't update again until I hit the search button then you see "abcde #0", "abcde #1", etc. Desired result is, of course, nothing happens until I hit the search button.
#pragma mark -
#pragma mark UISearchDisplayController Delegate Methods
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
return NO;
}
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption {
return NO;
}
#pragma mark -
#pragma mark UISearchBarDelegate Methods
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
[self.searchDisplayController.searchResultsTableView reloadData];
}
#pragma mark -
#pragma mark Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 20;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
cell.textLabel.text = [self.searchDisplayController.searchBar.text stringByAppendingFormat:#" #%d", indexPath.row];
return cell;
}
Thanks! (btw, this is my first question asked here—please let me know if I miss any points of etiquette :)
This is just the way UISearchDisplayController (SDC) works. When the user enters the first character into the searchBar the searchTable is loaded and displayed for the first time causing it to load. The methods "...shouldReloadTableForSearchString" and "...shouldReloadTableForSearchScope" allow you to control whether the searchTable reloads automatically on subsequent chars or a scope change.
I've done both of the following to provide a good user experience on the first character. Slight disclaimer: I do have implementations of both of these that work but this is simply a framework for implementation from my memory. I may have missed a detail but this should get you pretty close.
Option 1: Present a "loading" cell in the searchTable when the first char is typed.
This option allows the SDC to display the searchResultsTableView when the user types the first char, display status as to the current search/filter operation
in the SDC delegate class definition
add the iVar BOOL isLoading
add the iVar UITableView *searchTableView
in searchDisplayController:didLoadSearchResultsTableView
set searchTableView = tableView
in shouldReloadTableForSearchString/Scope
set isLoading = YES
call your method to load data in the background
return NO
when your background filter is complete:
set isLoading = NO
[searchTableView reloadData]
in the various tableView delegate methods respond how you like to show status if there are current search results or results are loading in the background. What I did is:
if there are current search results, show results (even if loading/filtering in the background)
if there are no search results and isLoading == NO return 1 row and show 'No matches' in a cell
if there are no search results and isLoading == YES return 1 row and and show search activity in a cell (I typically use UIActivityIndicatorView)
Option 2: Hide the searchTableView and display an overlay view in it's place until search results are loaded
This option hides the searchTableView when it is first loaded and only redisplays it if when the search/filter is complete. I defined this as an add on to option 1 as they can be done together though to optimize things you may not care about showing search activity in the searchResultsTableView if you are hiding the table and showing the overlay.
in the SDC delegate class definition
same as Option 1
add the iVar UIView *searchTableOverlayView
in searchDisplayController:didLoadSearchResultsTableView
same as Option 1
create a UIView to use as an overlay in place of searchTableView containing whatever UI is appropriate for your app and set it to searchTableOverlayView
in searchDisplayController:didUnloadSearchResultsTableView
release searchTableOverlayView
in 'searchDisplayController:didShowSearchResultsTableView(may be able to do this insearchDisplayController:willShowSearchResultsTableView`
if there are search results to display or isLoading == NO
seachTableOverlayView.hidden == YES
else (if isLoading == YES)
searchTableOverlayView.frame == searchResultsTableView.frame
add seachTableOverlayView as a subview of searchTableVIew.superview
searchTableView.hidden = YES
when your background filter is complete
same as option 1
if there are searchResults to display
searchTableCoverView.hidden = YES
searchResultsTableView.hidden = NO
else
searchResultsTableView.hidden = YES
searchTableCoverView.hidden = NO
in the various tableView delegate methods respond how you like to show status if there are current search results or results are loading in the background. What I did is:
same as option 1
Unfortunately I believe this is just how the UISearchDisplayController functions. You should file a bug report to Apple if you want to request different options for functionality.
The alternative would be to write your own UISearchBar and UITableView combination similar to how UISearchDisplayController works, then you'll get better control.
Hope this helps!
When I faced the same issue I tried to put an overlay view on search result view. That didn't help me. Looks like Apple change the behavior and I saw cell separators shone through my view. Finally I came up with a good approach.
At first my async search class had it's own overlay, progress and other views. UISearchDisplayController was used only for start, end delegate methods calls and sticking to default search flow.
To show view I used any subview I can put a search views on (as an init call):
newView.frame = frame;
[view addSubview:newView];
and then use the subview when changing my search progress views:
if (view == currentView)
return;
view.frame = currentView.frame;
view.alpha = [self getAlphaValueForView:view];
UIView * superView = [currentView superview];
[currentView removeFromSuperview];
currentView = view;
[superView addSubview:currentView];
The problem with empty search result table view blinking appears when I start to use my search content controller in default navigation routine (navigation controller).
So to solve the problem I start using for navigation not a content controller (in this case the default search display controller activating) but an empty controller. And I used empty controllers view as a subview for search views.
So in total: DO NOT use UISearchDisplayController's content view controller in navigation. Use stub view controller. And then use it's view to put your views as subviews.
Worked great for me.
Hide self.searchDisplayController.searchResultsTableView until user presse search button
- (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView;
{
tableView.hidden = YES;
}
After user press the search key, and the search results get ready, make the search result table visible
self.searchDisplayController.searchResultsTableView.hidden = NO;
[self.searchDisplayController reloadData]
This workaround works for me.
Maybe you need to implement UISearchDisplayController and then this function:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText