MCKapur
MCKapur

Reputation: 9157

Table view nil/not updating

I have a table view with only one cell. When I call a method, I try to refresh it and the text label's text should change. However it does not, it only changes if the view re appears.

I have added NSLog() in many places and the correct methods and conditions are called

I have tried a few things:

reloadData
setNeedsDisplay
setNeedsLayout
reloadCell
reloadRowsAtIndexPaths: withRowAnimation:

But nothing has worked, I know it is not a problem with my UITableViewDataSource code because it is properly getting called.

This is the most appropriate code I can give you:

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

    tableView.scrollEnabled = NO;

    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier: @"cell"];

    if (cell) {

        cell.selectionStyle = UITableViewCellSelectionStyleNone;

        if ([[TravelLogViewController locationArray] count]) {

            NSDictionary *flight = [NSDictionary dictionaryWithObjectsAndKeys:[[TravelLogViewController locationArray] lastObject], @"name", [[TravelLogViewController dateArray] lastObject], @"date", [[TravelLogViewController milesGainedIndex] lastObject], @"miles", nil];

            cell.detailTextLabel.textColor = [UIColor blackColor];

            cell.textLabel.numberOfLines = 2;
            cell.detailTextLabel.numberOfLines = 2;

            cell.textLabel.font = [UIFont fontWithName:@"Avenir" size: 14.0];
            cell.detailTextLabel.font = [UIFont fontWithName:@"Avenir" size: 12.0];

            cell.selectionStyle = UITableViewCellSelectionStyleNone;

            cell.textLabel.text = [flight objectForKey: @"name"];

            cell.detailTextLabel.text = [NSString stringWithFormat:@"Flight Date: %@, Miles Gained: %.1f", [flight objectForKey: @"date"], [[flight objectForKey: @"miles"] doubleValue]];

            cell.backgroundColor = [UIColor colorWithWhite:0.92 alpha:1];
            //        cell.backgroundColor = [UIColor clearColor];
        }
        else {

// This gets called when I want it to, but the textLabel text or detailTextLabel text is not changed


            cell.detailTextLabel.text = nil;
            cell.textLabel.text = nil;
            cell.textLabel.font = [UIFont fontWithName:@"Arial Rounded MT Bold" size: 18.0];
            cell.textLabel.text = @"\n\n                 No Flights";
        }
    }

    return cell;
}

UPDATE:

I am using ARC, and I have found that the table view is nil at the time I try to update the cell, but I have revised my cell and have no clue why!?

I am not nilling or releasing my table view anywhere, it should still be allocated

By the way I have a tab bar controller

UPDATE:

I have started a bounty now, its over a week later and I really feel this question needs some more attention and answers, I have checked my code multiple times and I cannot see why the table view is not updating / is nil.

UPDATE:

Ok, I have forgot to mention that the user can swipe to delete the cell. When he does, it does not actually delete the cell, it just changes its text (that is the part it is not updating). I put logs in viewDidLoad and viewDidAppear and table view appears to be valid, once the user swipes to delete... here is my code:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {

if (editingStyle == UITableViewCellEditingStyleDelete) {

    TravelLogViewController *travel = [[TravelLogViewController alloc] init];
    [travel manuallyDeleteTopCellData]; // Deletes some data in other view controller and calls the method `proceedWithDeletion`
}
}
- (void)proceedWithDeletion {

// Method is called from another view controller

NSLog(@"%@", mostRecentFlight.description); // NIL HERE

[mostRecentFlight reloadData];
[mostRecentFlight setNeedsDisplay];
[mostRecentFlight setNeedsLayout];

}

TravelLogViewController

- (void)manuallyDeleteTopCell {

[TheMileIndexViewController deductDesiredMilesToIndex: [[milesGainedIndex lastObject] floatValue]];

[locationArray removeLastObject];

[milesGainedIndex removeLastObject];

[dateArray removeLastObject];

MenuMileIndexViewController *menu = [[MenuMileIndexViewController alloc] init];
[menu proceedWithDeletion];

}

So to sum up, one the view loads/appears, the table view is valid. When the user swipes to delete, it does not actually delete the row it just calls another view controller called TravelLogViewController method called manuallyDeleteTopCellData where it deletes some data, that method then calls another method in my original view controller called proceedWithDeletion where it is supposed to reload the table view, but doesn't (the problem). The reason for this is because in proceedWithDeletion the table view is nil, but why!?

Upvotes: 2

Views: 1125

Answers (8)

user8472
user8472

Reputation: 3326

In your implementation of manuallyDeleteTopCell in TravelLogViewController you instantiate a new MenuMileIndexViewController at this point:

MenuMileIndexViewController *menu = [[MenuMileIndexViewController alloc] init];
[menu proceedWithDeletion];

This is a different controller and is neither properly initialized nor responsible for the view on the screen. In particular, it does not have the same mostRecentFlight property. In order for the TravelLogViewController to call your original controller, you need to pass it as a parameter to manuallyDeleteTopCell and call that one back, e.g. as follows

- (void)manuallyDeleteTopCell:(MenuMileIndexViewController *)origin {
    [TheMileIndexViewController deductDesiredMilesToIndex: [[milesGainedIndex lastObject] floatValue]];

    [locationArray removeLastObject];
    [milesGainedIndex removeLastObject];
    [dateArray removeLastObject];

    [origin proceedWithDeletion];
}

And rewrite the calling code as follows:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        TravelLogViewController *travel = [[TravelLogViewController alloc] init];
        [travel manuallyDeleteTopCellData:self];
}

Former suggestions:

  1. Make sure that you have set the outlet properly in the Interface Builder. You say in a comment that you did this, but at another point you say that you are using a tab bar controller. Make sure you are setting the outlet on a regular view controller (i.e., a subclass of UIViewController) that is currently visible and contained in the viewControllers property of the UITabBarController instance you are using. Do not subclass UITabBarController (see the Apple documentation which explicitly prohibits it). [CHECKED]

  2. Implement the viewDidLoad method of the regular view controller and NSLog the self.tableView property to verify it is non-nil at the time this method is called. [CHECKED]

  3. Write a @synthesize mostRecentFlight = _mostRecentFlight; statement in the @implementation of TravelLogViewController and set a breakpoint on the property in the @interface. Alternatively, manually implement the setMostRecentFlight: method for the mostRecentFlight property and set a breakpoint inside this method. This breakpoint will trigger if the setter is called which will help you find the time the property is set to nil.

  4. Assuming the property is non-nil in the viewDidLoad method in step 2, look at the address of the object (the beginning of the description which says (UITableView *) $1 = 0xXXXXXXXX with 0xXXXXXXXX being the address). Set a breakpoint where you want to use the object later in your code (when the property is nil). Type at the debugger po 0xXXXXXXXX (with the appropriate address from above). What is the response you get?

Upvotes: 1

Dave Lee
Dave Lee

Reputation: 6489

This code could be the source of your problem:

MenuMileIndexViewController *menu = [[MenuMileIndexViewController alloc] init];
[menu proceedWithDeletion];

You're creating a brand new MenuMileIndexViewController, not accessing the already existing instance, which is what I presume you want to do. This new instance won't have access to the original table view stored in mostRecentFlight. Inside -manuallyDeleteTopCell, you'll need a reference to the calling MenuMileIndexViewController instance.

It might be as simple as passing self to -manuallyDeleteTopCellData and using that variable to subsequently call -proceedWithDeletion on. For example:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        TravelLogViewController *travel = [[TravelLogViewController alloc] init];
        [travel manuallyDeleteTopCellData:self];
    }
}

- (void)manuallyDeleteTopCell:(id)sender {
    [TheMileIndexViewController deductDesiredMilesToIndex: [[milesGainedIndex lastObject] floatValue]];
    [locationArray removeLastObject];
    [milesGainedIndex removeLastObject];
    [dateArray removeLastObject];
    // MenuMileIndexViewController *menu = [[MenuMileIndexViewController alloc] init];
    [sender proceedWithDeletion];
}

Upvotes: 1

Danil
Danil

Reputation: 1780

I'd like to notice. if you are working with storyboard you don't need to alloc your cell. This code is enough:

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

UITableViewCell *cell = [tableView 
  dequeueReusableCellWithIdentifier:@"PlayerCell"];
Player *player = [self.players objectAtIndex:indexPath.row];
cell.textLabel.text = player.name;
cell.detailTextLabel.text = player.game;
return cell;

}

Upvotes: 0

Richard D
Richard D

Reputation: 5665

I can pretty much confirm that the problem is not in the areas you're looking or describing.

I have created a sample project from scratch, very minimally just putting a UITableView and a UIButton that triggers a [table reloadData] and flips a boolean value:

BOOL toggle = NO;

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 1;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier: @"cell"];

    if (cell) {

        cell.selectionStyle = UITableViewCellSelectionStyleNone;

        if (toggle )
        {
            NSDictionary *flight = @{@"name" : @"Bob",
                                 @"date" : @"Yesterday",
                                 @"miles" : @"3" };

            cell.textLabel.text = [flight objectForKey: @"name"];
            cell.textLabel.numberOfLines = 2;

            cell.detailTextLabel.numberOfLines = 2;
            cell.detailTextLabel.textColor = [UIColor blackColor];
            cell.detailTextLabel.text = [NSString stringWithFormat:@"Flight Date: %@, Miles Gained: %.1f", [flight objectForKey: @"date"], [[flight objectForKey: @"miles"] doubleValue]];

        } else {

            cell.textLabel.text = @"blank label";
            cell.detailTextLabel.text = @"blank detail";
        }
    }
    return cell;
}

- (IBAction)toggleValue:(id)sender
{
    toggle = !toggle;
    NSLog( @"toggle is now %@", (toggle) ? @"YES" : @"NO" );

    [tv reloadData];
}

By referencing the boolean value in tableView:cellForRowAtIndexPath: and using the code you provided, you can see that the row(s) do in fact update as expected when [table reloadData] is called.

You can download the sample project here (35KB): http://rdas.me/SampleProject.zip

I hope that this help you. If nothing else it should help you isolate where the problem may in fact reside.

Good luck!

Upvotes: 2

azfar
azfar

Reputation: 129

I can't find anything wrong in your code. However, please make sure you set your table view data source and delegate like (should be set somewhere in viewDidLoad/viewDidAppear/viewWillAppear):

[tableView setDelegate:self];
[tableView setDataSource:self];

After this, make sure you return the number of rows you need to display from the delegate method:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    return [yourDataSource count];
}

best of luck!

Upvotes: 0

Eiko
Eiko

Reputation: 25632

According to your comment, the tableView variable you called reloadData on was nil.

Common reasons for this are missing links to the IBOutlet in case of using a nib, weak ivars with ARC, or masking the ivar with a local variable with the same name.

Upvotes: 0

dan_fides
dan_fides

Reputation: 324

It's a blind shot without any code, but, if you using dequeueReusableCellWithIdentifier, it might cause the problem. If you have one cell, you can create it every time in your cellForRowAtIndexPath instead of re-using already created. Won't effect performance with such tiny table

Upvotes: 3

Marco Pace
Marco Pace

Reputation: 3870

I usually use this code:

[_myTable beginUpdates];

[... update my cell, adding / removing / changing them ...];

[_myTable endUpdates];

It works on my projects since version 5.0.

Upvotes: 0

Related Questions