Giter VIP home page Giter VIP logo

tlindexpathtools's Introduction

TLIndexPathTools

๐Ÿ”ฅ TLIndexPathTools is no longer actively maintained because Apple has finally introduced diffable data sources. ๐Ÿ”ฅ

TLIndexPathTools is a small set of classes that can greatly simplify your table and collection views. Here are some of the awesome things TLIndexPathTools does:

  • Organize data into sections with ease (now with blocks!)
  • Calculate and perform animated batch updates (inserts, moves and deletions)
  • Simplify data source and delegate methods via rich data model APIs
  • Provide a simpler alternative to Core Data NSFetchedResultsController
  • Provide base table view and collection view classes with advanced features

TLIndexPathTools is as lightweight as you want it to be. Start small by using TLIndexPathDataModel as your data model (instead of an array) and gain the ability to easily organize data into sections and simplify your view controller with APIs like [dataModel numberOfRowsInSection:], [dataModel itemAtIndexPath:], and [dataModel indexPathForItem:]. Or keep reading to learn about automatic batch updates, easier Core Data integration and more.

Installation

Use Swift Package Manager

Overview

NSArray is the standard construct for simple table and collection view data models. However, if multiple sections are involved, the typical setup is an NSArray containing section names and an NSDictionary of NSArrays containing data items, keyed by section name. Since table and collection views work with NSIndexPaths, the following pattern is used repeatedly in data source and delegate methods:

NSString *sectionName = self.sectionNameArray[indexPath.section];
NSArray *sectionArray = self.sectionArraysBySectionName[sectionName];    
id data = sectionArray[indexPath.row];

TLIndexPathDataModel encapsulates this pattern into a single class and provides numerous APIs for easy data access. Furthermore, the TLIndexPathDataModel initializers offer multiple ways to organize raw data into sections (including empty sections). TLIndexPathDataModel is perfectly suitable for single-section views where an NSArray would suffice and has the benefit of being "refactor proof" if additional sections are added later.

TLIndexPathUpdates is a very powerful companion class to TLIndexPathDataModel. One of the great things about table and collection views are their ability to perform batch updates (inserts, deletes and moves) that animate cells smoothly between states. However, calculating batch updates can be a complex (and confusing) task when multiple updates are involved. TLIndexPathUpdates solves this by taking two versions of your data model, calculating the changes for you and automatically performing the batch updates.

Most of the functionality in TLIndexPathTools can be accomplished with just TLIndexPathDataModel and TLIndexPathUpdates. However, there are a few of additional components that provide some great features:

  • TLIndexPathController provides a common programming model for building view controllers that work interchangeably with Core Data NSFetchRequests or plain arrays of any data type. One controller to rule them all.
  • TLTableViewController and TLCollectionViewController are table and collection view base classes that use TLIndexPathController and implement the essential data source and delegate methods to get you up and running quickly. They also support view controller-backed cells (see the View Controller Backed sample project) and automatic cell height calculation for table views (see the Dynamic Height sample project).
  • TLIndexPathItem is a wrapper class for data items that can simplify working with multiple data types or cell types. For example, take a look at the Settings sample project.
  • The Extensions folder contains a number of add-ons for things like collapsable sections and expandable tree views. This is a good resource to see how TLIndexPathDataModel can be easily extended for special data structures.
  • And last, but not least, the Examples folder contains numerous sample projects demonstrating various use cases and features of the framework. Shuffle is a good starting point and be sure to try Core Data.

This version of TLIndexPathTools is designed to handle up to a few thousand items. Larger data sets may have performance issues.

TLIndexPathDataModel

TLIndexPathDataModel is an immutable object you use in your view controller to hold your data items instead of an array (or dictionary of arrays, for multiple sections). There are four initializers, a basic one and three for handling multiple sections:

// single section initializer
TLIndexPathDataModel *dataModel1 = [[TLIndexPathDataModel alloc] initWithItems:items];

// multiple sections defined by a key path property on your data items
TLIndexPathDataModel *dataModel2 = [[TLIndexPathDataModel alloc] initWithItems:items sectionNameKeyPath:@"someKeyPath" identifierKeyPath:nil];

// multiple sections defined by an arbitrary code block
TLIndexPathDataModel *dataModel3 = [[TLIndexPathDataModel alloc] initWithItems:items sectionNameBlock:^NSString *(id item) {
    // organize items by first letter of description (like contacts app)
    return [item.description substringToIndex:1];
} identifierBlock:nil];

// multiple explicitly defined sections (including an empty section)
TLIndexPathSectionInfo *section1 = [[TLIndexPathSectionInfo alloc] initWithItems:@[@"Item 1.1"] name:@"Section 1"];
TLIndexPathSectionInfo *section2 = [[TLIndexPathSectionInfo alloc] initWithItems:@[@"Item 2.1", @"Item 2.2"] name:@"Section 2"];
TLIndexPathSectionInfo *section3 = [[TLIndexPathSectionInfo alloc] initWithItems:nil name:@"Section 3"];
TLIndexPathDataModel *dataModel4 = [[TLIndexPathDataModel alloc] initWithSectionInfos:@[section1, section2, section3] identifierKeyPath:nil];

And there are numerous APIs to simplify delegate and data source implementations:

// access all items across all sections as a flat array
dataModel.items;

// access items organized by sections
dataModel.sections;

// number of sections
[dataModel numberOfSections];

// number of rows in section
[dataModel numberOfRowsInSection:section];

// look up item at a given index path
[dataModel itemAtIndexPath:indexPath];

// look up index path for a given item
[dataModel indexPathForItem:item];

As an immutable object, all of the properties and methods in TLIndexPathDataModel are read-only. So using the data model is very straightforward once you've selected the appropriate initializer.

TLIndexPathUpdates

TLIndexPathUpdates is a companion class to TLIndexPathDataModel for batch updates. You provide two versions of your data model to the initializer and the inserts, deletes, and moves are calculated. Then call either performBatchUpdatesOnTableView: or performBatchUpdatesOnCollectionView: to perform the updates.

// initialize collection view with unordered items
// (assuming view controller has a self.dataModel property)
self.dataModel = [[TLIndexPathDataModel alloc] initWithItems:@[@"B", @"A", @"C"]];
[self.collectionView reloadData];

// ...

// sort items, update data model & perform batch updates (perhaps when a sort button it tapped)
TLIndexPathDataModel *oldDataModel = self.dataModel;
NSArray *sortedItems = [self.dataModel.items sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
self.dataModel = [[TLIndexPathDataModel alloc] initWithItems:sortedItems];
TLIndexPathUpdates *updates = [[TLIndexPathUpdates alloc] initWithOldDataModel:oldDataModel updatedDataModel:self.dataModel];
[updates performBatchUpdatesOnCollectionView:self.collectionView];

Thats all it takes!

TLIndexPathController

TLIndexPathController is TLIndexPathTools' version of NSFetchedResultsController. It should not come as a surprise, then, that you must use this class if you want to integrate with Core Data.

Although it primarily exists for Core Data integration, TLIndexPathController works interchangeably with NSFetchRequest or plain arrays of any data type. Thus, if you choose to standardize your view controllers on TLIndexPathController, it is possible to have a common programming model across all of your table and collection views.

TLIndexPathController also makes a few nice improvements relative to NSFetchedResultsController:

  • Items do not need to be presorted by section. The data model handles organizing sections.
  • Changes to your fetch request are animated. So you can get animated sorting and filtering.
  • Only one delegate method needs to be implemented (versus five for NSFetchedResultsController).

The basic template for using TLIndexPathController in a (table) view controller is as follows:

#import <UIKit/UIKit.h>
#import "TLIndexPathController.h"
@interface ViewController : UITableViewController <TLIndexPathControllerDelegate>
@end

#import "ViewController.h"
@interface ViewController ()
@property (strong, nonatomic) TLIndexPathController *indexPathController;
@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.indexPathController = [[TLIndexPathController alloc] init];
}

#pragma mark - UITableViewDataSource

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return self.indexPathController.dataModel.numberOfSections;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [self.indexPathController.dataModel numberOfRowsInSection:section];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
    id item = [self.indexPathController.dataModel itemAtIndexPath:indexPath];
    //configure cell using data item
    return cell;
}

#pragma mark - TLIndexPathControllerDelegate

- (void)controller:(TLIndexPathController *)controller didUpdateDataModel:(TLIndexPathUpdates *)updates
{
    [updates performBatchUpdatesOnTableView:self.tableView withRowAnimation:UITableViewRowAnimationFade];    
}

@end

This template works with plain arrays or NSFetchRequests. With plain arrays, you simply set the dataModel property of the controller (or set the items property and get a default data model). With NSFetchRequests, you set the fetchRequest property and call performFetch:. From then on, the controller updates the data model internally every time the fetch results change (using an internal instance of NSFetchedResultsController and responding to controllerDidChangeContent messages).

In either case, whether you explicitly set a data model or the controller converts a fetch result into a data model, the controller creates the TLIndexPathUpdates object for you and passes it to the delegate, giving you an opportunity to perform batch updates:

- (void)controller:(TLIndexPathController *)controller didUpdateDataModel:(TLIndexPathUpdates *)updates
{
    [updates performBatchUpdatesOnTableView:self.tableView withRowAnimation:UITableViewRowAnimationFade];    
}

The willUpdateDataModel delegate method is a really cool feature of TLIndexPathController, providing the delegate an opportunity to modify the data model before didUpdateDataModel gets called. This can be applied in some interesting ways when integrating with Core Data. For example, it can be used to mix in non-Core Data objects (try doing this with NSFetchedResultsController). Another nice application is automatic display of a "No results" message when the data model is empty (the TLNoResultsTableDataModel class is provided in the Extensions folder):

- (TLIndexPathDataModel *)controller:(TLIndexPathController *)controller willUpdateDataModel:(TLIndexPathDataModel *)oldDataModel withDataModel:(TLIndexPathDataModel *)updatedDataModel
{
    if (updatedDataModel.items.count == 0) {
        return [[TLNoResultsTableDataModel alloc] initWithRows:3 blankCellId:@"BlankCell" noResultsCellId:@"NoResultsCell" noResultsText:@"No results to display"];
    }
    return nil;
}

TLTableViewController & TLCollectionViewController

TLTableViewController and TLCollectionViewController are table and collection view base classes that use TLIndexPathController and implement the essential data source and delegate methods to get you up and running quickly. Both classes look much like the code outlined above for integrating with TLIndexPathController.

Both classes support view controller-backed cells. Enabling this feature is as easy as overriding the instantiateViewControllerForCell: method. For example, see the View Controller Backed sample project.

TLTableViewController also includes a default implementation of heightForRowAtIndexPath that calculates static or data-driven cell heights using prototype cell instances. For example, if you're using storyboards, the cell heights specified in the storyboard are automatically used. And if your cell implements the TLDynamicSizeView protocol, the height will be determined by calling the sizeWithData: method on the prototype cell. This is a great way to handle data-driven height because the sizeWithData: method can use the actual layout logic of the cell itself, rather than duplicating the layout logic in the view controller.

Most of the sample projects are based on TLTableViewController or TLCollectionViewController, so a brief perusal will give you a good idea what can be accomplished with a few lines of code.

Documentation

The Xcode docset can be generated by running the Docset project. The build configuration assumes Appledoc is installed at /usr/local/bin/appledoc. This can be changed at TLIndexPathTools project | Docset target | Build Phases tab | Run Script.

The API documentation is also available online.

About SwiftKick Mobile

We build high quality apps! Get in touch if you need help with a project.

tlindexpathtools's People

Contributors

cdzombak avatar cozmonate avatar futuretap avatar huntermonk avatar hyerra avatar levigroker avatar teanet avatar wtmoose avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tlindexpathtools's Issues

Expanded TLCollapsibleTableViewController not going to top of section

Hi,
I have a directory application running on iOS7 using the TLCollapsibleTableViewController class. When I click on a large section, lets say A, where there are over 50 results, the section header, in this case "A", cuts off two rows, so instead of Albert being the first entry, its Albridge, and to get to Albert my users have to scroll up some when a section is expanded. Any clean fix for this? Is it a iOS7 issue? Thanks for any input!

'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0

Firstly, thanks for writing this, its been a great find.

For testing purposes I am lazy loading content from sqlite. Its a very small table ...54 rows and 3 columns.

The error below occurs after continue usage. I can expand/collapse etc and the error cannot be consistently reproduced. I may be misunderstanding how the following should be implemented properly ?

- (void)controller:(TLTreeTableViewController *)controller willChangeNode:(TLIndexPathTreeItem *)treeItem collapsed:(BOOL)collapsed {
    if (collapsed == NO && [treeItem.childItems count] == 0)
    {
        Audit_categories *category = treeItem.data;
        NSArray *result = [[FAModel sharedInstance] subCategoriesForCategoryId: [category categoryId]];

        if ([result count] > 0)
        {
            NSMutableArray *childItems = [NSMutableArray arrayWithCapacity:result.count];
            [result enumerateObjectsUsingBlock:^(id subCategory, NSUInteger idx, BOOL *stop) {
                depthCount = 0;
                NSString *cellID = [NSString stringWithFormat:@"Level%d", [self depthLevelForCategory: subCategory]];
        //        NSLog(@" %@ -> %@ cellID: %@", [category categoryName], [subCategory categoryName], cellID);
                TLIndexPathTreeItem *childItem = [[TLIndexPathTreeItem alloc] initWithIdentifier: [subCategory categoryName] sectionName:nil cellIdentifier: cellID data:subCategory andChildItems:[[NSArray alloc] init]];
                [childItems addObject:childItem];

            }];
            TLIndexPathTreeItem *newTreeItem = [treeItem copyWithChildren:childItems];
            [self setNewVersionOfItem:newTreeItem collapsedChildNodeIdentifiers:[TLIndexPathItem identifiersForIndexPathItems:treeItem.childItems]];
        }
    }
}

*** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-2380.17/UITableView.m:1070

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (46) must be equal to the number of rows contained in that section before the update (36), plus or minus the number of rows inserted or deleted from that section (2 inserted, 2 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'

image

DataModel+IndexPathUpdates do not detect modified Core Data model objects

I have an app using a TLCollectionViewController backed by Core Data and a TLIndexPathController. When I modify one of my Core Data objects (in this case, changing its title), the cell isn't updated in the UI.

This is because the item in the oldDataModel gets updated transparently by Core Data. So in -[TLIndexPathUpdates initWithOldDataModel:updatedDataModel:]:

            id oldItem = [oldDataModel currentVersionOfItem:item];
            if (oldItem) {
                if (![oldItem isEqual:item]) {
                    [modifiedItems addObject:item];
                }

the [oldItem isEqual:item] check seems to indicate that the item hasn't been modified, even though in fact it was modified and the old data model has been updated. The old data model is thus out of sync with the UI.

How are changes to Core Data models supposed to be handled?

TLTreeTableViewController asynchronously expanding nodes should work if the parent node is collapsed during the operation

When asynchronously loading child nodes when the node is expanded, if the parent node is collapsed before the children have finished loading, thus hiding the node that is being populated, the call to setNewVersionOfItem is ignored because it only considers visible nodes. So the next time the node is expanded, the data will need to be loaded again.

Improve this such that the initial load is saved.

TLTableViewController with multiple table views

Is it possible to have multiple UITableViews with one controller? It seems everything is pretty baked into UITableViewController. Would this require re-writing TLTableViewController basically?

Thanks!

Setting dynamic cell height on lazy loaded tree

When loading cells into the table, heightForRowAtIndexPath: is called prior to cellForRowAtIndexPath:, so if we want to set the cell height dynamically we have to do it before the cell is created, and thus based on its contents.

In heightForRowAtIndexPath: we must obtain the cell contents just using the indexPath row. Normally we can keep track of our table items in another array and query that with the indexPath row to find its contents. But since we are lazy loading child nodes, the indexPath of a particular node changes depending on what is collapsed or expanded in the table.

Issues with another table view within same controller

Hi Tim,

Everything works fine until I added another table view (a normal table view with cells). I wrote the NumberOfRowsInSection and it somehow conflicting with your table view.

Here are my code
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (tableView == self.mainTableView)
{
return [self.fightcardArray count];
}
else
{
return 0;
}
}
mainTableView is the new table view that I added. What should I put in the Else block so that it can differentiate and return the correct value?

setObjectForKey: object cannot be nil when using TLIT on a CollectionView with a custom layout

I'm looking to use the excellent CHTCollectionViewWaterfallLayout in a CollectionView in conjunction with TLIndexPathTools.

When I try to put the two together, I get an error that looks like this:

*** setObjectForKey: object cannot be nil (key: <_UICollectionViewItemKey: 0xaaddf70> Type = SV Kind = CHTCollectionElementKindSectionHeader IndexPath = <NSIndexPath: 0xaace860> {length = 2, path = 0 - 0})'
*** First throw call stack:
(
    0   CoreFoundation                      0x010ae5e4 __exceptionPreprocess + 180
    1   libobjc.A.dylib                     0x031548b6 objc_exception_throw + 44
    2   CoreFoundation                      0x01138578 -[__NSDictionaryM setObject:forKey:] + 888
    3   UIKit                               0x0273e8fa -[UICollectionView _viewAnimationsForCurrentUpdate] + 10204
    4   UIKit                               0x0274047c -[UICollectionView _updateWithItems:] + 1635
    5   UIKit                               0x0273be58 -[UICollectionView _endItemAnimations] + 14317
    6   UIKit                               0x027419a4 -[UICollectionView _endUpdates] + 44
    7   UIKit                               0x02741b4b -[UICollectionView performBatchUpdates:completion:] + 418
    8   MakeEatSee                          0x00255556 -[TLIndexPathUpdates performBatchUpdatesOnCollectionView:completion:] + 1126

The last set of updates TLIndexPathTools tried to animate was:

debug    22:16:53.858 | Updating recipe collection view with INSERTS: (
    "<QuuxEntity: 0xada9520> (entity: QuuxEntity; id: 0xadaee80 <x-coredata://F8DE4511-16DC-4BEC-9475-D8EC23435F7E/QuuxEntity/p13> ; data: {...})"
)
DELETES: (
)
MOVED:(
    "<QuuxEntity: 0xc3e0f50> (entity: QuuxEntity; id: 0xc3df920 <x-coredata://F8DE4511-16DC-4BEC-9475-D8EC23435F7E/QuuxEntity/p4> ; data: {...})"
    "<QuuxEntity: 0xc3e0f90> (entity: QuuxEntity; id: 0xc3df930 <x-coredata://F8DE4511-16DC-4BEC-9475-D8EC23435F7E/QuuxEntity/p3> ; data: {...})"
)
MODIFIED:(
)

... where QuuxEntity is obviously a Core Data entity.

It looks like the layout wasn't expecting any assignments, but TLIT is forcing a nil down its throat. Any insights on this?

Thanks in advance.

'NSInternalInconsistencyException', reason: 'cannot move a row into a newly inserted section

Hi there,

Thanks for writing TLIndexPathTools, it's a great time saver for many table view issues.

I have an app using TLTreeTableViewController, TLTreeDataModel and TLIndexPathTreeItem, to show tasks and subtasks in various sections. The sections are based on when the tasks are due. In a manner similar to the Mailbox app, you can swipe to change the due time, and pick a new time. In my code, when an item is changed or removed, or a new one is added, I create a new TLTreeDataModel and set it on TLTreeTableViewController.

When I update the due time of an item in a way that moves it to a new section on the new data model, the table view generates the following exception :
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'cannot move a row into a newly inserted section (2)'

Workarounds? Fix?

For a workaround, I was thinking of pre-generating all possible sections with 0 items in them. That way, there's no newly inserted section, as the item moves to a section that already exists. But I haven't tried this yet.

Do you have any other ideas for workarounds, or a fix for the issue?

Thanks.

DataModel's numberOfRowsInSection: etc. methods should return NSUInteger

In Xcode 6, there are many more warnings about the need to explicitly cast NSInteger and NSUInteger values into long and unsigned long respectively in any string formatting statement, due to ever more diverse architectures being introduced to iOS.

It'd be great if TLIPT can be consistent with intentionally unsigned / signed integer values.

One such method is e.g. https://github.com/wtmoose/TLIndexPathTools/blob/0c0b1070c238153058f9d22fa66c58b86c6da1e0/Examples/Minimal/Minimal/ViewController.m#L35

EDIT: I'm aware that the UIKit API uses NSInteger right nowย โ€” why is that?

viewForHeaderInSection is calling even though is different table view

Hi Tim,

Thanks for the help from past issues that I submitted.

How do I disable the viewForHeaderInSection because I have another table view within the same controller and I just want to return an array of items in that table view (disable the header view) but instead it returns array of values over and over again depending on how many headers are there (with header showing).

Moving last row from a section causing some bugs.

  1. There are possible situation when new and old index paths of item are equal but it still should be moved. Example: table has 2 sections, first section contains only one item, when moving item from first section 1 to first place in section 2 index paths of this item before and after update is 0:0. So there should be additional check for section name.
  2. Also moving last item from a section causes exception 'cannot move a row from a deleted section'. In that case it is possible to delete and add that item instead of moving it.

TLTableViewController doesn't configure cells correctly under iOS 8

Demo project showing broken cell here: https://github.com/fatuhoku/Demo-TLIndexPathTools-Tables-Broken-Under-iOS8/tree/develop. Tested on Simulator, iPad Air (iOS 8).

Two symptoms:

  • text labels for default styles of UITableViewCell (Basic, Left / Right Detail, Subtitle) are misplaced ios simulator screen shot 2 sep 2014 17 51 20
  • custom styles cause cells to overlap ios simulator screen shot 2 sep 2014 17 54 28

The provided demo project covers the first case.

Using UITableViewController and explicitly wiring it up with TLIndexPathController still works.

Realm

First, thanks for making this ๐ŸŽŠ! It has made my job a lot easier. I had a question regarding if this could be used with Realm in place of Core Data?

Improve scroll optimizer to work with `TLTreeTableViewController`

The UITableViewController+ScrollOptimizer category can only optimize scrolling within a specified section. It should be enhanced to work within an arbitrary range of index paths so that, for example, it can be used with TLTreeTableViewController, which doesn't use sections.

Moving children in a sectioned TLTreeDataModel causes crash.

It seems TLIndexPathUpdates doesn't understand trees.

I have a TLTreeDataModel with TLIndexPathTreeItems assigned to specific sections based on the items's due date. In our system, when a child changes sections, the other items in the tree change sections in the model too.

But when I change the due date of a child in the tree, regenerate the model and set it to the TLTreeTableViewController's dataModel, I get the following crash.

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 7. The number of rows contained in an existing section after the update (0) must be equal to the number of rows contained in that section before the update (2), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'

Any ideas?

instanciateViewControllerForCell: indexPath parameter is always nil

This is because the method
-collectionView:viewControllerForCell:
and therefore
[self.collectionView indexPathForCell:cell]
gets called inside of
-collectionView:cellForItemAtIndexPath:
before the collectionView knows about the cell.

One solution would be to add a third parameter indexPath to collectionView:viewControllerForCell:

Someone has a better idea? Pull request will follow

TLTableViewController does not add view of viewcontroller as subview to cell

Hi,

I'm trying out a viewcontroller-backed table view (TLTableViewController), and i noticed that the viewcontrollers' view does not get added as a subview to the cell, like in TLCollectionViewController.

Therefore also viewDidLoad of the subviewcontroller does never get called.

Is this desired behavior or a bug? If it's a bug, I would gladly fix it but I would like to have some feedback before :)

Thanks
Steffen

TLCollapsibleTableViewController within Master of a SplitView

Hi, its a great set of classes, but i am trying to get it to work with custom cells (there are two different types of cells in the Table, with prototyped cells in the storyboard). The following error occurs when the TLCollapsibleTableViewController asks for a prototype cell with Id "cell" (which does not exist).

How can I give it one of my prototype cells?

* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* setObjectForKey: object cannot be nil (key: Cell)'

Is there any way to avoid/work arround this?

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil' crash when deleting non-persisted Core Data entities

Synopsis

TPIPT's [updates performBatchUpdatesOnCollectionView:self.collectionView]; crashes because it's confused whenever unpersisted Core Data entities that use a specific identifierKeyPath gets deleted.

Reproduction

Have a view controller hooked up with a collection view, and implement the delegate methods as TLIPT intends.

  • Create an entity "FooBar" with MagicalRecord's MR_createEntity. Do not save the entity.
  • Delete the same entity with MR_deleteEntity (this essentially calls [context deleteObject:inContext]; behind the scenes. This is bog standard Core Data).

The Crash

2014-07-10 16:13:47.706 FooBarProject[11421:70b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
*** First throw call stack:
(
    0   CoreFoundation                      0x018a61e4 __exceptionPreprocess + 180
    1   libobjc.A.dylib                     0x03b408e5 objc_exception_throw + 44
    2   CoreFoundation                      0x01858abc -[__NSArrayM insertObject:atIndex:] + 844
    3   CoreFoundation                      0x01858760 -[__NSArrayM addObject:] + 64
    4   BDDReactiveCocoa                    0x00379655 __69-[TLIndexPathUpdates performBatchUpdatesOnCollectionView:completion:]_block_invoke + 4293
    5   UIKit                               0x031345df -[UICollectionView performBatchUpdates:completion:] + 209
    6   BDDReactiveCocoa                    0x003784a6 -[TLIndexPathUpdates performBatchUpdatesOnCollectionView:completion:] + 1126
    7   BDDReactiveCocoa                    0x0037801c -[TLIndexPathUpdates performBatchUpdatesOnCollectionView:] + 108

The error is uninformative and leaves me with no leads. Perhaps TLIPT can improve on these error messages to help the user get back on their feet.

Offending code

The crash can be traced down into the code below in TLIndexPathUpdates.m:

        if (self.deletedItems.count) {
            NSMutableArray *indexPaths = [[NSMutableArray alloc] init];
            for (id item in self.deletedItems) {
                NSIndexPath *indexPath = [self.oldDataModel indexPathForItem:item]; // *** oldModel returns nil indexPath
                [indexPaths addObject:indexPath];
            }
            [collectionView deleteItemsAtIndexPaths:indexPaths];
        }

Extra information

I've defined my indexPathController like so:

    TLIndexPathController *indexPathController = [[TLIndexPathController alloc] initWithFetchRequest:fetchedRequest
                                          managedObjectContext:[NSManagedObjectContext MR_defaultContext]
                                            sectionNameKeyPath:nil
                                             identifierKeyPath:@"someID"  // someID is a field in FooBar.
                                                     cacheName:nil];

This function is pretty unsafe, because it's making the assumption that self.oldDataModel indexPathForItem will always succeed.

Below is some useful trace info I gathered when investigating the problem.

// Output from controller:willUpdateDataModel:withDataModel:

[DEBUG]   16:38:20.768 | > from model: Model(count=7, items=(
    "<FooBar: 0xb24b690> (entity: FooBar; id: 0xb24ce90 <x-coredata://55FB8417-7CBB-40CB-8633-CD9AE3447B4E/FooBar/p6> ; data: {...})",
    "<FooBar: 0xb24b4a0> (entity: FooBar; id: 0xb24bd30 <x-coredata://55FB8417-7CBB-40CB-8633-CD9AE3447B4E/FooBar/p8> ; data: {...})",
    "<FooBar: 0xb24b460> (entity: FooBar; id: 0xb24bd40 <x-coredata://55FB8417-7CBB-40CB-8633-CD9AE3447B4E/FooBar/p14> ; data: {...})",
    "<FooBar: 0xd0bbe80> (entity: FooBar; id: 0x144cfd20 <x-coredata:///FooBar/tE0DEE8FD-9BF1-4361-83D6-69FD3DA31B663> ; data: <fault>)",
    "<FooBar: 0xd0bcb80> (entity: FooBar; id: 0xd079380 <x-coredata:///FooBar/tE0DEE8FD-9BF1-4361-83D6-69FD3DA31B662> ; data: {...})",
    "<FooBar: 0xb24b3e0> (entity: FooBar; id: 0xb24bd60 <x-coredata://55FB8417-7CBB-40CB-8633-CD9AE3447B4E/FooBar/p7> ; data: {...})",
    "<FooBar: 0xb24b3a0> (entity: FooBar; id: 0xb24bd70 <x-coredata://55FB8417-7CBB-40CB-8633-CD9AE3447B4E/FooBar/p4> ; data: {...})"
))

2014-07-10 16:38:20.768 BDDReactiveCocoa[11798:70b] inserted: 0, deleted: 1, moved: 3, modified: 0
[DEBUG]   16:38:20.769 | > to model: Model(count=6, items=(
    "<FooBar: 0xb24b690> (entity: FooBar; id: 0xb24ce90 <x-coredata://55FB8417-7CBB-40CB-8633-CD9AE3447B4E/FooBar/p6> ; data: {...})",
    "<FooBar: 0xb24b4a0> (entity: FooBar; id: 0xb24bd30 <x-coredata://55FB8417-7CBB-40CB-8633-CD9AE3447B4E/FooBar/p8> ; data: {...})",
    "<FooBar: 0xb24b460> (entity: FooBar; id: 0xb24bd40 <x-coredata://55FB8417-7CBB-40CB-8633-CD9AE3447B4E/FooBar/p14> ; data: {...})",
    "<FooBar: 0xd0bcb80> (entity: FooBar; id: 0xd079380 <x-coredata:///FooBar/tE0DEE8FD-9BF1-4361-83D6-69FD3DA31B662> ; data: {...})",
    "<FooBar: 0xb24b3e0> (entity: FooBar; id: 0xb24bd60 <x-coredata://55FB8417-7CBB-40CB-8633-CD9AE3447B4E/FooBar/p7> ; data: {...})",
    "<FooBar: 0xb24b3a0> (entity: FooBar; id: 0xb24bd70 <x-coredata://55FB8417-7CBB-40CB-8633-CD9AE3447B4E/FooBar/p4> ; data: {...})"
))

It's obvious that in the old model, there's one item that has a data fault. This would cause [self.oldDataModel indexPathForItem:item]; to return nil, because it cannot access the identifier field someID of the deleted object, and therefore cannot look up the correct indexPath and therefore returns nil. This crashes the app.

Workaround

Persist the object (this is not what I want to do every time I create an object!)

Warning : Duplicate Item Ignored (Noob Question)

Quick and probably stupid question. I have table row values that are duplicates (think of locations for scenes in a movie script...) I need to be able to list them all, i.e., two or more scenes can occur in the same location.

Question: How can I eliminate the above warning (or do I have to fabricate a unique identifier for each row? Can't I just render them in cells, dupes an all)

Brilliant code by the way...
Thanx for sharing this

Mix with non Core Data objects

How would I manage to use the TLIndexPathController to group Core Data objects into sections, starting from section 1. Where section 0, contains non Core Data objects?
Thank you for this library and in advance.

Don't needlessly move sections

An example scenario is when there are 3 sections and the middle section is deleted:

Initial state:
Section A
Section B
Section C

Updated state:
Section A
Section C

TLIndexPathTools calculates that Section B is deleted and Section C is moved. The problem is that moving Section C can cause visual issues with updated being made within section C. The solution is to simply not move Section C because it is implied by when Section B is deleted.

Exception when moving item into new section.

When in single performBatchUpdates call new section added and old item moved into this section exception raises. I guess it's easy to fix this by checking movedPaths for new section and do not move them.

Compatibility with UISearchController [Xcode 6 beta 5]

In iOS 8, UISearchDisplayController has been deprecated in favour of using UISearchController instead.

The interface of UISearchController works quite differently: it takes a result view, as well as a so called id <UISearchResultsUpdating> delegate to let the user have a chance of updating the UI.

- (void)updateSearchResultsForSearchController:(UISearchController *)searchController;

I thought TLTableViewController's automatic table updates would provide a perfect drop-in implementation of this method. All one needs to do is update the data model:

- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
    NSString *searchText = searchController.searchBar.text;

    // ... Create a new array-based data model based on the search text.
    TLIndexPathDataModel *dataModel = f(searchText);
    self.indexPathController.dataModel = dataModel;  // implicitly calls `performUpdates... on table view`
}

However, surprisingly, this does not actually work. The UI of the table view doesn't get updated with the latest rows.

I'm using ignoreDataModelUpdates = YES and an explicit [tableView reloadData] to workaround this for the time being.

Two different table views in a same TLCollapsibleTableViewController

Hi Tim,

In TLCollapsibleTableViewController.m, I have two different table views using it and what should I put to differentiate between these two?

For example

  • (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
    {
    return 45;
    }
    heightForHeaderInSection in TLCollapsibleTableViewController.m, you return 45. But what should i put to check so that my table view return different value and yours still remain 45. It should goes something like this:
  • (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
    {
    if (tableView == myTable)
    {
    return 10;
    }
    else
    {
    return 45;
    }

}

Thanks.

loading time

With a data set of 10k items, the TLIndexPathDataModel takes 45 seconds to load. With 5k items, it takes 11 seconds. With 1k items it loads in under a second.

What is it doing which is so non-linear?

Swift support

With Swift's advocation of using value types, TLIndexPathTools might benefit from a more clear distinction between classes vs. structs when deriving whether something was added, removed, deleted or modified.

Struct modification is detected as ADD + DELETE but perhaps a protocol could be introduced to identify cases when they are modified...?

Changing the TLIndexPathDataModel while animations under way causes crash

I have a segmented control that switches the data source for my collection view. The user is completely at liberty to change the segmented control very very rapidly.

If the data model changes during the animation triggered by a previous change in data model, then this crash occurs (tested under iOS Simulator, iOS SDK 8.1):

2015-03-09 13:36:56.865 MyProj[81636:1887949] -[__NSCFArray zIndex]: unrecognized selector sent to instance 0x7fbf6d557690
2015-03-09 13:36:56.887 MyProj[81636:1887949] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFArray zIndex]: unrecognized selector sent to instance 0x7fbf6d557690'
*** First throw call stack:
(
    0   CoreFoundation                      0x000000010ac4af35 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x000000010a8e3bb7 objc_exception_throw + 45
    2   CoreFoundation                      0x000000010ac5204d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
    3   CoreFoundation                      0x000000010abaa27c ___forwarding___ + 988
    4   CoreFoundation                      0x000000010aba9e18 _CF_forwarding_prep_0 + 120
    5   UIKit                               0x0000000109ad6cd6 -[UICollectionView _applyLayoutAttributes:toView:] + 43
    6   UIKit                               0x0000000109ae3319 __88-[UICollectionView _dequeueReusableViewOfKind:withIdentifier:forIndexPath:viewCategory:]_block_invoke + 34
    7   UIKit                               0x00000001095195ce +[UIView(Animation) performWithoutAnimation:] + 65
    8   UIKit                               0x0000000109ae32d6 -[UICollectionView _dequeueReusableViewOfKind:withIdentifier:forIndexPath:viewCategory:] + 1565
    9   MyProj                              0x0000000106872c01 -[MESRecipeCollectionPresenter collectionView:cellForItemAtIndexPath:] + 113
    10  UIKit                               0x0000000109ad641b -[UICollectionView _createPreparedCellForItemAtIndexPath:withLayoutAttributes:applyAttributes:] + 244
    11  UIKit                               0x0000000109aea9ef __51-[UICollectionView _viewAnimationsForCurrentUpdate]_block_invoke1300 + 516
    12  UIKit                               0x0000000109ae88c9 -[UICollectionView _viewAnimationsForCurrentUpdate] + 3956
    13  UIKit                               0x0000000109aeb5ea -[UICollectionView _updateWithItems:] + 1409
    14  UIKit                               0x0000000109ae7714 -[UICollectionView _endItemAnimations] + 13024
    15  UIKit                               0x0000000109aec899 -[UICollectionView performBatchUpdates:completion:] + 390
    16  MyProj                              0x0000000106b15194 -[TLIndexPathUpdates performBatchUpdatesOnCollectionView:completion:] + 1124
    17  MyProj                              0x0000000106b14d15 -[TLIndexPathUpdates performBatchUpdatesOnCollectionView:] + 101

Readme full of code errors

The sample code in the readme is full of errors, like mismatched brackets ([ ]) and missing pointer symbols. For example:

TLIndexPathSectionInfo section1 = [TLIndexPathSectionInfo alloc] initWithItems:@[@"Item 1.1"] name:@"Section 1"];

should be

TLIndexPathSectionInfo *section1 = [[TLIndexPathSectionInfo alloc] initWithItems:@[@"Item 1.1"] name:@"Section 1"];

TLCollectionViewController with fetchRequest delete NSManagedObject fault crash

when using TLCollectionViewController together with an NSFetchRequest and deleting some NSManagedObjects, TLCollectionViewController crashes on performing the updates, because the items in oldDataModel are all faulted and thus always return nil at the identifier key path.

current work around that I am using:

#pragma mark - TLIndexPathControllerDelegate

- (void)controller:(TLIndexPathController *)controller didUpdateDataModel:(TLIndexPathUpdates *)updates
{
    // work around a crash involving faults with the identifier when items are deleted
    if (updates.deletedItems.count) {
        [self.collectionView reloadData];
    } else {
        [super controller:controller didUpdateDataModel:updates];
    }
}

TLIndexPathUpdates should optionally suppress scrolling when inserting rows

If the user is scrolled down 20 messages on a table, and a new message comes in, calling [TLIndexPathUpdates -performBatchUpdatesOnTableView: withRowAnimation:] will insert rows at the top, pushing the other cells down. It can be confusing for a user to have the table scroll all of a sudden, so an option should be provided to suppress this behavior during row insertion.

This Stack Overflow answer describes one method for inserting rows at the top, and manually adjusting the table's contentOffset so the user never notices anything.

Generalized mechanism for pausing updates

    //TODO we might want to consider queueing up the incremental changes
    //that get reported by the backing controller while ignoring is enabled
    //and not having to perform a full fetch.

__TLIndexPathDataModelNilSectionName__ when rebuilding data model

I have a uicollectionview with images grouped by either day, month or year. Switching between those resulted in some performance issues with the data model, because it had to move 700 items around into new sections. So I figured that just clearing out the collection view and then rebuilding it would make it faster, and it does. Unfortunately, this results in the first section name being TLIndexPathDataModelNilSectionName.

Here is some code

// create a new model with an empty array
TLIndexPathDataModel *newModel = [[TLIndexPathDataModel alloc] initWithSectionInfos:[NSArray array] identifierKeyPath:nil];
TLIndexPathDataModel *oldModel = self.dataModel;
TLIndexPathUpdates *updates = [[TLIndexPathUpdates alloc] initWithOldDataModel:oldModel updatedDataModel:newModel];

[self setDataModel:newModel];

[updates performBatchUpdatesOnCollectionView:self.collectionView];

// this does the same as the above code, but then with 700 images being inserted
[self updateDataModel];

Is this a bug or am I doing something wrong?

UICollectionViewCell not updating when using dynamic JSON content

Hi all,

I've got a small issue when updating items from the dataModel. My collectionview is refreshing just fine, cells are moving to correct places but I need to refresh the cell content too. What's the best hook to force them to redraw?

Code initial data load

self.indexPathController.dataModel = [[TLIndexPathDataModel alloc] initWithItems:[self sortDataArray:myData]
                                                                      sectionNameKeyPath:TK_PARKING_DATA_FAVORITE
                                                                       identifierKeyPath:TK_PARKING_DATA_NODE_ID];

Code Updating Content

NSMutableArray *oldItems = [NSMutableArray arrayWithArray:self.indexPathController.dataModel.items];
*** updating items ***
self.indexPathController.items = [self sortDataArray:oldItems];

Thanks

Same item in multiple sections: Possible?

I have a favorites section on top of the normal list. A favorite item appears in the top favorites section but also in the non-favorites section below. Can I model that using TLIndexPathTools? How? Specifically, I need something like

- (NSArray*)indexPathsForItem:(id)item

Items are never moved between those two sections, only added or deleted or moved within a section.

Wrong optionality on controller: willUpdateDataModel:

Swift requires a return value when using this method, but you are relying on a "nil" for the "do nothing" case.

- (TLIndexPathDataModel *)controller:(TLIndexPathController *)controller willUpdateDataModel:(TLIndexPathDataModel *)oldDataModel withDataModel:(TLIndexPathDataModel *)updatedDataModel
{
    if (updatedDataModel.items.count == 0) {
        return [[TLNoResultsTableDataModel alloc] initWithRows:3 blankCellId:@"BlankCell" noResultsCellId:@"NoResultsCell" noResultsText:@"No results to display"];
    }
    return nil;
}

If implementing this in Swift, the nil return value is invalid; the return type should be TLIndexPathDataModel? vs. TLIndexPathDataModel

    func controller(controller: TLIndexPathController, willUpdateDataModel oldDataModel: TLIndexPathDataModel?, withDataModel updatedDataModel: TLIndexPathDataModel?) -> TLIndexPathDataModel {
        if updatedDataModel!.items.count == 0 {
            // return injected model
        }
        return nil
    }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.