1

OK, I’ve got a table with 3 different prototype cells (cellVaccination, cellAdmin, cellExpire). In my cellForRowAtIndexPath method, I’m splitting up a Core Data object across those 3 individual cells so that structurally the table will look like the following:

- Drug 1
  - Drug 1 Admin
  - Drug 1 Expire
- Drug 2
  - Drug 2 Admin
  - Drug 2 Expire
- Drug 3
  - Drug 3 Admin
  - Drug 3 Expire

Additionally, I’ve programmatically added a UISwitch into the ‘top level’ cell (i.e. Drug 1) so that the switch might control the secondary cells features (i.e. color, text, etc). Here is what my current cellForRowAtIndexPath looks like:

- (VaccineTableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    // we need to adjust the indexPath because we split a single core data object into 3 different rows
    NSIndexPath *adjustedIndexPath = [NSIndexPath indexPathForRow:indexPath.row / 3 inSection:indexPath.section];
    Vaccine *vaccine = [self.fetchedResultsController objectAtIndexPath:adjustedIndexPath];

    // define the switch that will get added to the primary table rows
    UISwitch *switchview = [[UISwitch alloc] initWithFrame:CGRectZero];

    if (indexPath.row % 3 == 0) {
        static NSString *cellIdentifier = @"cellVaccination";
        VaccineTableViewCell *cell = [myTableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
        cell.vaccineName.text = vaccine.vaccineName;

        // add a switch into that table row
        cell.accessoryView = switchview;
        [switchview addTarget:self action:@selector(switchChanged:) forControlEvents:UIControlEventValueChanged];
        switchview.tag = indexPath.row;
        switchview.on = [vaccine.vaccineEnabled boolValue];

        // PROBLEM AREA BELOW
        if (switchview.on) {
            VaccineTableViewCell *cell1 = [myTableView dequeueReusableCellWithIdentifier:@"cellAdmin" forIndexPath:[NSIndexPath indexPathForItem:indexPath.row + 1 inSection:0]];
            cell1.vaccineAdmin.textColor = [UIColor redColor];
            cell1.vaccineAdminDate.textColor = [UIColor redColor];

            NSLog(@"Row %d is %@", indexPath.row, switchview.on ? @"ON" : @"OFF");
        } else {
            VaccineTableViewCell *cell1 = [myTableView dequeueReusableCellWithIdentifier:@"cellAdmin" forIndexPath:[NSIndexPath indexPathForItem:indexPath.row + 1 inSection:0]];
            cell1.vaccineAdmin.textColor = [UIColor lightGrayColor];
            cell1.vaccineAdminDate.textColor = [UIColor lightGrayColor];

            NSLog(@"Row %d is %@", indexPath.row, switchview.on ? @"ON" : @"OFF");
         }    
        //

        return cell;
    }

    else if (indexPath.row % 3 == 1) {
        VaccineTableViewCell *cell = [myTableView dequeueReusableCellWithIdentifier:@"cellAdmin" forIndexPath:indexPath];
        cell.vaccineAdminDate.text = vaccine.vaccineAdmin;
        return cell;
    }

    else if (indexPath.row % 3 == 2) {
        VaccineTableViewCell *cell = [myTableView dequeueReusableCellWithIdentifier:@"cellExpire" forIndexPath:indexPath];
        cell.vaccineExpireDate.text = vaccine.vaccineExpire;
        return cell;
    }

    else {
        // do nothing at the moment
    }
}

The problem I’m having seems to stem around the area notated within the “Problem Area Below” element, more specifically I’m guessing with the dequeueReusableCellWithIdentifier. In theory, what’s supposed to happen is that when the cells are first populated via the Core Data objects, I want to test whether or not the switch is either “on” or “off” and adjust a parameter (such as color) appropriately so that, without any other interaction, the respective rows are colored appropriately.

What’s happening is this - let’s assume that I’m simulating on an iPhone 4S and that the screen is displaying 4 row sets, or 12 rows total (4 rows of 3 different prototypes). And let’s also assume that the first 2 are switched ON and the second 2 are switched OFF, again driven directly from Core Data. Initially, the screen will look correct, the first two items have been colored red, and the next two items have been colored gray. However when I start scrolling my table up, the NEXT two (that were off the screen) are colored red, and so the pattern continues. Oddly, when NSLog returns the Row identifiers (seen within that “problem area” section) everything looks like it’s identifying the correct rows, but apparently it’s not, i.e.:

vaccinations[10952:1486529] Row 0 is ON
vaccinations[10952:1486529] Row 3 is ON
vaccinations[10952:1486529] Row 6 is OFF
vaccinations[10952:1486529] Row 9 is OFF
vaccinations[10952:1486529] Row 12 is OFF
vaccinations[10952:1486529] Row 15 is OFF

I believe it has something to do with the dequeueReusableCellWithIdentifier method, however why would the NSLog identify the rows correctly, but the changing of the colors not hit the correct rows?

Rob
  • 415,655
  • 72
  • 787
  • 1,044
pscarnegie
  • 109
  • 1
  • 11
  • It might have something to do with your programmatically added uiswitch. Any reason you don't just add this to your cells in the storyboard? – Alex Mar 20 '15 at 20:28
  • I need to be able to reference the switches by tag number later on, and this is the easiest way to create unique tags - as the cells are created. Also, keep in mind that the indexPath.row is returning the correct cells and their correct switch states, however when I attempt to use indexPath.row within the dequeueReusableCellWithIdentifier routine, I can't consistently target the same cells. – pscarnegie Mar 20 '15 at 20:35
  • where do you add the switchview to your cell? – Alex Mar 20 '15 at 20:36
  • It's commented "add a switch to the table cell ..." – pscarnegie Mar 20 '15 at 20:38

1 Answers1

1

You have references to cell1 in which you dequeue a cell for a different NSIndexPath, configure the color of that cell, and then discard this cell. I'm guessing that you are trying to adjust the visual appearance of a different cell (the next cell).

That is not correct. The cellForRowAtIndexPath should be adjusting the state of the current cell only. If you want to adjust the appearance of cellAdmin cell, you should do that within the if (indexPath.row % 3 == 1) ... block.

So the if (indexPath.row % 3 == 0) block will look up in the model to determine if the switch is on or off. The if (indexPath.row % 3 == 1) block will look up in the model to determine what color the text should be.

But cellForRowAtIndexPath should not be trying to adjust the appearance of another cell. You have no assurances of what order these will be instantiated (and it may vary depending upon whether your scrolling, from which direction, etc.).

If one did want to update another cell that is visible, dequeueReusableCellWithIdentifier is not the correct method, regardless. Instead, one would use [tableView cellForRowAtIndexPath:] which retrieves the cell for a currently visible cell (and must not to be confused with the similarly named UITableViewDataSource method). But you would never do that in this context because you don't know if that other cell had been loaded or not. (I actually think it's a bad practice in general to update another cell in any context, a violation of the separation of responsibilities.)

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Rob, thank you for your insights. They were very well explained. Originally my intent was to only show the 'child' cells when the switch was on, but that proved to be an even more difficult dilemma on a single section table. Eventually I plan to embed inline date pickers so that a date can be selected for both child cells. Since hiding them seemed more complicated, my plan was to simply 'disable' user interaction and grey the cells out when the switch was off instead of hiding them. Any suggestions in regards to that endeavor? Seems simple enough in theory, but apparently not in practice. – pscarnegie Mar 20 '15 at 21:32
  • If you search for "uitableview collapse", you'll probably find all sorts of links, such as http://stackoverflow.com/questions/1938921/expand-collapse-section-in-uitableview. But the principle still holds: You don't update/show/hide the other cells, but rather when you flip the switch you update your model accordingly and then call the appropriate method to reload the appropriate cells. The governing principle is to decouple the presenting/flipping of the switch in one cell from the update of the other cells. – Rob Mar 20 '15 at 21:56
  • My core data object consists of an individual entry containing name, admin date, expire date and switch state which is broken up into individual cells with those modulus statements. Removing the admin and expire date so as to remove those cells (with a table update) would seem to necessitate a different core data model, yes? Or can it be done with its current structure? – pscarnegie Mar 20 '15 at 22:06
  • Just had a thought, if I separate the drug name and switch state in one core data object and relate it with an admin and expire date in another object, then theoretically I can use the switch to delete its respective admin/expire date and update the table - which would essentially "hide" those cells. I'm I heading in the right direction here? – pscarnegie Mar 20 '15 at 22:16
  • You can probably do it either way. The key is that if you're hiding, showing rows, your `numberOfRowsInSection` method has to change so it returns the right number based upon whether the rows are hidden/collapsed or not. It's probably easiest if you were using separate sections for each collapsable group of cells (it gets you out of the nuisance of needing to have `cellForRowAtIndexPath` doing a lot of math to reverse engineer where you are). I'd suggest you create temp project, take CoreData out of the equation, and once you've got the concept nailed, then come back to your CoreData model. – Rob Mar 20 '15 at 22:27
  • Rob, you're a brilliant man. I'll attempt to process your brilliance over the weekend. You've been extremely helpful and forthcoming with your suggestions. Very informative and explained extremely well. Thank you again. – pscarnegie Mar 21 '15 at 00:28