Reputation: 2361
My set up here is that I have a UITableView
with static cells defined in a storyboard. In one of static cells, I have a nested UITableView
that is dynamic and has its content populated programmatically. I have researched this and assume this to be a perfectly valid configuration, as you are allowed to have any kind of UIView
subclass (i.e., a table view) inside a UITableViewCell
.
However, whenever the view is loaded, I get an index beyond bounds exception, even though my backing data structure is valid and I've assured all the inner table view's delegate methods return the correct values. I have placed breakpoints in my cellForRowAtIndexPath:
method, but that is fruitless, as the exception is thrown in between calls to that method. So I'm not sure what I am doing wrong or if secretly this is an invalid configuration.
The goal of this interface is to show an invoice and all its associated line items (the nested/embedded table view) on the same screen, versus the way I had it before where you would tap to view the line items on a separate UITableViewController
view.
See my screenshot and code below.
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSInteger count = 0;
if (tableView == self.tableView) {
count = [super numberOfSectionsInTableView:tableView];
} else if (tableView == itemsTable) {
count = 3;
}
return count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger count = 0;
if (tableView == self.tableView) {
count = [super tableView:tableView numberOfRowsInSection:section];
} else if (tableView == itemsTable) {
switch (section) {
case 0:
// Header
count = 1;
break;
case 1:
// Items
count = assoicatedInvoice.items.count;
break;
case 2:
// Add item
count = 1;
break;
default:
break;
}
}
return count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell * cell;
if (tableView == self.tableView) {
cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
} else if (tableView == itemsTable) {
InvoiceDetailEmbeddedLineItemViewCell * iCell;
switch (indexPath.section) {
case 0:
// Header section
iCell = (InvoiceDetailEmbeddedLineItemViewCell *) [tableView dequeueReusableCellWithIdentifier:invoiceDetailLineItemHeadersViewCellIdentifier];
break;
case 1: {
// Item
InvoiceItem * associatedItem = [assoicatedInvoice.items objectAtIndex:indexPath.row];
iCell = (InvoiceDetailEmbeddedLineItemViewCell *) [tableView dequeueReusableCellWithIdentifier:invoiceDetailEmbeddedLineItemViewCellIdentifier];
iCell.nameLabel.text = associatedItem.name;
iCell.qtyLabel.text = [UtilityFunctions decimalFormatForInput:associatedItem.quantity minDecimalPlaces:0 maxDecimalPlaces:2];
iCell.priceLabel.text = [UtilityFunctions currencyFormatForInput:associatedItem.price];
iCell.taxLabel.text = [UtilityFunctions percentageFormatForInput:associatedItem.tax minDecimalPlaces:0 maxDecimalPlaces:2];
break;
}
case 2:
// Add new item
iCell = (InvoiceDetailEmbeddedLineItemViewCell *) [tableView dequeueReusableCellWithIdentifier:invoiceDetailLineItemAddItemCellIdentifier];
break;
default:
break;
}
cell = iCell;
}
return cell;
}
- (NSString *) tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
NSString * title = nil;
if (tableView == self.tableView) {
if (section != 0) {
title = [super tableView:tableView titleForHeaderInSection:section];
} else {
title = [Invoice friendlyNameForInvoiceType:assoicatedInvoice.invoiceType];
}
}
return title;
}
- (UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if (tableView == self.tableView) {
return [super tableView:tableView viewForHeaderInSection:section];
} else if (tableView == itemsTable) {
return nil;
}
return nil;
}
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat height = 0.0;
if (tableView == self.tableView) {
if (indexPath.section != 1) {
height = [super tableView:tableView heightForRowAtIndexPath:indexPath];
} else {
height = itemsTable.contentSize.height;
}
} else if (tableView == itemsTable) {
height = 30.0;
}
// height = [super tableView:tableView heightForRowAtIndexPath:indexPath];
return height;
}
After running, this is the stack trace I get.
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
*** First throw call stack:
(
0 CoreFoundation 0x041dc1e4 __exceptionPreprocess + 180
1 libobjc.A.dylib 0x0303a8e5 objc_exception_throw + 44
2 CoreFoundation 0x041908b2 -[__NSArrayI objectAtIndex:] + 210
3 UIKit 0x0244935f -[UITableViewDataSource tableView:indentationLevelForRowAtIndexPath:] + 127
4 UIKit 0x021c2f34 -[UITableViewController tableView:indentationLevelForRowAtIndexPath:] + 61
5 UIKit 0x01fe02cf __53-[UITableView _configureCellForDisplay:forIndexPath:]_block_invoke + 1786
6 UIKit 0x01f5481f +[UIView(Animation) performWithoutAnimation:] + 82
7 UIKit 0x01f54868 +[UIView(Animation) _performWithoutAnimation:] + 40
8 UIKit 0x01fdfbd0 -[UITableView _configureCellForDisplay:forIndexPath:] + 108
9 UIKit 0x01fe713d -[UITableView _createPreparedCellForGlobalRow:withIndexPath:] + 442
10 UIKit 0x01fe71f3 -[UITableView _createPreparedCellForGlobalRow:] + 69
11 UIKit 0x01fc8ece -[UITableView _updateVisibleCellsNow:] + 2428
12 UIKit 0x01fdd6a5 -[UITableView layoutSubviews] + 213
13 UIKit 0x01f5d964 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 355
14 libobjc.A.dylib 0x0304c82b -[NSObject performSelector:withObject:] + 70
15 QuartzCore 0x02f2345a -[CALayer layoutSublayers] + 148
16 QuartzCore 0x02f17244 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 380
17 QuartzCore 0x02f170b0 _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 26
18 QuartzCore 0x02e7d7fa _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 294
19 QuartzCore 0x02e7eb85 _ZN2CA11Transaction6commitEv + 393
20 QuartzCore 0x02f3c5b0 +[CATransaction flush] + 52
21 UIKit 0x01eec9bb _UIApplicationHandleEventQueue + 13095
22 CoreFoundation 0x0416577f __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 15
23 CoreFoundation 0x0416510b __CFRunLoopDoSources0 + 235
24 CoreFoundation 0x041821ae __CFRunLoopRun + 910
25 CoreFoundation 0x041819d3 CFRunLoopRunSpecific + 467
26 CoreFoundation 0x041817eb CFRunLoopRunInMode + 123
27 GraphicsServices 0x044395ee GSEventRunModal + 192
28 GraphicsServices 0x0443942b GSEventRun + 104
29 UIKit 0x01eeef9b UIApplicationMain + 1225
30 Field Manage 0x000aae7d main + 141
31 libdyld.dylib 0x0355d701 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
Upvotes: 0
Views: 2765
Reputation: 3001
I do something like this in my app with nested UICollectionView
s. I found the easiest way to make each CELL the dataSource
and delegate
for the table imbedded in it. So you'll have to make a total of 2 custom subclasses of UITableViewCell, set your static cell as an instance of the first subclass, then implement things like cellForRowAtIndexPath:
in the .m for that first UITableViewCell subclass, and have it create and return an instance of the second subclass.
Upvotes: 1
Reputation: 41226
Kudos on an innovative way to put dynamic prototype cells in a static table :)
Static cell table views implement many of the size related methods (in this case indentationLevelForRowAtIndexPath
) and return values from internal arrays. Since you're trying to use the same delegate and dataSource for both tableviews, you're going to need to provide overrides for pretty much all of the delegate methods that have anything to do with sizing. A better solution might be to use a different data source and delegate entirely for the embedded table.
Upvotes: 2