I have a tableview that has a UISegmentedControl as the tableHeaderView for my tableViewController. I have two arrays as properties that will populate the cells based on which index is selected in the segmentedController. When the two arrays have a different number of items in them, I get an out of bounds exception, which I would expect. When the number of items is the same in each array, I can reload the tableView data and everything works fine. How can I prevent the controller from trying to reuse a cell when the cell isn't needed anymore?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Downloads Cell" forIndexPath:indexPath];
// 1st option selected
if (self.downloadTypeSelector.selectedSegmentIndex == 0) {
if (indexPath.row < self.talkDownloads.count) {
// Configure the cell...
Talk *talk = [self.talkDownloads objectAtIndex:indexPath.row];
cell.textLabel.text = talk.title;
cell.detailTextLabel.text = talk.speaker;
}
// 2nd option selected
} else if (self.downloadTypeSelector.selectedSegmentIndex == 1) {
if (indexPath.row < self.speechesDownloads.count) {
Speech *speech = [self.speechesDownloads objectAtIndex:indexPath.row];
cell.textLabel.text = speech.title;
cell.detailTextLabel.text = speech.speaker;
}
}
return cell;
}
I see that you have a condition that checks downloadTypeSelector.selectedSegmentIndex and uses the respective array accordingly, which is fine.
Do you have the same if else condition in - tableView:numberOfRowsInSection: that checks the selectedSegmentIndex and returns the correct count of the respective array?
If that works then I think checking if indexPath.row < array.count will be obsolete too.
Cheers!
Related
I have a setting screen that is a UITableView with rows of settings. When user open that screen I load stored settings and filled to UITextField etc... Everything was fine.
But there are some of the checkmark settings, I've been trying to check this in programmatically way but is not work, here is my code:
-(void)viewDidAppear:(BOOL)animated{
[self LoadCurrentRecord];
if(_previousValid)
{
NSIndexPath *regionFromData = [NSIndexPath indexPathForRow:_regionAutoCheck inSection:3];
[self.tableView cellForRowAtIndexPath:regionFromData].accessoryType = UITableViewCellAccessoryCheckmark;
}
[self.tableView reloadData];
}
In fact, I can see data can load by check to this category but I didn't see checkmark icon.
Any idea?
You must need to set it in this method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell; // Create Cell
if(_previousValid && indexPath.row == _regionAutoCheck && indexPath.section == 3 )
{
// SET CHECKMARK
} else {
// SET NONE
}
}
Here is how you should work with table- and collectionViews:
Have a collection of dedicated data-elements for each of which a cell will be used to display its data. In your case this would be some kind of Region object.
Implement the table/collectionview datasource methods that return number of cells in section and number of sections based on that collection.
Make sure you configure each cell ONLY in -tableView:cellForRowAtIndexPath: / -collectionView:cellForItemAtIndexPath:, just like PKT's answer. This cell might be reused, so assume any changes you made to this type of cell you need to update here.
When data changes, create an indexPath for the object that changes based on its position in the collection from 1. Then call '-reloadRowAtIndexPath:'/ -reloadItemAtIndexPath for that object. This will cause the tableView/collectionView to call -tableViewcellForRowAtIndexPath:/-`collectionView:cellForItemAtIndexPath:' again. As this method now contains the logic for configuring the cell based on your data-object, everything is in sync.
I can't stress enough how important 1. is. Simplest example is: each section is an array of data objects. If you have multiple sections, you can add each array to another array:
// one section:
NSArray *myData =
#[
#[dataItem1, dataItem2, dataItem3]
#[dataItem4, dataItem5, dataItem6]
];
- (NSUInteger) numberOfSectionsInTableView: (UITableView *) tableView
{
return myData.count;
}
- (NSUInteger) tableView: (UITableView *) tableView numnberOfRowsInSection: (NSUInteger) section
{
NSArray *dataForSection = myData[section];
return dataForSection.count;
}
- (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath:
{
NSArray *dataForSection = myData[indexPath.section];
MyObject *dataObject = dataForSection[indexPath.row];
NSString * cellID = #"myCellID";
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier: cellID];
if (nil == cell)
{
// create a cell
// ...
}
// configure the cell based on the data object
if (dataObject.isBlue)
{
cell.contentView.backgroundColor = [UIColor blueColor];
}
else
{
// N.B.! don't forget the else clause, as cells are reused, so this
// cell might be recycled from a cell that was used to display
// the data of a blue data object before.
cell.contentView.backgroundColor = [UIColor blueColor];
}
return cell;
}
Finally, I've found a really simple solution for this case.
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.row == _regionAutoCheck && indexPath.section == 3)
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else{
cell.accessoryType = UITableViewCellAccessoryNone; // for other cases remove it
}
}
UPDATE
After that, add [self.tableView reloadData]; into -(void)viewDidAppear:(BOOL)animated and you will see it works.
That's all.
Thanks all for help
I have created a tableview with multiple prototype (4) cells in order to display different content in each cell but being a newb - I am not clear on how to then code this in the tableviewcontroller to pull data into the multiple cells (just using multiple simple arrays with one data point for each label for now to test it) but I can only get the data to populate in the first cell - and not clear how to code to get the data into the remaining cells. I created 4 separate customtableviewcell files as well. Can someone point me in the right direction on how to code so I can get data into the four separate prototype cells? I need it to be able scroll as it won;t all fit on the the screen which is why I chose table view to do this - but there will only ever be these four sections in the view (with different data depending on what you pushed to get here) should I not be using tableview? if I should be using something different like a view controller with 4 views instead? will it scroll so the user can see all sections? Thanks in advance for any help and suggestions.
You should assign unique Identifier to the each cell in the Storyboard. Then, you can populate appropriate cells like this:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.section)
{
case 0:
{
MyCustomCell1 *cell = [tableView dequeueReusableCellWithIdentifier:#"cell_1"];
// Configure cell
return cell;
}
case 1:
...
default: return nil;
}
}
Consider creating custom subclasses of UITableViewCell to provide handy IBOutlets.
(1) You need 4 cells. So, prepare 4 custom cells by creating subclass of UITableViewCell. You will know how to create custom cell by search on google.
(2) Set number of sections to 1.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
(3) Set desired height for each cell
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
switch(indexPath.row)
{
case 0:
{
return 40;
}
case 1:
{
return 50;
}
case 2:
{
return 30;
}
case 3:
{
return 45;
}
default:
{
return 0; // Default case
}
}
}
(4) Set contents of each cell. The data will come from your data source i.e. Array or Dictionary.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *CellIdentifier = [NSString stringWithFormat:#"CellIdentifier%d%d",indexPath.section,indexPath.row];
if(indexPath.row == 0)
{
CustomCell1 *objCustomCell1 = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(objCustomCell1 == nil)
{
objCustomCell1 = [[CustomCell1 alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
objCustomCell1.selectionStyle = UITableViewCellSelectionStyleNone;
}
// Set row specific data here...
NSDictionary *dicObj = [arrYourDataSource objectAtIndex:indexPath.row];
objCustomCell1.myLabel.text = [dicObj objectForKey:#"your key"];
return objCustomCell1;
}
else if(indexPath.row == 1)
{
CustomCell2 *objCustomCell2 = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(objCustomCell2 == nil)
{
objCustomCell2 = [[CustomCell2 alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
objCustomCell2.selectionStyle = UITableViewCellSelectionStyleNone;
}
// Set row specific data here...
NSDictionary *dicObj = [arrYourDataSource objectAtIndex:indexPath.row];
UIImage *theImage = [UIImage imageNamed:[dicObj objectForKey:#"your key"]];
objCustomCell2.myImageView.image = theImage;
return objCustomCell2;
}
// Do same for remaining 2 rows.
return nil;
}
First do this and then add comment. We will move ahead.
I have a NSArray of NSDictionaries, in this array there are several values which I do not want to show in the UITableView, I would like to know how to avoid returning these cells in the tableView:cellForRowAtIndexPath:
I have tried to return nil; but this has caused me errors.
This is what my code looks like
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
CustomInstallCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[CustomInstallCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the cell...
cell.selectionStyle = UITableViewCellSelectionStyleGray;
currentInstallDictionary = [sortedItemsArray objectAtIndex:indexPath.row];
NSNumber *tempDP = [currentInstallDictionary objectForKey:#"dp"];
NSInteger myInteger = [tempDP integerValue];
if (myInteger == 0) {
return cell;
}
return nil; // gives error
}
any help would be appreciated.
This method must return a cell. It cannot return nil. The best thing to do is filter your list before you load your table and use the filtered array when dequeueing cells.
The UITableView is only asking for a cell because you told it to ask. Your implementation of the UITableViewDataSource protocol implements the two methods:
numberOfSectionsInTableView:
tableView:numberOfRowsInSection:
In those methods you determine how many cells should appear on screen. As Brian Shamblen answered, if you don't want that data to appear in the table view, some how ignore (filter, delete, whatever) that data when you calculate the number of sections and rows. If you do so, no "extra" cells will be requested.
I have a UITableView where the user should be able to select (check) multiple rows.
I have an NSMutableArray in my controller to hold the selected items, and in my cellForRowAtIndexPath method, I check whether the item is in that array and return the cell in a checked/unchecked state accordingly.
Here's the code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = kContactCellReuseIdentifier;
static NSString *searchIdentifier = kContactSearchCellReuseIdentifier;
POContactCell *cell;
// Configure the cell...
if (tableView == self.tableView) {
cell = (POContactCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.contact = self.contacts[indexPath.row];
NSLog(#"Returned cell with name %#", cell.contact.name);
} else {
cell = (POContactCell*)[tableView dequeueReusableCellWithIdentifier:searchIdentifier forIndexPath:indexPath];
cell.contact = self.searchResults[indexPath.row];
}
if ([self.selectedContacts containsObject:cell.contact])
{
NSLog(#"was checked");
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else
cell.accessoryType = UITableViewCellAccessoryNone;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
POContactCell* tappedCell = (POContactCell*)[self tableView:tableView cellForRowAtIndexPath:indexPath];
NSLog(#"Selected contact %#", tappedCell.contact.name);
if ([self.selectedContacts containsObject:tappedCell.contact]) {
// cell is already selected, so deselect it
NSLog(#"It's already selected, so deselect it");
[self.selectedContacts removeObject:tappedCell.contact];
tappedCell.accessoryType = UITableViewCellAccessoryNone;
}
else
{
NSLog(#"It's not already selected, so select it");
[self.selectedContacts addObject:tappedCell.contact];
tappedCell.accessoryType = UITableViewCellAccessoryCheckmark;
}
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:NO];
}
This code works... except for the first selection. The first cell that the user taps will get checked and will never get unchecked. I see from the log statements that all the cells are going through the exact same process and it's correctly recognizing the selection state of the first tapped row too, even though the accessory view doesn't reflect it.
After the first selection, all the other rows work perfectly.
Any debugging ideas?
You should be putting self.contacts[indexPath.row] (or self.searchResults[indexPath.row], as appropriate) in your array of selected items, and checking whether or not those objects exist or not in the array when the user taps a cell. You are almost doing that, it would appear, by setting cell.contact to the object from your data source and checking for cell.contact in your array. But I'd try putting the object directly into your array, e.g.
id contact = self.contacts[indexPath.row];
if ([self.selectedContacs containsObject:contact])
...
and stop checking if cell.contact is in the array to determine "selected-ness".
In a UITableView there is a small set of actual UITableViewCell objects in memory, and they get re-used. The root of your problem could very well be this, because you are checking to see if cell.contact is in your set of selected items; when a cell is reused, unless you wrote your own prepareForReuse, the previous value of your custom attributes may not (likely will not) be cleared.
Does that make sense?
I'm trying to display a no results message on my tableview when it is empty. I have done the uilabel approach where it appears when it is empty but it seems like it's not how Apple has done it in the Contacts etc where the "No Results" move as well when you try to scroll up and down. Mine just stays there in one spot.
Does anyone know how to do this?
I think they added a No Results cell?
Yes. If you have no results to display, do the following
Create a boolean flag named noResultsToDisplay, or something else.
If you have no results to display then set noResultsToDisplay = YES, set it to NO otherwise.
In numberOfRowsInSection, if (noResultsToDisplay) return 3;
In cellForRowAtIndexPath, if (noResultsToDisplay && indexPath.row == 2) cell.textLabel.text = #"No Results";
#pragma mark -
#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 3;
}
// Customize the appearance of table view cells.
- (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];
}
// Configure the cell...
if (indexPath.row == 2) {
cell.textLabel.text = #"Empty cell";
}
return cell;
}
Two way of doing it: Either do as you suggest yourself, make a "No Results Cell" and have a state in your tableViewController that is BOOL resultIsEmpty = YES. In the cellForRowAtIndexPath you first test for this empty BOOL and return only the no result cell , remember to also check in numberOfRowsInSection so you can return 1 in the case of empty (otherwise it will probably return the length of your model array which of course is 0).
The other way is to make a y inset on the table view and place your label there. This can be done because UITableView is a subclass of UIScrollView.
self.tableView.contentInset = UIEdgeInsetsMake(heighOfNoResultLabel, 0, 0, 0);
Then in the "resultsDidLoad" or what your delegate for new data is called, you test if it is 0 and inset the tableView and place a label there. If it is not 0 set the inset to
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);
all 0's. You can animate this property, so that when there are no results, the tableview "scrolls" down to reveal the the "No Results" Label.
Bot solutions are valid I would say, about the same amount of code. The difference is probably what you can do with it afterwards.
I have resolved this by maintaining a label view with the same farme as the table has, and play with the "hidden" attribute of the label and table (always one is YES and the other is NO).
i have edited the accepted answer to be look like the no search results in tableView
Create a boolean flag named noResultsToDisplay, or something else.
1-If you have no results to display then set noResultsToDisplay = YES, set it to NO otherwise.
2-(this step changed)In numberOfRowsInSection, if (noResultsToDisplay) return 1;
// 1 returned not 3
3-(this step changed) In cellForRowAtIndexPath,
static NSString *CellIdentifier;
if (noResultsToDisplay){
CellIdentifier = #"Cell";
// in my case i use custom cell this step can be skipped if you use the default UITableViewCell
}
else{
CellIdentifier = #"DocInqueryCell";
}
DocumentationInqueryCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// don't forgt to add cell in your storyboard with identifier Cell and class your custom class , again this step can be skipped if you use the default cell
then
if (noResultsToDisplay) {
cell.textLabel.text = #"No Results";
}else{
do what you want in your custom cell
}