tableView numberOfRowsInSection calls tableView viewForHeaderInSection on iOS4 - objective-c

I got a big app containing a lot of dependencies. For this case I implemented a class called RootTableViewController to handle all the stuff that has to be done everytime a table view controller is required.
Now I discovered an endless loop and I dont know how to fix it. I got the following code in RootTableViewController:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
NSString *sectionTitle = [self tableView:tableView titleForHeaderInSection:section];
int numbersOfRowInSection = [self.tableView numberOfRowsInSection:section];
if (numbersOfRowInSection > 0)
{
// ...
}
else
{
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 28.0f)];
return view;
}
}
This works perfect on iOS 5 and iOS 6, but on iOS4 it causes an endless loop, because [tableView numberOfRowsInSection] is calling [tableView viewForHeaderInSection]. How can I fix this using the table view api? Its no solution for me to work with the [ count] of internal data arrays because I got a lot of table view controllers extending this RootTableViewController with different data sources.

This is simply not good style. You are supposed to subclass or rahter implement the related delegate method but you shoudl not call UITableView.numberofRowsInSection:
However, you have certainly implemented tableView:numberOfRowsInSection. Move all of its functionality to the new method myNumberOfRowsInSection: In there do the same. It is mainly a copy of your current numberOfRowsInSection.
Then here in your code sniplet call [self myNumberOfRowsInSection:...];
And within tableView:numberOfRowsInSection:section just do:
return [self myNumberOfRowsInSection:section];
Apply the same pattern to all delegate methods that you may want to call yourself. Move all its business logic into your own method and then only call your own method from the delegate method and from your own code.

If you want to get the number of rows in a section of the data source without accessing the internal data array, you could query the dataSource delegate for it, something like
int numbersOfRowInSection = [self.tableView.dataSource tableView:self.tableView numberOfRowsInSection:section];
(not compiler checked)

Related

tableView:cellForRowAtIndexPath: never being called

I have a UITableView, named tblParentComments in a UIView, of class CBox.
I have definitely set my view as the datasource and delegate of my table view, and my view does implement those protocols. The method tableView:numberOfRowsInSection: does get called and returns a non-zero value. But the function tableView:cellForRowAtIndexPath: is never called.
I noticed that if I put the method tableView:cellForRowAtIndexPath: in comments, Xcode does NOT stop compiling with an error like "tableView:cellForRowAtIndexPath: is required" -- the app just runs and show a empty table.
I don't understand. Any ideas? This is the code for my view:
Interface CBox.h
#interface CBox : UIView <UITableViewDelegate, UITableViewDataSource>
And in the implementation file:
- (id) initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
tblParentComments = [[UITableView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.frame.size.width, frame.size.height)];
tblParentComments.delegate = self;
tblParentComments.dataSource = self;
//tblParentComments.userInteractionEnabled = NO;
tblParentComments.backgroundColor = [UIColor clearColor];
tblParentComments.separatorStyle = UITableViewCellSeparatorStyleNone;
tblParentComments.bounces = NO;
[self addSubview:tblParentComments];
}
return self;
}
#pragma mark - UITableViewDelegate + UITableViewDatasource
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"num of rows = %d", parentComents.count);
return 1; // I set a non-zero value for test
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
.... // I set a breakpoint here, never been called here
}
YES..i have the same problem... and I just found out the solution.
In my class i use different inits with different parameters.
In my -(void)viewDidLoad i use to alloc the table view with CGRectZero, and ONLY in this case IF u DONT set up the FRAME of the UITableView then:
the numberOfRowsInSection will BE CALLED
the cellForRowAtIndexPath will NEVER BE CALL
I just set up my UITableView frame and it's works.
As I read above comments I can figure out couple of things:
You probably have messed up a bit structure of your code. You should always conform to protocols in your view controller - ! not view. Alternatively, what I like to do (as it gives me better control over my code and it keeps things clean), separate protocols out of view controller - means create new object (model object) that will handle everything what table requires and it will conforms to table delegate and datasource.
If you organise your code wisely, you should avoid situation you described.
Also I believe you may have 2 objects conforming to table protocols, and thats where the things get ugly.

reloadData doesn't call cellforrowatindexpath

Maybe it's a similar beginning, but it's true.
first of all sorry if this isn't formatted correctly, first time doing this. I've been using stackoverflow to find help for a long time now and it's been very helpful (thank you all), but this is the first time I've posted a question of my own. This question has been asked many times, but when I call [self.tableView reloadTable] the methods numberOfSectionsInTableView and numberOfRowsInSection are called but not cellForRowAtIndexPath.
Every answer I've seen when searching has been a variation of:
The tableView is nil
numberOfRowsInSection is 0
tableView's delegate/data source not set. None of these are the case for me so I'm wondering what else could be wrong.
But I'm not sure 4. calling reloadTable on the wrong uiTableView. Or it's about some other false.
Now my APP is similar to dropbox,
first when we log into it, we get a file list(include directories) in the TableView.also, I added a toolbar in the bottom of the view by [self.navigationController.view addSubview:toolBar], when I touch the button item "refresh", it calls [self.tableView reloadData] and works well.
Second when we select a directory we will get a new file list table which is pushViewController by self.navigationController, but this time when we touch the "refresh", the statement [self.tableView reloadData] calls numberOfSections, numberOfRows, not cellForRowAtIndexPath
Any ideas as to why cellForRow's not being called the Second time? Thanks in advance.
FileListViewController.h
#interface FileListViewController : UITableViewController <UITableViewDelegate, UITableViewDataSource>
FileListViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (isDir) {
FileListViewController *fileListViewController = [[FileListViewController alloc] init];
[self.navigationController pushViewController:fileListViewController animated:YES];
}
}
- (void)refresh
{
[Utilities refresh];//Utilities is my custom class.
[self viewDidLoad];
[self.tableView reloadData];
}
My return number of section and row in table view is not 0.
When I added NSLog(#"Calling reloadData on %#", self.tableView); into "refresh":
- (void)refresh
{
[Utilities refresh];//Utilities is my custom class.
[self viewDidLoad];
NSLog(#"Calling reloadData on %#", self.tableView);
[self.tableView reloadData];
}
Then it returns Calling reloadData on ; contentOffset: {0, 0}>. Delegate: FileListViewController, DataSource: FileListViewController
You should not manually call [self viewDidLoad]. This method is designed to be overridden, and is automatically called. For more information, please read this documentation.

Show a preloaded search results?

I have a non-tableview view with a searchbar in it, and while it works perfectly, the search display controller hides the table view and overlays a dark dimmed view when an empty string is in the searchbar. I want it to show a preloaded data when the empty string is in the searchbar instead of hiding the table view and overlaying the dark dimmed view underneath the searchbar. Just like how the Google search bar in Safari for iOS works.
I found a similar question asked on stackoverflow before:
UISearchDisplayController - how to preload searchResultTableView, I couldn't really get it to work.
I have no problem getting the preloaded data and setting the current data to it, but I'm not sure how to prevent the displaycontroller from removing the searchResultsTableView.
Thanks in advance.
I finally found a way to do this.
I found out that the searchDisplayController simply removes the searchResultsTableView from the superview, so I just added the table view back into the superview whenever the display controller tried to hide the table view:
- (void)searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView
{
// add the tableview back in
[self.view addSubview:self.searchDisplayController.searchResultsTableView];
}
and then I also have to show the tableview the first time the searchbar is clicked, so I did:
- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller
{
// after the data has been preloaded
self.searchResults = self.allItems;
[self.searchDisplayController.searchResultsTableView reloadData];
}
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller
{
[self.view addSubview:self.searchDisplayController.searchResultsTableView];
}
For me, 'allItems' is where I stored all the searchable items and 'searchResults' is where the filtered items (after the search) is stored. And of course, you would have to preload the items (e.g. search history) before reloading the data.
I don't know if this is a nice way or not to do it in terms of the performance and what not, but it worked perfectly for me, and I hope this could be useful for other people as well. Please comment if there is a better way to do this.
After hours and hours I finally figured out a solution that works in iOS 7
Just implement the following two methods in your UISearchDisplayDelegate
-(void)searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView {
// We need to prevent the resultsTable from hiding if the search is still active
if (self.searchDisplayController.active == YES) {
tableView.hidden = NO;
}
}
When the search starts, the searchResultsTableView is being hidden automatically, so we need to unhide it again
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller {
controller.searchResultsTableView.hidden = NO;
// Then we need to remove the semi transparent overlay which is here
for (UIView *v in [[[controller.searchResultsTableView superview] superview] subviews]) {
if (v.frame.origin.y == 64) {
[v setHidden:YES];
}
}
}
I found a much better solution to this issue, and it seems to work perfectly on iOS 6 and 7. While it is still a hack, its a much cleaner and future proof hack than the above. The other solutions do not work consistently and prevent some UISearchDisplayDelegate methods from ever firing! Further I had complex insetting issues which I could not resolve with the above methods. The main issue with the other solutions is that they seriously confuse the internals of the UISearchDisplayController. My solution is based on the observation that UISearchDisplayContoller is a UISearchbarDelegate and that the automatic undimming & showing of results table can be triggered by simulating a keypress in the search field! So:
- (void) searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller
{
if ([controller respondsToSelector: #selector(searchBar:textDidChange:)])
[(id<UISearchBarDelegate>)controller searchBar: controller.searchBar textDidChange: #" "];
}
This code is future proof against crashing by checking it responds to the UISearchbarDelegate method, and sends space #" " to trick the UISearchDisplayController into thinking user has typed a letter.
Now if the user types something and then erases it, the table will dim again. The other solutions try to work around this by doing something in the searchDisplayController:didHideSearchResultsTableView: method. But this doesn't make sense to me, as surely when you cancel the search it will need to truly hide your results table and you may need to run code in this case. My solution for this part is to subclass (note you could probably use a Method Swizzled Category to make it work everywhere if needed in your project):
// privately declare protocol to suppress compiler warning
#interface UISearchDisplayController (Super) <UISearchBarDelegate>
#end
// subclass to change behavior
#interface GMSearchDisplayController : UISearchDisplayController
#end
#implementation GMSearchDisplayController
- (void) searchBar: (UISearchBar *) searchBar textDidChange: (NSString *) searchString
{
if (searchString.length == 0)
searchString = #" ";
if ([super respondsToSelector: #selector(searchBar:textDidChange:)])
[super searchBar: searchBar textDidChange: searchString];
}
#end
This code works by intercepting the textDidChange delegate method and changing nil or empty strings in to space string #" " preventing the normal hiding/dimming that occurs on an empty search bar. If you are using this second bit of code, then you could modify the first bit to pass a nil instead of #" " as this second bit will do the needed conversion to #" " for you.
In my own project, I needed to handle the case that user does type a space, so instead of #" " above I used a defined token:
// arbitrary token used internally
#define SEARCH_PRELOAD_CONDITIONAL #"_#preresults#_"
And then handle it internally by converting it back to nil string:
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
if ([searchString isEqualToString: SEARCH_PRELOAD_CONDITIONAL])
searchString = nil;
}
Enjoy! :)
This works in iOS 8:
- (void)searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView
{
self.searchDisplayController.searchResultsTableView.hidden = NO;
}
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller
{
self.searchDisplayController.searchResultsTableView.hidden = NO;
[self.searchDisplayController.searchResultsTableView.superview.superview bringSubviewToFront:self.searchDisplayController.searchResultsTableView.superview];
CGRect frame = self.searchDisplayController.searchResultsTableView.frame;
self.searchDisplayController.searchResultsTableView.frame = CGRectMake(frame.origin.x, 64, frame.size.width, frame.size.height);
}
When you start searching this method gets called. Add the searchResultsTableView and unhide it. It would then display your already preloaded data. I must have your data preloaded in order for this to work.
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller
{
CGRect testFrame = CGRectMake(0, self.notesSearchBar.frame.size.height, self.notesSearchBar.frame.size.width, self.view.frame.size.height - self.notesSearchBar.frame.size.height);
self.searchDisplayController.searchResultsTableView.frame = testFrame;
[self.notesSearchBar.superview addSubview:self.searchDisplayController.searchResultsTableView];
// [self.view addSubview:self.searchDisplayController.searchResultsTableView];
controller.searchResultsTableView.hidden = NO;
}
-(void) searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView
{
CGRect testFrame = CGRectMake(0, self.notesSearchBar.frame.size.height, self.notesSearchBar.frame.size.width, self.view.frame.size.height - self.notesSearchBar.frame.size.height);
self.searchDisplayController.searchResultsTableView.frame = testFrame;
[self.notesSearchBar.superview addSubview:self.searchDisplayController.searchResultsTableView];
// [self.view addSubview:self.searchDisplayController.searchResultsTableView];
controller.searchResultsTableView.hidden = NO;
}
-(void) searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller
{
controller.searchResultsTableView.hidden = YES;
}
iOS 9 working code.
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller {
// Bring the search table view to the view's front
self.searchDisplayController.searchResultsTableView.hidden = NO;
[self.searchDisplayController.searchResultsTableView.superview bringSubviewToFront:self.searchDisplayController.searchResultsTableView];
}
- (void)searchDisplayController:(UISearchDisplayController *)controller didHideSearchResultsTableView:(UITableView *)tableView {
// We need to prevent the resultsTable from hiding if the search is still active
if (self.searchDisplayController.active == YES) {
tableView.hidden = NO;
}
}
Swift 2.0+ version
func searchDisplayControllerDidBeginSearch(controller: UISearchDisplayController) {
controller.searchResultsTableView.hidden = false
controller.searchResultsTableView.superview!.bringSubviewToFront(controller.searchResultsTableView)
}
func searchDisplayController(controller: UISearchDisplayController, didHideSearchResultsTableView tableView: UITableView) {
if ((searchDisplayController?.active) != nil) {
tableView.hidden = false
}
}

Confusion with storyboard and UITableView data source: How to display text in a cell

So I've been given an assignment in my Mobile apps class: make a color game app for the iphone.(The description of how to game works is at the top of the pasted viewcontroller.h file below.)
I'm very new to Objective-C and cocoa, but have managed to troubleshoot and fix a lot of things in this app. The problem I have right now is that I don't know how to properly initialize and send UITableViewCells to the view. I'm confused because all of the tutorials I've found online use datasource methods to change different attributes of the UITableView and the cells as well. I'm not sure how these methods will interact with the controls I've already placed. I'm confused because I added them by the storyboard file, not by defining tableview attributes with datasource code.
My immediate issue is that my program won't display the proper text to the cells textlabel and detailtextlabel.
I've looked everywhere online for UITableView and UITableViewCell tutorials, but they are all from years ago and I'm not sure if the advent of the storyboard has changed the way I would treat these controls.
All of the code I've written is either in the viewcontroller.m or viewcontroller.h files.
The method within ViewController.m file, that should call the cell and display text and detail text:
-(IBAction)enterClicked
{
//On enter- send instance colors to the colorTable row[i], perform comparisons and append the resulting symbols to the instanceResults String. Send instanceResults string to the resultTable row[i]. When game counter reaches 6, gameOver. If on comparisons check, the instanceColors are the same as the gameColors, then the player wins.
[self checkForLoss];
if(!self.gameOver)
{
resultOfGuess = [self comparePlayerInputToGameColors:guessColors];
[listOfGuesses addObject:guessColors];
[listOfOutcomes addObject:resultOfGuess];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:_numberOfTurnsPlayed inSection:0];
UITableViewCell *thisCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
thisCell.textLabel.text = [self.listOfGuesses lastObject];
thisCell.detailTextLabel.text = [self.listOfOutcomes lastObject];
[guessColors setString:#""];
if([self checkForWin:resultOfGuess])
[UpdateLabel setText:#"You have won!"];
else
[UpdateLabel setText:#""];
self.colorCounter = 0;
self.isStepOne = YES;
_numberOfTurnsPlayed++;
}
else
{
if([self checkForLoss])
[UpdateLabel setText:#"You have lost!"];
}
}
The UITableView DataSource Methods I've called at the bottom of the viewcontroller.m file:
#pragma mark - UITableViewDataSource protocol
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
if(section == 0)
return #"Guesses: Results:";
return 0;
}
-(NSInteger) numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 6;
}
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
}
return cell;
}
So my questions are: Can I change a control's properties with datasource methods, if I created the controls through the storyboard? How do I properly display the text in a uitableview's cells?
Edit/update: Thank you, I've used your advice jrturton, but now I've found something peculiar that may be the source of my problems. in my viewController.h file I've changed my header from
ViewController: UIViewController to ViewController: UITableViewController
Thinking that the datasource methods I call within the viewcontroller files have to be able to call the same methods and properties of the class that I call in the header-- Also, I see this done in other UITableView tutorial files.
The problem is that when I change the header to read-- ViewController: UITableViewController -- and I try to compile, I get this error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[UITableViewController loadView] loaded the "2-view-3" nib but didn't get a UITableView.'
It compiles fine if I use just :UIViewController in the header file though.
Any ideas?
Further update: I''ve noticed within my storyboard that the only available ViewController object is a UIViewController object, while in the other tutorial files I've seen, this ViewController object is a UITableViewController object. I imagine this is my problem, but I can't seem to switch my UIViewController object to a UITableViewController. All I can do is create a new one, which isn't what I want, I imagine.
Your action method should update the data model (which I think it does, since it changes your listOfGuesses array). You then need to let your table view know that you have added or updated rows so that it can re-load them for you - check the UITableView documentation for reloading data or specific rows.
Creating a cell outside of the datasource methods isn't going to let that cell appear in your table.
At the moment I'm guessing you have 6 empty cells in your table view? You need to populate the text and detail labels in your cellForRowAtIndexPath method. The difference now there are storyboards is that you don't need to do the if (cell == nil) bit, as long as you have set the re-use identifier in your storyboard prototype cell then it will do all that for you. So your cellForRowAtIndexPath method can be reduced to:
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
// This will dequeue or create a new cell based on the prototype in your storyboard
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
// Put your actual configuration here based on your model array
cell.textLabel.text = #"Hello";
return cell;
}
Further hints (this is homework so I'm not giving full samples)
'indexPath.row` in the above method will give you the index from your model array that the cell refers to
You have defined the table as having 6 rows, but you are adding items to your model arrays as you go - so when the table asks for row 5, and your model only has 3 entries, you need to deal with this. Consider changing the number of rows in the table dynamically and using table view methods to indicate that new rows have been added. Again, see the UITableView documentation for this.
Typically the text is set in each cell by accessing the setText property:
[[cell textLabel] setText:#"static string"];
or
[[cell textLabel] setText:someNSString];
or with .dot notation
cell.textLabel.text = someNSString;
return cell;
BTW this is done in the method:
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:

getting error unrecognized selector sent at tableview when last row of table view is clicked

i m parsing json data and populating the tableview and making some validation with the incoming json data..everthing works fine.i made the code such that when the last table view row is clicked it got to open a modal view controller.when clicked .i m getting this error [tableiew1] Unrecognised selector send at the instance...could u guys help me out below is the code.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic may go here. Create and push another view controller.
if (indexPath.row == 5) {
if (self.dvController6 == nil)
{
Vad_tycker *temp = [[Vad_tycker alloc] initWithNibName:#"Vad_tycker" bundle:[NSBundle mainBundle]];
self.dvController6 = temp;
[temp release];
}
[self presentModalViewController:self.dvController6 animated:YES];
}
}
Seems like you have forgotten to provide the access for the tableView1 in Vad_tycker.
Or You should do a crosscheck whether you have assigned the correct instance in tableView delegate's and also make sure to provide the implementation for the method of delegate's in their respect target classes.
I think you forgot to connect the tableView Datasource and Delegate methods in the vad_tycker controller.
Also check that the instance of UITableView i.e. in your case tableView1 is also connected with the TableView. on the view.
Thanks