refresh a tableview after popping from a navigation stack

Discussion in 'iOS Programming' started by tutiplain, Dec 17, 2011.

  1. tutiplain, Dec 17, 2011
    Last edited: Dec 17, 2011

    tutiplain macrumors member

    Joined:
    Feb 4, 2011
    #1
    Hi all,

    I have two UIViewControllers inside a navigation interface. The first one has a UITableView inside it (The view controller itself is both datasource and delegate). When one particular row is touched, the navigation view pushes the second view into the stack. This second view presents another UITableView with some options.

    When one option is selected in the second view controller, the navigation pops the second view controller out of the navigation stack, but not before setting a property in the first navigation item that will be displayed in another section of the table View in the first. The second view controller also calls the reloadData on the first view controller's table view before popping. The problem, however, is that the first view controller's data is never refreshed.

    Here is what happens on the second view controller when an item is tapped:

    Code:
    -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
        if (self.action ==@"set_active") {
            FirstViewController *firstMenu = (FirstViewController*)           [self.navigationController.viewControllers objectAtIndex:[self.navigationController.viewControllers count]-2];
            
            Scheme* selectedScheme = [self.theSchemes objectAtIndex:indexPath.row];
            firstMenu.activeScheme = selectedScheme;
            [firstMenu.tableView reloadData];  //this method doesn't seem to work
            [self.navigationController popViewControllerAnimated:YES];
        }
    
    
    By debugging I discovered that, indeed, the activeScheme property has a value after this method call runs, so the line before the call to reloadData runs successfully. However, calling reloadData here does nothing. I even set up a breakpoint in the first view controller's cellForRowAtIndexPath: method, and the app never stopped on it.

    As an alternative approach, I tried to call the reloadData method on the first view's viewWillAppear: method. Nothing happened.

    I also tried using a CADisplayLink to call the reloadData method after popping the second view, and it still doesn't work.

    I know I have my tableview set up correctly since it initially shows what it's supposed to. I just don't know why it won't refresh afterwards. Here is the code for the cellForRowAtIndexPath: method:

    Code:
    -(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        if (!self.identifier){
            self.identifier = [NSString stringWithFormat:@"MyIdentifier"];
        }
        
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.identifier];
        if (!cell)
        {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:self.identifier];   
        }
        
        if (indexPath.section ==0)
        {
            cell.accessoryType=UITableViewCellAccessoryDisclosureIndicator;
            cell.textLabel.text = [taskOptions objectAtIndex:indexPath.row];  
        }
        else
        {
            cell.textLabel.text = self.activeScheme.name ;  //this is the cell where the selected value is supposed to appear
        }
        
        return cell;    
    }
    
    
     
  2. North Bronson macrumors 6502

    Joined:
    Oct 31, 2007
    Location:
    San José
    #2
    It is not really a great design pattern. You should not make your Detail View Controller poke through the navigation controller stack to find the Master View Controller.

    Your Master View Controller should create a Detail View Controller when necessary and set itself as the delegate object. The Detail View Controller should define a protocol of methods for when interesting events happen. The Master View Controller should conform to that protocol.

    When the user updates your data, Detail View Controller sends a message to its delegate object. The Master View Controller handles the delegate method appropriately.
     
  3. tutiplain thread starter macrumors member

    Joined:
    Feb 4, 2011
    #3
    Hi. Thanks for your reply. I haven't actually yet looked into protocol creation (though I've used protocols to program table views), but it seems like a good way to handle it. I will definitely look into it. Still, is there any particular reason why my approach doesn't work?
     
  4. jonnymo5 macrumors 6502

    Joined:
    Jan 21, 2008
    Location:
    Texas
    #4
    Wirelessly posted (Mozilla/5.0 (iPhone; CPU iPhone OS 5_0_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A405 Safari/7534.48.3)

    Never tried it but it is extremely fragile even if it did work. As NB said you should set up a protocol and delegate. You don't want your subviews to require specific knowledge about who created them and how deep they are in the navigation. Learning the new design pattern will end up being faster than debugging it :D
     
  5. tutiplain thread starter macrumors member

    Joined:
    Feb 4, 2011
    #5
    Hi again,

    I read up a bit on protocols on the apple developer site, but I am not quite sure how declaring a protocol is going to help me solve the problem. Is there some example you could direct me to, that shows this at work? It would be much appreciated, thanks!
     
  6. bweberapps macrumors member

    Joined:
    Jun 25, 2010
    #6
    To answer the original question, if the data loaded into the table is already updated by your detail view then just put a reload data call in the viewwillappear function on the table view controller. The viewwillappear method gets called, but not the viewdidload, after a pop. Hope that helps.
     
  7. jonnymo5 macrumors 6502

    Joined:
    Jan 21, 2008
    Location:
    Texas
    #7
    When you define a protocol for that second view controller you are giving it a way to control any class that implements it. The second view controller then can force its delegate to run one of the methods in its protocol.

    So when the first view controller instantiates the second one it can set itself as the delegate for the second one before pushing it on the navigation stack. Then you just put your update code in the method the first view controller had to implement to conform to the protocol. That lets you pass variables back to the first controller without the second one having to know specific details about it. All the second view controller knows is it has some delegate that has promised to respond to its protocol methods.

    As for examples look at the table view delegate. You have to implement the methods that provide cells and the table view has to provide you with the index path it wants the info about.
     
  8. tutiplain thread starter macrumors member

    Joined:
    Feb 4, 2011
    #8
    Hi again.

    So I did a bit poking around, and found a way to implement this in a protocol, but I still can't get the table to refresh.

    I defined my protocol like this:

    Code:
    @protocol ActiveSchemeSelector <NSObject>
    
    @required	
    -(void)selectedActiveScheme:(Scheme *)newActiveScheme;
    
    @end
    
    Note that Scheme is a custom class for my app, part of my data model. I then modified the second view controller (the Detail View, as some of you call it) and added a property like this:

    Code:
    @interface SecondViewController : UIViewController <UITableViewDataSource,UITableViewDelegate>
    {
        NSString *action;
        id <ActiveSchemeSelector> schemeSelectorDelegate;
    }
    @property(nonatomic,retain) NSString *action;
    @property(assign) id schemeSelectorDelegate;
    
    Modified the didSelectRowAtIndexPath method like this:

    Code:
    -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
        if (self.action ==@"set_active") {
            Scheme* selectedScheme = [self.theSchemes objectAtIndex:indexPath.row];
            [schemeSelectorDelegate selectedActiveScheme:selectedScheme]; //this is the protocol method
            [self.navigationController popViewControllerAnimated:YES];
        }
        
    }
    
    Finally, I added the following to my FirstViewController's definition:

    Code:
    @interface FirstViewController : UIViewController <UITableViewDataSource, UITableViewDelegate,ActiveSchemeSelector> 
    And added the following code to the implementation of the first view controller:

    Code:
    -(void)selectedActiveScheme:(Scheme *)newActiveScheme
    {
        self.activeScheme =newActiveScheme;
        NSLog(self.activeScheme.name); //shows the name of the selected Scheme object, as selected in the second view controller
        [taskOptionsTableView reloadData]; //this line doesn't do anything
    }
    
    
    With that NSLog, I make certain, that, indeed, the correct value was assigned to self.activeSheme. However, calling [tableView reloadData] does not cause the name to be displayed there. So while things are better designed now, it still doesn't do what I want it to. What can I possibly be missing?
     
  9. North Bronson macrumors 6502

    Joined:
    Oct 31, 2007
    Location:
    San José
    #9
    It is usually the custom that your delegate methods also pass the object that is doing the calling:

    Code:
    - (void)detailViewController:(DetailViewController *)detailViewController didSelectDetailModel:(DetailModel *)detailModel;
    This gives you a handy way to query the main object if you need some additional properties that the small object does not provide.

    Why are your view controllers not descended directly from UITableViewController?
     
  10. tutiplain thread starter macrumors member

    Joined:
    Feb 4, 2011
    #10
    I guess I learned "the long way" of creating and using table views. UITableViewController gives some functionality for free, so you don't have to manually specify a delegate and data source. But I read in another forum that using UITableViewController also limits the customizations you can do to the way the table view paints itself. Of course, I haven't tried this myself. In any case, my approach is that UIViewControllers implement the UITableViewDelegate and UITableViewDataSource protocols, and the view controller itself is set as both data source and delegate in Interface Builder. This approach is covered in the Apple docs, so I guess it must be acceptable.
     
  11. North Bronson macrumors 6502

    Joined:
    Oct 31, 2007
    Location:
    San José
    #11
    Yes. It is true that using UITableViewController will limit the amount of advanced functionality that you achieve. Yes. It is true that certain users might want to use their own view controller. Is it right for you? Will it unnecessarily complicate things more than it will help? Will it stand in the way of your learning? Potentially.

    I would guess that the problem has something to do with the way that you are creating the table or saving it to an instance variable. Using the UITableViewController will do that for free and let you focus on the bigger picture of making these two view controllers speak to each other. This is the bigger fish for you to fry.

    Try using two UITableViewController subclasses and I bet your problem will vanish.

    Either that or something is wrong with the method you use to customize your cells.
     
  12. bweberapps, Dec 21, 2011
    Last edited: Dec 21, 2011

    bweberapps macrumors member

    Joined:
    Jun 25, 2010
    #12
    You don't need to subclass uitableviewcontroller for it to work. I have implemented exactly what he is trying to do many times. I assume the data used in the main table is global and edited by the detail view and saved. All you need to do then is put one line in your main table class to get it to refresh. In the viewdidload method of your main view put [yourtable relaodData]; that is it. No protocols needed. I have this working in an app I am currently working on. The reason your protocol isn't working is because the detail view still has focus when the protocol function is called. If you reload the table data AFTER the detail view has popped and when the main view comes back into focus it will actually be able to refresh the table data. Please give it a try.

    Edit: I saw you did try the viewwillappear method, but maybe try it with the protocol to update the selectedSystem instance variable. I also think a modal view might be a better design choice for your scheme selection view. They work great with protocols, but you will still need to refresh the table data when the view will appear. I also have something similar to that working in one of my apps, but I use a global data source instead of an instance variable to put data into my tables.
     
  13. tutiplain thread starter macrumors member

    Joined:
    Feb 4, 2011
    #13
    Hi all,

    Making my master Detail view a subclass of UITableViewController, plus implementing the protocol solved the problem. Now, my table displays the info selected in Detail View in the last row of the Master, which is what I wanted.

    Still, there is something to be learned here, and I'd like to know what it is. I saved my original code, just in case. I decided to compare the cellForRowAtIndexPath methods of each:

    The original:

    Code:
    -(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        if (!self.identifier){
            self.identifier = [NSString stringWithFormat:@"MyIdentifier"];
        }
        
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.identifier];
        if (!cell)
        {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:self.identifier];   
        }
        
        if (indexPath.section ==0)
        {
            cell.accessoryType=UITableViewCellAccessoryDisclosureIndicator;
            cell.textLabel.text = [taskOptions objectAtIndex:indexPath.row];  
        }
        if (indexPath.section ==1) {
            cell.textLabel.text = self.activeScheme.name ; 
        }
        
        return cell;    
    }
    
    
    And the new one, based on the template generated by XCode:

    Code:
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        static NSString *CellIdentifier = @"Cell";
        
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
        if (cell == nil) {
            cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
        }
        
        if (indexPath.section ==0)
        {
            cell.accessoryType=UITableViewCellAccessoryDisclosureIndicator;
            cell.textLabel.text = [taskOptions objectAtIndex:indexPath.row];  
        }
        if (indexPath.section ==1) {
            cell.textLabel.text = self.activeScheme.name ; 
        }
        
        
        return cell;
    }
    
    
    Could the source of my problem have been the way I am handling the reuseIdentifier in my original code?
     
  14. North Bronson macrumors 6502

    Joined:
    Oct 31, 2007
    Location:
    San José
    #14
    You are doing well. Can you tell me why this fixed the bug? Tell me what it was about your old view controller that was not working. Go back and forth and try to make them both work.

    Create an application with a tab bar controller. Make one tab be your UITableViewController subclass. Make the other tab be your UIViewController subclass. Use the same Detail Controller class for each one. Work on the code so that both tabs are functionally equivalent.
     
  15. PhoneyDeveloper macrumors 68040

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #15
    Most likely the problem was that you weren't assigning the tableview ivar or it wasn't connected in IB, so its value was nil. You should do it the same way that UITableViewController does it. So you should have a property named tableView and you should refer to it as self.tableView wherever you need to access that ivar in your code. This way you can more easily copy/paste code from UITableViewController subclasses to UIViewController subclasses.

    Another suggestion: if you are going to write a UIViewController subclass that is like a UITableViewController when you choose File > New create a UITableViewController subclass. Then change the base class to UIViewController. This way you get all the UITableViewController methods in the file without having to copy them from somewhere else or type them in yourself.

    The way you were managing the reuse identifier was certainly overly complicated but didn't look wrong. I always use a simple #define for that.

    In your cellForRowAtIndexPath method you need to set all the properties of the cell that might change every time a cell is returned. So in your case you need to always set the cell.accessoryType, which you aren't doing.
     
  16. tutiplain thread starter macrumors member

    Joined:
    Feb 4, 2011
    #16
    Hi, thanks again for all the replies. I'd say this has been one of my posts that has proven most insightful so far, and I've learned a lot from each and every reply.

    Thanks! I cannot yet say why the bug is fixed. I've been looking over the differences in the table view methods on both approaches, but still have yet to find a reason. I will kepp going back and forth as you suggest and see if I can pin it down. It might be working now, but for future references, I might need to know exactly why.

    I will post back what I find, or share more of the code so perhaps you guys might be able to help me track it down.

    Again, Thanks to everyone, and hope you all have a nice Christmas.
     
  17. moonline macrumors newbie

    Joined:
    Dec 26, 2011
    #17
    very nice
     
  18. tutiplain thread starter macrumors member

    Joined:
    Feb 4, 2011
    #18
    Hi guys, sorry about the looong delay in answering. I've been on holiday vacation, and hadn't had much time to practice. While working on another part of the same application which originated this issue, I had the same problem again. Only this time, my Master view was an UITableViewController subclass from the start. I was able to solve the issue by removing a @property/@synthesize pair which referenced the table view in the NIB file (since UITableViewController already provides a self.tableView property, there is no need to declare it explicitly in my @interface block). I wonder if this has anything to do with why my first case scenario didn't work?
     

Share This Page