1

I want to update the data source of a table view with animations (like with NSFetchedResultsController) but without core data.

There may be objects that were added, objects that were removed, objects that were moved etc...

Is it possible?

Michael Kessler
  • 14,245
  • 13
  • 50
  • 64

2 Answers2

3

Take a look at TLIndexPathTools. It is an alternative to NSFetchedResultsController that, in addition to working with NSFetchRequest and Core Data objects, also works with plain arrays containing any type of data. It provides a standardized data model class TLIndexPathDataModel that can automatically organize your data into sections and has many APIs to simplify the implementation of your data source and delegate methods. But the main thing it does is automatically calculate and perform the animated batch updates for you when you update the data model.

Try running the numerous sample projects to get an idea what can be done. The Shuffle sample project is a good starting point: tap the Shuffle button and the collection view grid randomly rearranges itself with animation. Here is the full source of the Shuffle view controller:

#import "TLCollectionViewController.h"
@interface ShuffleCollectionViewController : TLCollectionViewController <UICollectionViewDelegateFlowLayout>
- (IBAction)shuffle;
@end

#import <QuartzCore/QuartzCore.h>
#import "ShuffleCollectionViewController.h"
#import "TLIndexPathDataModel.h"
#import "UIColor+Hex.h"

#define IDX_TEXT 0
#define IDX_COLOR 1

@implementation ShuffleCollectionViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    //initialize the controller with a list data items. To keep it simple, we'll
    //just use two element arrays (text and color) for our items.
    NSArray *items = @[
           @[@"A", [UIColor colorWithHexRGB:0x96D6C1]],
           @[@"B", [UIColor colorWithHexRGB:0xD696A3]],
           @[@"C", [UIColor colorWithHexRGB:0xFACB96]],
           @[@"D", [UIColor colorWithHexRGB:0xFAED96]],
           @[@"E", [UIColor colorWithHexRGB:0x96FAC3]],
           @[@"F", [UIColor colorWithHexRGB:0x6AA9CF]],
           ];
    self.indexPathController.items = items;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell = [super collectionView:collectionView cellForItemAtIndexPath:indexPath];
    //retrieve the cell data for the given index path from the controller
    //and set the cell's text label and background color
    NSArray *item = [self.indexPathController.dataModel itemAtIndexPath:indexPath];
    UILabel *label = (UILabel *)[cell viewWithTag:1];
    label.text = item[IDX_TEXT];
    cell.backgroundColor = item[IDX_COLOR];
    cell.layer.cornerRadius = 10;
    return cell;
}

- (void)shuffle
{
    //shuffle the items randomly and update the controller with the shuffled items
    NSMutableArray *shuffledItems = [NSMutableArray arrayWithArray:self.indexPathController.items];
    NSInteger count = shuffledItems.count;
    for (int i = 0; i < count; i++) {
        [shuffledItems exchangeObjectAtIndex:i withObjectAtIndex:arc4random() % count];
    }
    self.indexPathController.items = shuffledItems;
}

@end

This example derives from the provided TLCollectionViewController base class, but you can easily integrate TLIndexPathTools into your own view controllers by copy/pasting from TLCollectionViewController (there is very little going on there).

It also improves on NSFetchedResultsController in some respects. First, your items do not need to be pre-sorted to be organized into sections. Secondly, you can do animated sorting and filtering (if you change the sort descriptors or predicate with NSFetchedResultsController, you've got re-execute the fetch and you won't get animation). Try running the Core Data sample project.

The current version is designed to scale to 1000s of items. So if you've got a really large data set, you might run into performance issues.

Timothy Moose
  • 9,895
  • 3
  • 33
  • 44
  • Thanks. Looks great, but too blown up with features for this one thing that I need. I will definitely use it in couple of other projects I have. – Michael Kessler Aug 13 '13 at 08:08
  • Sure thing. `reloadSections` (as you mentioned in your answer) is a good alternative if you don't need fine-grained row animations. – Timothy Moose Aug 13 '13 at 08:13
  • I understand that you are the developer that created the TLIndexPathTools. As such, can you tell me if there is a way to use it without storyboard? I have tried to use it in this project (the one I have asked the current question for) and realised that there is no way to use it with a code-only UITableViewController (or with XIB). This is a pretty old project and it doesn't use storyboard (yet)... – Michael Kessler Aug 18 '13 at 05:46
  • @MichaelKessler It doesn't require storyboards at all as far as I'm aware. I just took one of my template projects, deleted the storyboard, and it still worked. Can you share some details about the problem you saw? – Timothy Moose Aug 18 '13 at 15:06
  • Oops. Just realized deleting the storyboard isn't a valid test. Will take another look in a bit. – Timothy Moose Aug 18 '13 at 15:16
  • Here's a [sample project](https://dl.dropboxusercontent.com/u/2183704/Table%20View%20NIB%20Test.zip) using a NIB file. Also, the [Minimal sample project](https://github.com/wtmoose/TLIndexPathTools/blob/master/Examples/Minimal/Minimal/ViewController.m) demonstrates using `TLIndexPathController` without the `TLTableViewController` base class if you want to pare down library dependencies. – Timothy Moose Aug 18 '13 at 16:48
  • So, the magic is in `[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"Cell"];` line? Because in my tests the library crashed because there was no dynamic cell (storyboard) in `tableView:cellForRowAtIndexPath:` method implementation and the cell was returned as nil... – Michael Kessler Aug 18 '13 at 19:38
  • Yeah, `TLTableViewController` provides a default implementation of `cellForRowAtIndexPath` that assumes "Cell" as the default reuse identifier. It doesn't register "Cell" for you because you could be using a custom cell class and also registering the class while using storyboards would crash. – Timothy Moose Aug 18 '13 at 22:30
  • Regarding reuse identifiers. The normal way to specify reuse identifiers is to override the `cellIdentifierAtIndexPath` method or use data objects that have a `cellIdentifierKeyPath` (like `TLIndexPathItems`). Or you can just write your own `cellForRowAtIndexPath` method (the default implementation doesn't do anything essential). You don't necessarily even need to use `TLTableViewController`. For example, see the [Minimal sample project](https://github.com/wtmoose/TLIndexPathTools/blob/master/Examples/Minimal/Minimal/ViewController.m) is a subclass of `UIViewController`. – Timothy Moose Aug 18 '13 at 22:31
  • I, actually, tried subclassing UITableViewController and implement the minimal table view datasource methods (as in minimal sample project), but it still crashed when called the `performBatchUpdatesOnTableView: withRowAnimation:` method. I have a dynamic table that may change frequently... – Michael Kessler Aug 19 '13 at 05:39
  • Hrmm. Could you possibly post a question or file a ticket detailing the crash? When you say "update frequently" do you mean rapid asynchronous changes cing in from multie background threads? I've posted solutions for that in other questions. – Timothy Moose Aug 19 '13 at 12:39
  • If you post the issue, please include the error & view controller code. It would also be helpful to know the before and after index paths in the updater. If you get the latest version of TLIndexPathTools, I added an `indexPaths` property to `TLIndexPathDataModel`. Just before you do `performBatchUpdatesOnTableView`, do a `po updates.oldDataModel.indexPaths` and a `po updates.updatedDataModel.indexPaths` in the console and copy the output. – Timothy Moose Aug 19 '13 at 13:02
0

Thanks to @IanL I have used the next solution:

[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
Community
  • 1
  • 1
Michael Kessler
  • 14,245
  • 13
  • 50
  • 64