UITableView cells containing a UISwitch update inconsistently












0















I have a UITableView where each cell contains a UISwitch. The switch is supposed to toggle the content named in the cell as active or not, and it works perfectly when the user toggles the switch.



However, then I got the bright idea of also doing the toggling when the entire table row is selected. Simple in theory right? And it was, in terms of updating the data model.



However, for whatever reason that has had me stumped for the last 2 hours, only the switch in cell #3 updates to reflect the state change when I reload the table data. No matter how many rows I have in the table, it is always only the third cell that works. If there are less than 3 cells, then no cells work.



I've dumped a load of NSLog calls in there and they reflect the proper data states as I'd expect at all times, but they just aren't being reflected in the switch beyond the first load. If I leave the view controller then come back, the switches are all properly set...



I just know this is going to be some stupid thing I've done somewhere, because it works in exactly one instance, but for the life of me I cannot see it :(



Can you?



-(void)viewDidLoad
{
[super viewDidLoad];

[_tableAddPeople setDelegate:self];
[_tableAddPeople setDataSource:self];
}

-(void)viewWillAppear:(BOOL)animated
{
[_tableAddPeople reloadData];
}

-(void)listSwitchChanged:(id)sender
{
UISwitch * switchControl = sender;

Person * person = [SAVE_DATA getPersonWithIndex:(int)switchControl.tag];

if (switchControl.on)
{
[SAVE_DATA activePeopleAdd:person.tag];
}
else
{
[SAVE_DATA activePeopleRemove:person.tag];
}
}


#pragma mark UITableViewDataSource Delegate

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [SAVE_DATA peopleCount];
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell * cell = [_tableAddPeople dequeueReusableCellWithIdentifier:@"AddPeopleCell"];

UILabel * nameLabel = (UILabel *)[cell.contentView viewWithTag:1];
UISwitch * activeSwitch = (UISwitch *)[cell.contentView viewWithTag:2];

Person * person = [SAVE_DATA getPersonWithIndex:(int)[indexPath row]];

[nameLabel setText:[person name]];

BOOL active = [SAVE_DATA activePeopleContains:person.tag];
NSLog(@"Row for %@ is %@", person.name, (active? @"ON" : @"OFF"));
[activeSwitch setOn:active animated:YES];
[activeSwitch addTarget:self action:@selector(listSwitchChanged:) forControlEvents:UIControlEventValueChanged];
activeSwitch.tag = (int)[indexPath row];

return cell;
}

-(BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
return NO;
}

-(BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
return NO;
}


#pragma mark UITableView Delegate

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
Person * person = [SAVE_DATA getPersonWithIndex:(int)[indexPath row]];

if ([SAVE_DATA activePeopleContains:person.tag])
{
NSLog(@"Removing %@", person.name);
[SAVE_DATA activePeopleRemove:person.tag];
}
else
{
NSLog(@"Adding %@", person.name);
[SAVE_DATA activePeopleAdd:person.tag];
}

[_tableAddPeople reloadData];
//dispatch_async(dispatch_get_main_queue(), ^{[tableView reloadData];});
}









share|improve this question



























    0















    I have a UITableView where each cell contains a UISwitch. The switch is supposed to toggle the content named in the cell as active or not, and it works perfectly when the user toggles the switch.



    However, then I got the bright idea of also doing the toggling when the entire table row is selected. Simple in theory right? And it was, in terms of updating the data model.



    However, for whatever reason that has had me stumped for the last 2 hours, only the switch in cell #3 updates to reflect the state change when I reload the table data. No matter how many rows I have in the table, it is always only the third cell that works. If there are less than 3 cells, then no cells work.



    I've dumped a load of NSLog calls in there and they reflect the proper data states as I'd expect at all times, but they just aren't being reflected in the switch beyond the first load. If I leave the view controller then come back, the switches are all properly set...



    I just know this is going to be some stupid thing I've done somewhere, because it works in exactly one instance, but for the life of me I cannot see it :(



    Can you?



    -(void)viewDidLoad
    {
    [super viewDidLoad];

    [_tableAddPeople setDelegate:self];
    [_tableAddPeople setDataSource:self];
    }

    -(void)viewWillAppear:(BOOL)animated
    {
    [_tableAddPeople reloadData];
    }

    -(void)listSwitchChanged:(id)sender
    {
    UISwitch * switchControl = sender;

    Person * person = [SAVE_DATA getPersonWithIndex:(int)switchControl.tag];

    if (switchControl.on)
    {
    [SAVE_DATA activePeopleAdd:person.tag];
    }
    else
    {
    [SAVE_DATA activePeopleRemove:person.tag];
    }
    }


    #pragma mark UITableViewDataSource Delegate

    -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
    return [SAVE_DATA peopleCount];
    }

    -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    UITableViewCell * cell = [_tableAddPeople dequeueReusableCellWithIdentifier:@"AddPeopleCell"];

    UILabel * nameLabel = (UILabel *)[cell.contentView viewWithTag:1];
    UISwitch * activeSwitch = (UISwitch *)[cell.contentView viewWithTag:2];

    Person * person = [SAVE_DATA getPersonWithIndex:(int)[indexPath row]];

    [nameLabel setText:[person name]];

    BOOL active = [SAVE_DATA activePeopleContains:person.tag];
    NSLog(@"Row for %@ is %@", person.name, (active? @"ON" : @"OFF"));
    [activeSwitch setOn:active animated:YES];
    [activeSwitch addTarget:self action:@selector(listSwitchChanged:) forControlEvents:UIControlEventValueChanged];
    activeSwitch.tag = (int)[indexPath row];

    return cell;
    }

    -(BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
    {
    return NO;
    }

    -(BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
    {
    return NO;
    }


    #pragma mark UITableView Delegate

    -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
    Person * person = [SAVE_DATA getPersonWithIndex:(int)[indexPath row]];

    if ([SAVE_DATA activePeopleContains:person.tag])
    {
    NSLog(@"Removing %@", person.name);
    [SAVE_DATA activePeopleRemove:person.tag];
    }
    else
    {
    NSLog(@"Adding %@", person.name);
    [SAVE_DATA activePeopleAdd:person.tag];
    }

    [_tableAddPeople reloadData];
    //dispatch_async(dispatch_get_main_queue(), ^{[tableView reloadData];});
    }









    share|improve this question

























      0












      0








      0








      I have a UITableView where each cell contains a UISwitch. The switch is supposed to toggle the content named in the cell as active or not, and it works perfectly when the user toggles the switch.



      However, then I got the bright idea of also doing the toggling when the entire table row is selected. Simple in theory right? And it was, in terms of updating the data model.



      However, for whatever reason that has had me stumped for the last 2 hours, only the switch in cell #3 updates to reflect the state change when I reload the table data. No matter how many rows I have in the table, it is always only the third cell that works. If there are less than 3 cells, then no cells work.



      I've dumped a load of NSLog calls in there and they reflect the proper data states as I'd expect at all times, but they just aren't being reflected in the switch beyond the first load. If I leave the view controller then come back, the switches are all properly set...



      I just know this is going to be some stupid thing I've done somewhere, because it works in exactly one instance, but for the life of me I cannot see it :(



      Can you?



      -(void)viewDidLoad
      {
      [super viewDidLoad];

      [_tableAddPeople setDelegate:self];
      [_tableAddPeople setDataSource:self];
      }

      -(void)viewWillAppear:(BOOL)animated
      {
      [_tableAddPeople reloadData];
      }

      -(void)listSwitchChanged:(id)sender
      {
      UISwitch * switchControl = sender;

      Person * person = [SAVE_DATA getPersonWithIndex:(int)switchControl.tag];

      if (switchControl.on)
      {
      [SAVE_DATA activePeopleAdd:person.tag];
      }
      else
      {
      [SAVE_DATA activePeopleRemove:person.tag];
      }
      }


      #pragma mark UITableViewDataSource Delegate

      -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
      {
      return [SAVE_DATA peopleCount];
      }

      -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
      {
      UITableViewCell * cell = [_tableAddPeople dequeueReusableCellWithIdentifier:@"AddPeopleCell"];

      UILabel * nameLabel = (UILabel *)[cell.contentView viewWithTag:1];
      UISwitch * activeSwitch = (UISwitch *)[cell.contentView viewWithTag:2];

      Person * person = [SAVE_DATA getPersonWithIndex:(int)[indexPath row]];

      [nameLabel setText:[person name]];

      BOOL active = [SAVE_DATA activePeopleContains:person.tag];
      NSLog(@"Row for %@ is %@", person.name, (active? @"ON" : @"OFF"));
      [activeSwitch setOn:active animated:YES];
      [activeSwitch addTarget:self action:@selector(listSwitchChanged:) forControlEvents:UIControlEventValueChanged];
      activeSwitch.tag = (int)[indexPath row];

      return cell;
      }

      -(BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
      {
      return NO;
      }

      -(BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
      {
      return NO;
      }


      #pragma mark UITableView Delegate

      -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
      {
      Person * person = [SAVE_DATA getPersonWithIndex:(int)[indexPath row]];

      if ([SAVE_DATA activePeopleContains:person.tag])
      {
      NSLog(@"Removing %@", person.name);
      [SAVE_DATA activePeopleRemove:person.tag];
      }
      else
      {
      NSLog(@"Adding %@", person.name);
      [SAVE_DATA activePeopleAdd:person.tag];
      }

      [_tableAddPeople reloadData];
      //dispatch_async(dispatch_get_main_queue(), ^{[tableView reloadData];});
      }









      share|improve this question














      I have a UITableView where each cell contains a UISwitch. The switch is supposed to toggle the content named in the cell as active or not, and it works perfectly when the user toggles the switch.



      However, then I got the bright idea of also doing the toggling when the entire table row is selected. Simple in theory right? And it was, in terms of updating the data model.



      However, for whatever reason that has had me stumped for the last 2 hours, only the switch in cell #3 updates to reflect the state change when I reload the table data. No matter how many rows I have in the table, it is always only the third cell that works. If there are less than 3 cells, then no cells work.



      I've dumped a load of NSLog calls in there and they reflect the proper data states as I'd expect at all times, but they just aren't being reflected in the switch beyond the first load. If I leave the view controller then come back, the switches are all properly set...



      I just know this is going to be some stupid thing I've done somewhere, because it works in exactly one instance, but for the life of me I cannot see it :(



      Can you?



      -(void)viewDidLoad
      {
      [super viewDidLoad];

      [_tableAddPeople setDelegate:self];
      [_tableAddPeople setDataSource:self];
      }

      -(void)viewWillAppear:(BOOL)animated
      {
      [_tableAddPeople reloadData];
      }

      -(void)listSwitchChanged:(id)sender
      {
      UISwitch * switchControl = sender;

      Person * person = [SAVE_DATA getPersonWithIndex:(int)switchControl.tag];

      if (switchControl.on)
      {
      [SAVE_DATA activePeopleAdd:person.tag];
      }
      else
      {
      [SAVE_DATA activePeopleRemove:person.tag];
      }
      }


      #pragma mark UITableViewDataSource Delegate

      -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
      {
      return [SAVE_DATA peopleCount];
      }

      -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
      {
      UITableViewCell * cell = [_tableAddPeople dequeueReusableCellWithIdentifier:@"AddPeopleCell"];

      UILabel * nameLabel = (UILabel *)[cell.contentView viewWithTag:1];
      UISwitch * activeSwitch = (UISwitch *)[cell.contentView viewWithTag:2];

      Person * person = [SAVE_DATA getPersonWithIndex:(int)[indexPath row]];

      [nameLabel setText:[person name]];

      BOOL active = [SAVE_DATA activePeopleContains:person.tag];
      NSLog(@"Row for %@ is %@", person.name, (active? @"ON" : @"OFF"));
      [activeSwitch setOn:active animated:YES];
      [activeSwitch addTarget:self action:@selector(listSwitchChanged:) forControlEvents:UIControlEventValueChanged];
      activeSwitch.tag = (int)[indexPath row];

      return cell;
      }

      -(BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
      {
      return NO;
      }

      -(BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
      {
      return NO;
      }


      #pragma mark UITableView Delegate

      -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
      {
      Person * person = [SAVE_DATA getPersonWithIndex:(int)[indexPath row]];

      if ([SAVE_DATA activePeopleContains:person.tag])
      {
      NSLog(@"Removing %@", person.name);
      [SAVE_DATA activePeopleRemove:person.tag];
      }
      else
      {
      NSLog(@"Adding %@", person.name);
      [SAVE_DATA activePeopleAdd:person.tag];
      }

      [_tableAddPeople reloadData];
      //dispatch_async(dispatch_get_main_queue(), ^{[tableView reloadData];});
      }






      ios objective-c uitableview uiswitch






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 13 '18 at 1:51









      Charlie Scott-SkinnerCharlie Scott-Skinner

      36029




      36029
























          1 Answer
          1






          active

          oldest

          votes


















          1














          Your problem is that you are using the switch tag property for two things:




          1. To identify it in the view hierarchy (You have assigned value '2')

          2. To determine in which row the switch was changed


          Because you are assigning indexPath.row to tag, in the case where the index path isn't 2 (i.e. the third row), [cell.contentView viewWithTag:2]; will return nil when the cell is re-used.



          You also have an issue with cell re-use because you are adding the switch action handler multiple times.



          I would suggest that you adopt the following approach:




          1. Expose your cell's sub views using properties and cast the result of dequeueReusableCell to your UITableViewCell subclass so that you can access them without needing to use the tag


          2. Establish your UITableViewCell subclass as the switch action handler.


          3. Implement a delegation pattern between the cell and your view controller, so that the cell can notify the view controller that the switch changed. The cell can pass self to the delegate method. In the delegate method you can use indexPathForCell to determine the affected row.


          4. Don't reload the whole tableview just because one row has changed. Only reload the affected row.







          share|improve this answer























            Your Answer






            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "1"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            autoActivateHeartbeat: false,
            convertImagesToLinks: true,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: 10,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














            draft saved

            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53272638%2fuitableview-cells-containing-a-uiswitch-update-inconsistently%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            1 Answer
            1






            active

            oldest

            votes








            1 Answer
            1






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            1














            Your problem is that you are using the switch tag property for two things:




            1. To identify it in the view hierarchy (You have assigned value '2')

            2. To determine in which row the switch was changed


            Because you are assigning indexPath.row to tag, in the case where the index path isn't 2 (i.e. the third row), [cell.contentView viewWithTag:2]; will return nil when the cell is re-used.



            You also have an issue with cell re-use because you are adding the switch action handler multiple times.



            I would suggest that you adopt the following approach:




            1. Expose your cell's sub views using properties and cast the result of dequeueReusableCell to your UITableViewCell subclass so that you can access them without needing to use the tag


            2. Establish your UITableViewCell subclass as the switch action handler.


            3. Implement a delegation pattern between the cell and your view controller, so that the cell can notify the view controller that the switch changed. The cell can pass self to the delegate method. In the delegate method you can use indexPathForCell to determine the affected row.


            4. Don't reload the whole tableview just because one row has changed. Only reload the affected row.







            share|improve this answer




























              1














              Your problem is that you are using the switch tag property for two things:




              1. To identify it in the view hierarchy (You have assigned value '2')

              2. To determine in which row the switch was changed


              Because you are assigning indexPath.row to tag, in the case where the index path isn't 2 (i.e. the third row), [cell.contentView viewWithTag:2]; will return nil when the cell is re-used.



              You also have an issue with cell re-use because you are adding the switch action handler multiple times.



              I would suggest that you adopt the following approach:




              1. Expose your cell's sub views using properties and cast the result of dequeueReusableCell to your UITableViewCell subclass so that you can access them without needing to use the tag


              2. Establish your UITableViewCell subclass as the switch action handler.


              3. Implement a delegation pattern between the cell and your view controller, so that the cell can notify the view controller that the switch changed. The cell can pass self to the delegate method. In the delegate method you can use indexPathForCell to determine the affected row.


              4. Don't reload the whole tableview just because one row has changed. Only reload the affected row.







              share|improve this answer


























                1












                1








                1







                Your problem is that you are using the switch tag property for two things:




                1. To identify it in the view hierarchy (You have assigned value '2')

                2. To determine in which row the switch was changed


                Because you are assigning indexPath.row to tag, in the case where the index path isn't 2 (i.e. the third row), [cell.contentView viewWithTag:2]; will return nil when the cell is re-used.



                You also have an issue with cell re-use because you are adding the switch action handler multiple times.



                I would suggest that you adopt the following approach:




                1. Expose your cell's sub views using properties and cast the result of dequeueReusableCell to your UITableViewCell subclass so that you can access them without needing to use the tag


                2. Establish your UITableViewCell subclass as the switch action handler.


                3. Implement a delegation pattern between the cell and your view controller, so that the cell can notify the view controller that the switch changed. The cell can pass self to the delegate method. In the delegate method you can use indexPathForCell to determine the affected row.


                4. Don't reload the whole tableview just because one row has changed. Only reload the affected row.







                share|improve this answer













                Your problem is that you are using the switch tag property for two things:




                1. To identify it in the view hierarchy (You have assigned value '2')

                2. To determine in which row the switch was changed


                Because you are assigning indexPath.row to tag, in the case where the index path isn't 2 (i.e. the third row), [cell.contentView viewWithTag:2]; will return nil when the cell is re-used.



                You also have an issue with cell re-use because you are adding the switch action handler multiple times.



                I would suggest that you adopt the following approach:




                1. Expose your cell's sub views using properties and cast the result of dequeueReusableCell to your UITableViewCell subclass so that you can access them without needing to use the tag


                2. Establish your UITableViewCell subclass as the switch action handler.


                3. Implement a delegation pattern between the cell and your view controller, so that the cell can notify the view controller that the switch changed. The cell can pass self to the delegate method. In the delegate method you can use indexPathForCell to determine the affected row.


                4. Don't reload the whole tableview just because one row has changed. Only reload the affected row.








                share|improve this answer












                share|improve this answer



                share|improve this answer










                answered Nov 13 '18 at 2:18









                Paulw11Paulw11

                67.5k1084102




                67.5k1084102






























                    draft saved

                    draft discarded




















































                    Thanks for contributing an answer to Stack Overflow!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53272638%2fuitableview-cells-containing-a-uiswitch-update-inconsistently%23new-answer', 'question_page');
                    }
                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    Florida Star v. B. J. F.

                    Danny Elfman

                    Lugert, Oklahoma