Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS- Update UITableViewCell information without reloading the row?

Let's say you have a UITableView that displays a list of file metadata, and you want to show the download_progress of each file in a UILabel of a custom UITableViewCell. (This is an arbitrarily long list - thus dynamic cells will be reused).

If you want to update the label without calling either reloadData or reloadRowsAtIndexPaths, how can you do it?

For those who are wondering - I don't want to call either of the reload... methods because there's no need to reload the entire cell for each percentage point update on download_progress.

The only solutions I've come across are:

  • Adding the cell as a key-value observer for the file's download_progress.

  • Calling cellForRowAtIndexPath... directly to obtain the label and change it's text.

However, KVO in general isn't a fun api to work with - and even less so when you add cell reuse into the mix. Calling cellForRowAtIndexPath directly each time a percentage point is added feels dirty though.

So, what are some possible solutions? Any help would be appreciated.

Thanks.

like image 799
Christian Avatar asked Oct 19 '22 13:10

Christian


2 Answers

As a corollary to Doug's response, here is what I ended up going with:

Each file has a unique identifier, so I made it responsible for posting notifications about updates to its attributes (think KVO, but without the hassle):

I made a FileNotificationType enum (i.e. FileNotificationTypeDownloadTriggered, and FileNotificationTypeDownloadProgress). Then I would send the progress into the NSNotification's userInfo NSDictionary along with the FileNotificationType.

- (void)postNotificationWithType:(FileNotificationType)type andAttributes:(NSDictionary *)attributes
{
    NSString *unique_notification_id = <FILE UNIQUE ID>;

    NSMutableDictionary *mutable_attributes = [NSMutableDictionary dictionaryWithDictionary:attributes];
    [mutable_attributes setObject:@(type) forKey:@"type"];

    NSDictionary *user_info = [NSDictionary dictionaryWithDictionary:mutable_attributes];

    dispatch_async(dispatch_get_main_queue(), ^{
    [[NSNotificationCenter defaultCenter] postNotificationName:unique_notification_id object:nil userInfo:user_info];
    });
}

The file object also has a method to enumerate what types of notifications it could send:

- (NSArray *)notificationIdentifiers
{
    NSString *progress_id = <FILE UNIQUE ID + FILENOTIFICATIONTYPE>;
    NSString *status_id = <FILE UNIQUE ID + FILENOTIFICATIONTYPE>
    NSString *triggered_id = <FILE UNIQUE ID + FILENOTIFICATIONTYPE>

    NSArray *identifiers = @[progress_id, status_id, triggered_id];
    return identifiers;
}

So when you update an attribute of a file elsewhere, simply do this:

NSDictionary *attributes = @{@"download_progress" : @(<PROGRESS_INTEGER>)};
[file_instance postNotificationWithType:FileNotificationTypeDownloadProgress andAttributes:attributes];

On the receiving end, my table view delegate implemented these methods to add / remove my custom UITableViewCells as observers for these notifications:

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    File *file = [modelObject getFileAtIndex:indexPath.row];
    for (NSString *notification_id in file.notificationIdentifiers)
    {
        [[NSNotificationCenter defaultCenter] addObserver:cell selector:@selector(receiveFileNotification:) name:notification_id object:nil];
    }
}

- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    [[NSNotificationCenter defaultCenter] removeObserver:cell];
}

Finally, the custom UITableViewCell has to implement the receiveFileNotification: method:

 - (void)receiveFileNotification:(NSNotification *)notification
{
    FileNotificationType type = (FileNotificationType)[notification.userInfo[@"type"] integerValue];

    // Access updated property info with: [notification.userInfo valueForKey:@"<Your key here>"]

    switch (type)
    {
        case FileNotificationTypeDownloadProgress:
        {
            // Do something with the progress
            break;
        }
        case FileNotificationTypeDownloadStatus:
        {
            // Do something with the status
            break;
        }
        case FSEpisodeNotificationTypeDownloadTriggered:
        {
            // Do something if the download is triggered
            break;
        }
        default:
            break;
    }
}

Hopefully this helps someone who is looking to update tableview cells without having to reload them! The benefit over key-value observing is that you won't get issues if the File object is deallocated with the cell still observing. I also don't have to call cellForRow....

Enjoy!

like image 136
Christian Avatar answered Nov 01 '22 18:11

Christian


I would create a custom cell, which I'm guessing you've done. Then I'd have the cell listen for a specific notification that your download progress method would post, then update the label there. You'd have to figure out a way for your download progress to specify a certain cell, maybe by a title string or something that would be unique that your download progress method could be told, so your cell update method could make sure the note was meant for it. Let me know if you need me to clarify my thought process on this.

like image 28
Doug Watkins Avatar answered Nov 01 '22 18:11

Doug Watkins