I have a one View controller managing 2 tableviews. I use a flag to track which table is selected. In each of the delegate functions I just check the flag and use the right table.
Everything works great except that when i load the second table which has lesser items than the first one, crashes when I scroll the table , for the following error.
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'no object at index 2 in section at index 0'
* First throw call stack:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"Drawing Row = %d Total num Of items = %d", indexPath.row, [[self.fetchedResultsControllerComments fetchedObjects] count]);
Prints this:
Drawing Row = 2 Total num Of items = 0
If the number of items in this table is correct, then why is this function getting called in the first place?
Here is the code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(currentSelectionTableType1)
{
// Draw first kind of cell.
PlainImageCell *cell1 = [tableView dequeueReusableCellWithIdentifier:#"ImageCell"];
if(cell1 == nil)
cell1 =[[PlainImageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"ImageCell"];
[self configureCell1:cell1 atIndexPath:indexPath];
return cell1;
}
// else Draw the second kind of cell
PlainTextCell *cell2 = [tableView dequeueReusableCellWithIdentifier:#"TextCell"];
if(cell2 == nil)
cell2 =[[PlainTextCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"TextCell"];
[self configureCell2:cell2 atIndexPath:indexPath];
return cell2;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
if(currentSelectionTableType1)
return [[self.fetchedResultsControllerDataSource1 sections] count];
return [[self.fetchedResultsControllerDataSource2 sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo;
if(currentSelectionTableType1)
{
sectionInfo = [self.fetchedResultsControllerDataSource1 sections][section];
}
else
{
sectionInfo = [self.fetchedResultsControllerDatasource2 sections][section];
}
return [sectionInfo numberOfObjects];
}
Thx
EDIT - based on the code you added:
You need to define one cell before your conditional and then configure that cell based on the conditional and then return the cell after the conditional. If you need both an ImageView and a TextCell, you can configure those objects in the conditional code.
Why not just use one TableView with two datasources and switch out the datasources as needed?
Something like this:
#property(nonatomic, strong) NSArray *tableViewDataSource1;
#property(nonatomic, strong) NSArray * tableViewDataSource2;
#property(nonatomic) BOOL usingDataSource2;
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (self.usingDataSource2) {
return [self.tableViewDataSource2 count];
}
return [self. tableViewDataSource1 count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Create the cell before conditional
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"reuseIdentifier" forIndexPath:indexPath];
// Conditionally configure the cell
if (self.usingDataSource2) {
// Configure Cell using self.tableViewDataSource2 data
} else {
// Configure Cell using self.tableViewDataSource1 data
}
// Return the configured cell after the conditional
return cell;
}
Related
I have two UITableView's embedded into a UIViewController. Both are dynamic and retrieve the different data. The issue is that the indexPath is used in both tables when using - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath.
Is there a work-around here? I am referencing my entities like so:
//tableView2 has a cell named differently
static NSString *CellIdentifier = #"Cell";
static NSString *CellIdentifier2 = #"Cell2";
//reference the cells to the right tables
UITableViewCell *cell = [self.tableView1 dequeueReusableCellWithIdentifier: CellIdentifier forIndexPath:indexPath];
UITableViewCell *cell2 = [self.tableView2 dequeueReusableCellWithIdentifier: CellIdentifier2 forIndexPath:indexPath];
//create access to details the cell will display from the core date
entity1 * e1 = [self.fetchedResultsController objectAtIndexPath:indexPath];
//this is the issue
//entity2 * e2 = [self.fetchedResultsController2 objectAtIndexPath:indexPath];
The issue as mentioned is the indexPath. If I try to run the app with both entities equating to their respective fetchedResults it causes an error due to the indexPath being reused. If I create another indexPath it causes this error (understandably so):
'Invalid index path for use with UITableView. Index paths passed to table view must contain exactly two indices specifying the section and row. Please use the category on NSIndexPath in UITableView.h if possible.'
My question is, how do I reuse or create a separate reference to the NSIndexPath inside the - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method, or is there a workaround?
I have already attempted using if/if else statements inside the method but this displays the same number of cells in both tables; if table1 has 10 rows there will be 10 rows in table2 regardless of content.
The below code is possibly incorrect, I am not experienced enough implementing two table views to know, but it's not the main issue:
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
if ([self fetchedResultsController]){
return [[self.fetchedResultsController sections] count];
}
if ([self fetchedResultsController2]) {
return [[self.fetchedResultsController2 sections] count];
}
else{
return 0;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if ([self fetchedResultsController]){
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections]objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
if ([self fetchedResultsController2]){
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController2 sections]objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
else{
return 0;
}
}
First of all you don't have to create two sections.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
Then you have to set your Rows according to your tableView
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (tableView == self.tableView1){
//Return the count you want inside self.tableView1
}
else if (tableView == self.tableView2){
//Return the count you want inside self.tableView2
}
else{
return 0;
}
}
Finally inside your cellForRowAtIndexPath set your table Values according to your TableView.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView == self.tableView1){
static NSString *CellIdentifier = #"Cell";
//reference the cells to the right tables
UITableViewCell *cell = [self.tableView1 dequeueReusableCellWithIdentifier: CellIdentifier forIndexPath:indexPath];
}
else if (tableView == self.tableView2){
static NSString *CellIdentifier2 = #"Cell2";
UITableViewCell *cell2 = [self.tableView2 dequeueReusableCellWithIdentifier: CellIdentifier2 forIndexPath:indexPath];
}
else{
return 0;
}
}
you need to set tags for tableviews. and in cellforRowAt IndexPath
if(tabaleView.tag==100)
{
UITableViewCell *cell = [self.tableView1 dequeueReusableCellWithIdentifier: CellIdentifier forIndexPath:indexPath];
}
else
{
UITableViewCell *cell2 = [self.tableView2 dequeueReusableCellWithIdentifier: CellIdentifier2 forIndexPath:indexPath];
}
I have an iOS 7/8 application. In a view I have a static UITableView with given number of cells - 17 in my case.
One of the cells contains another UITableView with dynamic cells. In the case described they are 20.
Because of the difference in the number of cells (+3) I get
NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 17 beyond bounds [0 .. 16]
exception when I set the 18th cell in the dynamic view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (tableView == _dynamicTableView) {
NSLog(#"%lu", (unsigned long)[[_filter types] count]);
return [[_filter types] count];
} else {
return 17;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (tableView == _dynamicTableView) {
static NSString *cellIdentifier = #"type";
TypeTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[TypeTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:cellIdentifier];
}
NSArray *types = [_filter types];
Type *type = [types objectAtIndex:[indexPath row]];
[cell.nameLabel setText:[type name]];
return cell;
} else {
return [super tableView:tableView cellForRowAtIndexPath:indexPath];
}
}
When I go to the Storyboard and increase the number of static cells to something like 30 everything works fine. tableView:numberOfRowsInSection... method removes the unused cells - only 17 static and 20 dynamic cells are shown.
I am aware that the source of my problem is having two UITableView-s in one controller and the large amount of 'if'-s.
Try to check with tags, you can give different tags to each table and return number of cells according to that.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (tableView.tag == 1) // Tag for _dynamicTableView
{
return [[_filter types] count];
}
else {
return 17;
}
}
I have managed to make the TableView expandable. The problem is that when I start the app, it's always opened. I want it to be closed, and to be opened just when I hit the row.
What am I missing here? How can I set it to be open at start?
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 3;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (!indexPath.row)
{
// first row
cell.textLabel.text = #"Expandable"; // only top row showing
}
else
{
cell.textLabel.text = #"Some Detail";
cell.accessoryView = nil;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
return cell;
}
- (BOOL)tableView:(UITableView *)tableView canCollapseSection:(NSInteger)section
{
return YES;
}
You first create a regular table with some array of booleans that holds the state of each row (open/close).
Then, when you hit a first row in a section, I reload the table with new rows:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//subject hitted
if (indexPath.row == 0)
{
// set relevant boolean here ,also , reload the table again with the new rows
collapsedRows[indexPath.section]= ! collapsedRows[indexPath.section];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section]withRowAnimation:UITableViewRowAnimationFade];
}
//sub subject hitted
else
{
}
}
I'm trying to get a UITableView to work with Core Data using an NSFetchedResultsController, while also having an insertion control (UITableViewCellStyleInsert) as the last row during editing.
Since the insertion control is just another tableviewcell, just with a different editing style, I have changed the appropriate UITableViewDatasource delegate methods, like:
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView
editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self isInsertionControl:indexPath]) {
return UITableViewCellEditingStyleInsert;
} else {
return UITableViewCellEditingStyleDelete;
}
}
The reported number of rows should also be updated accordingly, when editing (assuming there's only one section for now):
(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id<NSFetchedResultsSectionInfo> sectionInfo = self.fetchedResultsController.sections[section];
NSUInteger numberOfObjects = sectionInfo.numberOfObjects;
// FIXME, things will go out of hand with more than one section.
if (self.tableView.editing) {
return numberOfObjects + 1;
} else {
return numberOfObjects;
}
}
And if requested, return the appropriate cell for the insertion row:
-(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *tableViewCell = nil;
if ([self isInsertionControl:indexPath]) {
static NSString *InsertIdentifier = #"InsertCell";
tableViewCell = [tableView dequeueReusableCellWithIdentifier:InsertIdentifier
forIndexPath:indexPath];
} else {
tableViewCell = [tableView dequeueReusableCellWithIdentifier:self.cellIdentifier
forIndexPath:indexPath];
[self configureCell:tableViewCell atIndexPath:indexPath];
}
return tableViewCell;
}
This doesn't work, can anybody help me find out why?
QUICK EDIT
To be clear, I have 2 prototype cells in my storyboard, one for the regular content and one for representing the insertion row, both with the appropriate reuse identifier.
I'm trying to make it so my UITableView always has a specific row at the bottom, anchored there.
I'm using a NSFetchedResultsController to perform a fetch, and then the regular Apple boilerplate for detecting a merged context change.
What I'd like to do is always have one row at the bottom of the results, "Not What You're Looking For?". How would I do this? I'm comfortable with custom cell types, but I can't get even a cell of the same type to anchor to the bottom.
Code that adds one more row than what is in the fetchedResultsController:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
return[sectionInfo numberOfObjects]+1;
}
Code for cellForRowForIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifierNormal = #"NormalCell";
UITableViewCell *cell = nil;
cell = [tableView CellIdentifierNormal];
if (cell == nil) {
cell = [[NormalCell createCell]autorelease];
}
// For regular results, go configure the cell. For the extra 'Special Row' at bottom, assign it the Special cell.
if([indexPath indexAtPosition:0] <= [[[fetchedResultsController sections] objectAtIndex:0] numberOfObjects])
[self configureCell:cell atIndexPath:indexPath];
else {
if (cell == nil) {
cell = [[SpecialCell createCell] autorelease];
}
SpecialCell *nc = (SpecialCell*)cell;
nc.labelFirstLine.text = #"Not What You're Looking For?";
}
return cell;
}
This would work if not using a NSFetchedResultsController, but what happens is that whenever a cell is updated, the method 'configureCell' is being called from the Controller, which knows nothing about SpecialCell.
From - (void)controller:(NSFetchedResultsController *)controller didChangeObject:......
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
And here is configureCell:
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
// Configure the cell
// Go get it from coredata
NormalCell *vc = (NormalCell*)cell;
NormalObject *no = (NormalCell *)[fetchedResultsController objectAtIndexPath:indexPath];
....<ASSIGNMENT TO CELL HERE>....
}
in cellForRowAtIndexPath when you are creating cell for different rows from array, then keep the condition that if it reaches the end of array then you add your custom cell with a string that you want...
For more information please add the code...