UITableView with dynamic sections and custom cells - objective-c

My data structor looks like this:
Inside an NSArray I have a few objects from different classes. How the array looks like if something like this:
Let's say myArray has 3 objectAs. Each objectA and an NSArry that has objectBs
Now at runtime I do not know the array counts.
My UITableView displays one objectA per section. Each section has 1 row. Unless my objectA has an NSArray of objectBs with a count of greater than 0.
So the UITableView would look like this if all my objectA's has no arrays of objectB's in them.
Section 1: Row 1: `objectA instance
Section 2: Row 1: `objectA instance
Section 3: Row 1: `objectA instance
Lets say the 3rd objectA has an array of objectB's with a count of 1. My UITableView would look like this:
Section 1: Row 1: `objectA instance
Section 2: Row 1: `objectA instance
Section 3: Row 1: `objectA instance
Section 3: Row 2: `objectB instance
Now I am using two different UITableViewCells for each object so objectA would use CustomCell1 and objectB would use CustomCell2
Here is where I am getting stuck - I need to return the correct cell to the correct section / row.
So I made sure my UITableView knows what to expect.
I wrote the methods like so:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
ObjectA *objectA = self.myArray[section];
return [objectA.objectBArray count] +1;
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [self.myArra count];
}
So numberOfRowsInSection takes a look at the objectBs array and returns its count + 1 so that even if its 0 there is always one row. (I need at least one row for objectA in the current section.
numberOfSectionsInTableView is straight forward - one section for each objectA in self.myArray
This seems to do exactly as I need.
I then got stuck on cellForRowAtIndexPath method.
This is how far I got:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell1 *cell1 = [tableView dequeueReusableCellWithIdentifier:#"cell1"];
CustomCell2 *cell2 = [tableView dequeueReusableCellWithIdentifier:#"cell2"];
ObjectA *objectA = self.myArray[indexPath.section]; //Each section has one ObjectA.
//Make sure we have some objectB's to display first
if ([objectA. objectBArray count] > 0){
ObjectB *objectb = (ObjectB*) objectA.objectB[indexPath.row];
//if the current section (section 3) has a indexPath 0 already
// index path 1 would have the first objectB
}
cell1.objectA = objectA;
cell2.objectB = objectB;
//Now return the correct cell for the current section / index path
}
So I need to check if the current section already has valid cell1 object if it does I then need to return cell2 to the second or third row. for objectB As row one must always have a cell1.
I know I am almost there. I just can't figure out how to return the correct cell.
Update
So the cellForRowAtIndexPath method is only being called three times. Here is what I did in numberOfRowsInSection method to make sure it returns the right number of rows:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
ObjectA *objectA = self.myArray[section];
NSLog (#"Section:%ld", (long)section);
NSLog(#"Returning %ld", (long)[objectA.objectBArray count]+1);
return [objectA.objectBArray count] +1;
}
My console shows this:
2014-07-16 19:36:17.488 App[15473:60b] Section:2
2014-07-16 19:36:17.488 App[15473:60b] Returning 2
2014-07-16 19:36:18.128 App[15473:60b] Section:0
2014-07-16 19:36:18.128 App[15473:60b] Returning 1
2014-07-16 19:36:19.063 App[15473:60b] Section:1
2014-07-16 19:36:19.063 App[15473:60b] Returning 1
Any ideas?

If there will always only be one objectA in each section then you can test if your indexPath.row is anything larger then zero (not the first row).
See if this does the trick:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell1 *cell1 = [tableView dequeueReusableCellWithIdentifier:#"cell1"];
CustomCell2 *cell2 = [tableView dequeueReusableCellWithIdentifier:#"cell2"];
ObjectA *objectA = self.myArray[indexPath.section]; //Each section has one ObjectA.
cell1.objectA = objectA;
//Check if not first row and objectB's exist
if (indexPath.row > 0 && [objectA. objectBArray count] > 0){
ObjectB *objectb = (ObjectB*) objectA.objectB[indexPath.row];
cell2.objectB = objectB;
return cell2;
}
return cell1;
}

This is an answer to the second issue I was having. After setting breakpoints in my view controller - I noticed my tableView was already loading cells before my call to [self.tableView reloadData];
Not sure why that was happening so a quick nil-check in my rows for items method solved the issue:
if the main array had no objects - return 0. If it did - return objectBArray count + 1; Now the cellForRowAtIndexPath is called the correct number of times.

Related

Objective C- numberOfRowsInSection method is not called properly in UITableView

In my table view, I have 7 section. Each section count is 3 initially.
When I launch my app, numberOfRowsInSection method is calling from last section count(6) but data are displayed in all sections properly.
When I reload my table after insert event numberOfRowsInSection is calling again from 6 not from 0 so how to resolve it?
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSMutableArray *arrayOfItems = [workScheduleArray objectAtIndex:section];
return arrayOfItems.count;
}
-(void)insertNewDutyTimeRow:(UIButton *)addNewRowBtn atIndexPath:(NSIndexPath *)addNewDutyIndex
{
insertNewRow = [[NSMutableArray alloc]init];
[insertNewRow addObject:#"Start Time"];
[insertNewRow addObject:#"End time"];
[insertNewRow addObject:#"Service type"];
newArray = [workScheduleArray objectAtIndex:addNewDutyIndex.section];
[newArray insertObject:insertNewRow atIndex:addNewDutyIndex.row+1];
NSLog(#"WorkSchedule array:%#",workScheduleArray[addNewDutyIndex.section]);
[self.personalTable reloadData];
}
Your numberOfRowsInSection is generic, it returns the same number for all your sections. it begins by section 0 and ends by your last section (in your case its 6). So this is why you must specify all the cases. For example you can do this:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
switch (section) {
case 0:
NSMutableArray *arrayOfItems = [workScheduleArray objectAtIndex:section];
return arrayOfItems.count;
{...}
}
}

Selecting Objects from NSArray for further deleting with an IBAction

So I am trying to select objects from my array, to be able to delete them when I do an IBAction. I tried:
checking if the item is Selected:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if (self.editEnabled) {
RDNote *selectedNote = [self.notes objectAtIndex:indexPath.row];
if (selectedNote.isSelected) {
selectedNote.selected = NO;
for (NSIndexPath *indexPathFromArray in self.indexPathsOfSelectedCells) {
if (indexPathFromArray.row == indexPath.row) {
[self.mutableCopy removeObject:indexPathFromArray];
}
}
} else {
selectedNote.selected = YES;
[self.indexPathsOfSelectedCells addObject:indexPath];
}
[self.collectionView reloadData];
IBAction:
- (IBAction)didTapTrashBarButton:(id)sender {
NSMutableArray *mutableNotes = [NSMutableArray arrayWithArray:self.notes];
for (NSIndexPath *indexPath in self.indexPathsOfSelectedCells) {
[mutableNotes removeObjectAtIndex:indexPath.row];
}
self.notes = [NSArray arrayWithArray:mutableNotes];
[self.collectionView performBatchUpdates:^{
[self.collectionView deleteItemsAtIndexPaths:[NSArray arrayWithArray:self.indexPathsOfSelectedCells]];
} completion:^(BOOL finished) {
self.indexPathsOfSelectedCells = nil;
[self activateEditMode:NO];
[self saveDataToFile:self.notes];
}];
}
But I am having issues with indexes e.g.:(some times shows me the Error that Object index 2 is not between [0..1]), and there are bugs while selecting multiple Object and Deleting them.
Please help me with an advice for some other method that I can use, a Code will be perfect! Thanks!
This problem arrises because:
Let say you have five objects in array 1,2,3,4,5
you are running a loop for removing object based on indexpath which are selected rows. Now your indexpath contains 1st row and 3rd row.
while executing it for the first time you will remove object 1. Now 2,3,4,5 will left in the array. Now second time your indexpath.row is 3rd. It will remove third object which is 4 but in actual array it was 3.
Your code is crashing sometimes because if you have selected first row and last row. In this case i have selected 1 and 5. Now my indexpaths array will say I have to delect objectsAtIndexes 1 and 5.
While executing the loop I'll remove object at Index 1. Now i will be left with 2,3,4,5. On second iteration it will say remove objectAtIndex 5 where as Index 5 doesn't exist because now we have 4 elements in array.
In such cases best way of doing this is try removing elements in array from the end like remove 5th element first and then go for other one. Run your loop in reverse order.
NSInteger i = [self.indexPathsOfSelectedCells count]-1;
while (i > 0){
NSIndexPath *indexPath = [self.indexPathsOfSelectedCells objectAtIndex:i];
[mutableNotes removeObjectAtIndex:indexPath.row];
i--;
}

Objective-C Sectioned UITableView

I want a sectioned UITableView but I don't know how, my situation:
I have 2 arrays
array Date ("12/08/13", "13/08/13", "17/08/13")
array Count("2", "1", "5")
The count means how many rows are there in the section.
I have
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [dateArray objectAtIndex:section];
}
But how can make in the first section, 2 rows in the second section 1 row and in the third section 5 rows.
I hope someone can help me. If you have questions just ask.
Thanks.
EDIT: I get my information from my website so the array changes everyday.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// length of your section count array:
return [sectionCountArray count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// the section count from your array, converted to an integer:
return [sectionCountArray[section] integerValue];
}
UPDATE: From your comment is seems that you have a "flat" data source array.
Since the row numbers start with zero in each section, you have to compute the
index into the array from the row number and the section count of all previous sections
in cellForRowAtIndexPath:
NSInteger flatIndex = indexPath.row;
for (NSInteger sec = 0; sec < indexPath.section; sec++)
flatIndex += [tableView numberOfRowsInSection:sec];
cell.textLabel.text = yourFlatDataArray[flatIndex]; // (Just an example!)
You return the number of sections in the numberOfSectionsInTableView:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return [self.countArray count];
}
Then get the number from the array and return that in the numberOfRowsInSection:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSNumber *count = [self.countArray objectAtIndex:section];
return [count integerValue];
}
You need to override the following UITableView datasource method:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 0)
return 2
else if (section == 1)
return 1
else (section == 2)
return 5
}

indexOfObjectIdenticalTo: returns value outside of array count

I'm currently working on an app that populates a UITableView with items from a MPMediaItemCollection. I'm trying to add a UITableViewCellAccessoryCheckmark to the row that matches the title of the currently playing track.
I've done so by creating a mutable array of the track titles, which are also set for my cell's textLabel.text property. (for comparison purposes)
Note: This is all done in - (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath
MPMediaItem *mediaItem = (MPMediaItem *)[collectionMutableCopy objectAtIndex: row];
if (mediaItem) {
cell.textLabel.text = [mediaItem valueForProperty:MPMediaItemPropertyTitle];
}
[mutArray insertObject:cell.textLabel.text atIndex:indexPath.row];
To the best of my knowledge this all works fine except for the below. At this point, I am trying to get the index of the currently playing tracks title and add the UITableViewCellAccessoryCheckmark to that row.
if (indexPath.row == [mutArray indexOfObjectIdenticalTo:[mainViewController.musicPlayer.nowPlayingItem valueForProperty:MPMediaItemPropertyTitle]]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
Getting to my question, I added all of the above (mostly irrelevant) code because I'm stumped on where I went wrong. When I log indexOfObjectIdenticalTo: it spits out "2147483647" every time, even though there are never more than 5 objects in the array. But why?
If anyone has any tips or pointers to help me fix this it would be greatly appreciated!
2147483647 just mean the object is not found.
From the documentation of -[NSArray indexOfObjectIdenticalTo:]:
Return Value
The lowest index whose corresponding array value is identical to anObject. If none of the objects in the array is identical to anObject, returns NSNotFound.
and NSNotFound is defined as:
enum {
NSNotFound = NSIntegerMax
};
and 2147483647 = 0x7fffffff is the maximum integer on 32-bit iOS.
Please note that even if two NSString have the same content, they may not be the identical object. Two objects are identical if they share the same location, e.g.
NSString* a = #"foo";
NSString* b = a;
NSString* c = [a copy];
assert([a isEqual:b]); // a and b are equal.
assert([a isEqual:c]); // a and c are equal.
assert(a == b); // a and b are identical.
assert(a != c); // a and c are *not* identical.
I believe you just want equality test instead of identity test, i.e.
if (indexPath.row == [mutArray indexOfObject:[....]]) {
Looking at the docs for NSArray
Return Value
The lowest index whose corresponding array value is identical to anObject. If none of the objects in the array is identical to anObject, returns NSNotFound.
So you should probably do a check
NSInteger index = [array indexOfObjectIdenticalTo:otherObject];
if (NSNotFound == index) {
// ... handle not being in array
} else {
// ... do your normal stuff
}

TitleForHeaderInSection throwing EXC_BAD_ACCESS

I am trying to create a generic UITableView in my iPhone app.
I have a UITableView which populates the data using an array via a SELECT query loop.
I add the data into my array and populate the array in cellForRowAtIndexPath:.
I get the section header using that array and by using a sort method, I put the section headers in Array1.
I would like to have titleForHeaderInSection: work by having section 0 be a static header name and sections 1 and later become generic, meaning the header name will come from Array1.
I am not sure how can I create that logic since the app always throws EXC_BAD_ACCESS with the code below.
My logic: I keep the count of the array in an int and see if the value is greater than 0. If it is, I add the section header for and objectAtIndex:0, otherwise I use the static one. But when the count gets to 2, for section 2 and objectAtIndex:1, it breaks and throws EXC_BAD_ACCESS.
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
int value = [[self Array1] count];
if(section == 0)
return #"Countries";
if (value > 0) {
if (section == value){
return [[self Array1] objectAtIndex:section - 1];
}
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
int count = [[self Array1] count];
return count + 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
int value = [[self Array1] count];
if (section == 0) {
return [self.Array count];
}
if (value > 0) {
if (section == [[self Array1] count]) {
NSString *initialLetter = [[self Array1] objectAtIndex:section - 1];
// get the array of elements that begin with that letter
NSArray *elementsWithInitialLetter = [self elementsWithInitialLetter:initialLetter];
// return the count
return [elementsWithInitialLetter count];
}
}
}
Looks like you're just missing a retain on the iVar backing the Array1 method. declare the array as a property:
#property (nonatomic, retain) NSArray* datastore;
Then cache the value you're referring to in the Array1 method in this method (probably in viewDidLoad).
self.datastore = [self Array1];
Then replace all remaining references to [self Array1] with self.datastore. Build run and see if it still crashes. (Don't forget to set self.datastore = nil in your dealloc!