189

I have a custom UITableView using UITableViewCells. Each UITableViewCell has 2 buttons. Clicking these buttons will change an image in a UIImageView within the cell.

Is it possible to refresh each cell separately to display the new image? Any help is appreciated.

Brian
  • 14,610
  • 7
  • 35
  • 43
Wizard Of iOS
  • 2,389
  • 2
  • 18
  • 22

9 Answers9

331

Once you have the indexPath of your cell, you can do something like:

[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPathOfYourCell, nil] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates]; 

In Xcode 4.6 and higher:

[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:@[indexPathOfYourCell] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates]; 

You can set whatever your like as animation effect, of course.

Aaron Brager
  • 65,323
  • 19
  • 161
  • 287
Romain
  • 4,080
  • 1
  • 18
  • 15
  • 4
    Nitpicking here, but of course if you were only refreshing a single cell you'd probably want to use `[NSArray arrayWithObject:]` instead. – Leo Cassarani Feb 16 '11 at 19:50
  • 58
    Also, in this situation, the `beginUpdates` and `endUpdates` are unnecessary. – kubi Mar 11 '11 at 01:00
  • @kubi hi, can you explain why they are not necessary? – Unheilig Jul 14 '13 at 17:24
  • 2
    The OP isn't animating anything, so there's no need to call the begin/endupdates – kubi Jul 15 '13 at 17:38
  • @Romain I am confused that why we call these two functions `beginUpdates` and `endUpdates`. I find that a lot of examples don't call these methods. Can you tell the different? – BlackMamba Nov 10 '13 at 05:21
  • 2
    As long as the method doesn't say **deprecated** in the last public Xcode version, all iOS versions should be fine. – Alejandro Iván Sep 21 '15 at 14:51
  • @AlejandroIván Actually as I have been told many times by SO user "rmaddy", just because an iOS API is deprecated, doesn't mean you can't use it. – Supertecnoboff Nov 13 '15 at 21:18
  • 1
    @Supertecnoboff true, but at some point it could be replaced or will change its behavior. It's better to handle deprecations asap – Alejandro Iván Nov 13 '15 at 21:20
  • @AlejandroIván Agreed. I believe it does state that on Apple's website. I guess the only reason to use a deprecated API, is to retain support for an older version of iOS. – Supertecnoboff Nov 13 '15 at 21:22
  • 1
    @Supertecnoboff I believe that's the reason. That's why I mentioned that as long as the API isn't deprecated, the code should be fine. The problem will arise when that deprecated API disappears/changes behavior and thus the app will not work properly. – Alejandro Iván Nov 13 '15 at 21:27
  • @kubi mentioned that in this situation, beginUpdates() and endUpdates() are unnecessary, may I know why is this the case? Also, then what situation are they necessary? – Happiehappie Apr 12 '16 at 09:46
  • 1
    @happiehappie `beginUpdates()` and `endUpdates()` consolidate a bunch of changes into a single animation. If you're only doing one reload, there's no need to consolidate anything. – kubi Apr 12 '16 at 16:42
34

I tried just calling -[UITableView cellForRowAtIndexPath:], but that didn't work. But, the following works for me for example. I alloc and release the NSArray for tight memory management.

- (void)reloadRow0Section0 {
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
    NSArray *indexPaths = [[NSArray alloc] initWithObjects:indexPath, nil];
    [self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
    [indexPaths release];
}
ma11hew28
  • 121,420
  • 116
  • 450
  • 651
  • Is [indexPaths release] necessary here? I thought you only needed that if you alloc'd the object yourself. – powerj1984 Dec 21 '12 at 18:38
  • 4
    He did alloc the indexPaths array. But the better question is why he thinks "tight memory management" is necessary. Autorelease will do the job perfectly well here. – John Cromartie Jan 11 '13 at 18:16
22

Swift:

func updateCell(path:Int){
    let indexPath = NSIndexPath(forRow: path, inSection: 1)

    tableView.beginUpdates()
    tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic) //try other animations
    tableView.endUpdates()
}
Esqarrouth
  • 38,543
  • 21
  • 161
  • 168
17

reloadRowsAtIndexPaths: is fine, but still will force UITableViewDelegate methods to fire.

The simplest approach I can imagine is:

UITableViewCell* cell = [self.tableView cellForRowAtIndexPath:indexPath];
[self configureCell:cell forIndexPath:indexPath];

It's important to invoke your configureCell: implementation on main thread, as it wont work on non-UI thread (the same story with reloadData/reloadRowsAtIndexPaths:). Sometimes it might be helpful to add:

dispatch_async(dispatch_get_main_queue(), ^
{
    [self configureCell:cell forIndexPath:indexPath];
});

It's also worth to avoid work that would be done outside of the currently visible view:

BOOL cellIsVisible = [[self.tableView indexPathsForVisibleRows] indexOfObject:indexPath] != NSNotFound;
if (cellIsVisible)
{
    ....
}
Maciek Czarnik
  • 5,950
  • 2
  • 37
  • 50
16

If you are using custom TableViewCells, the generic

[self.tableView reloadData];    

does not effectively answer this question unless you leave the current view and come back. Neither does the first answer.

To successfully reload your first table view cell without switching views, use the following code:

//For iOS 5 and later
- (void)reloadTopCell {
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
    NSArray *indexPaths = [[NSArray alloc] initWithObjects:indexPath, nil];
    [self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
}

Insert the following refresh method which calls to the above method so you can custom reload only the top cell (or the entire table view if you wish):

- (void)refresh:(UIRefreshControl *)refreshControl {
    //call to the method which will perform the function
    [self reloadTopCell];

    //finish refreshing 
    [refreshControl endRefreshing];
}

Now that you have that sorted, inside of your viewDidLoad add the following:

//refresh table view
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];

[refreshControl addTarget:self action:@selector(refresh:) forControlEvents:UIControlEventValueChanged];

[self.tableView addSubview:refreshControl];

You now have a custom refresh table feature that will reload the top cell. To reload the entire table, add the

[self.tableView reloadData]; to your new refresh method.

If you wish to reload the data every time you switch views, implement the method:

//ensure that it reloads the table view data when switching to this view
- (void) viewWillAppear:(BOOL)animated {
    [self.tableView reloadData];
}
App Dev Guy
  • 5,396
  • 4
  • 31
  • 54
6

Swift 3 :

tableView.beginUpdates()
tableView.reloadRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
ergunkocak
  • 3,334
  • 1
  • 32
  • 31
4

Here is a UITableView extension with Swift 5:

import UIKit

extension UITableView
{    
    func updateRow(row: Int, section: Int = 0)
    {
        let indexPath = IndexPath(row: row, section: section)
        
        self.beginUpdates()
        self.reloadRows(at: [indexPath], with: .automatic)
        self.endUpdates()
    }
    
}

Call with

self.tableView.updateRow(row: 1)
Ethan Allen
  • 14,425
  • 24
  • 101
  • 194
iphaaw
  • 6,764
  • 11
  • 58
  • 83
3

Just to update these answers slightly with the new literal syntax in iOS 6--you can use Paths = @[indexPath] for a single object, or Paths = @[indexPath1, indexPath2,...] for multiple objects.

Personally, I've found the literal syntax for arrays and dictionaries to be immensely useful and big time savers. It's just easier to read, for one thing. And it removes the need for a nil at the end of any multi-object list, which has always been a personal bugaboo. We all have our windmills to tilt with, yes? ;-)

Just thought I'd throw this into the mix. Hope it helps.

Gregory Hill
  • 100
  • 1
  • 9
1

I need the upgrade cell but I want close the keyboard. If I use

let indexPath = NSIndexPath(forRow: path, inSection: 1)
tableView.beginUpdates()
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic) //try other animations
tableView.endUpdates()

the keyboard disappear