Latest 1.7.1
Homepage https://github.com/benguild/BGRecursiveTableViewDataSource
License MIT
Platforms ios 8.0, requires ARC
Frameworks CoreData
Authors

NOTE: This project is no longer being maintained by the author as of March 2018.

Recursive “stacking” and modularization of UITableViewDataSource(s) with Apple iOS’s UIKit.

Version
License
Platform

Objective

To provide a lightweight module for the vertical stacking and dynamic toggling of existing, modular UITableViewDataSource implementations for a UITableView and UITableViewController, while making as few (if any) changes to UITableViewDataSource as possible.

Implementation

This module provides a liaison between multiple stacked or "pinned" UITableViewDataSource(s) for use on a single UITableView or UITableViewController. It also includes support for using NSFetchedResultsController with Core Data on a range of sections or subsections in a UITableView, rather than on the entire thing.

Structural usage diagram

With this module, adding toggleable subsections using switches and reusing "blocks" of UITableView data throughout your app becomes much easier and more straightforward, while retaining familiarity through the implementation of the well-known UIKit protocol, UITableViewDataSource.

Imagine trying to maintain `UITableViewDataSource(s)` like this!

UITableViewDataSource(s) for more complicated views (like the "Settings" app on iOS, for example) can be complex and difficult to maintain. Making changes to an implementation of this while using stacked data sources and BGRecursiveTableViewDataSource is much more straightforward, thanks to modularization and code re-use.

This module allows you to build modular, subclassable UITableViewDataSource implementations and group them together dynamically for use with one or more UITableViewController(s) easily, WITHOUT the "spaghetti code" that requires dissection and complex testing during revision or bug-fixes. Support for the dynamic adding and removal of entire UITableViewDataSource stacks is supported!

Basic differences

Part of the simplicity of this implementation comes from the use of the familiar UITableViewDataSource protocol for groups of UITableView sections or subsections. However, there are two replacement methods for typical cell de-queuing and re-queuing behavior, rather than using cellForRowAtIndexPath:indexPath, due to the abstraction layer.

- (NSString *)cellReuseIdentifierForRowAtIndexPath:(NSIndexPath *)indexPath;

This method is to provide the re-use identifier for the cell from the group (for the top-level UITableView), before this passes the UITableViewCell itself back to the BGRecursiveTableViewDataSourceSectionGroup subclass for any required reconfiguration.

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath;

Since this library handles de-queuing of cells automatically, you can perform any required reconfigurations of cells before they are displayed by subclassing this method of your BGRecursiveTableViewDataSourceSectionGroup instances. (implementation is optional, based on your needs)

Support methods

If the code within your BGRecursiveTableViewDataSourceSectionGroup needs to resolve its top-level NSIndexPath from its internal, offset NSIndexPath (based on its rows and sections’ overall position given other prior sections from any other preceding section groups), this can be accomplished by calling this method on the BGRecursiveTableViewDataSourceSectionGroup itself:

- (NSIndexPath *)translateInternalIndexPathToTopLevel:(NSIndexPath *)indexPath;

Convenience methods for inserting, reloading, or deleting rows and sections dynamically (and also beginning/ending updates on the BGRecursiveTableViewDataSourceSectionGroup(s) themselves) are provided simply by calling said methods on the BGRecursiveTableViewDataSourceSectionGroup(s) rather than on the UITableView directly:

- (void)beginUpdatesForSectionGroups:(NSSet <BGRecursiveTableViewDataSourceSectionGroup *>*)priorSectionGroups; // Use these internally instead of calling `UITableView` beginUpdates/endUpdates() methods.
- (void)endUpdatesForSectionGroups:(NSSet <BGRecursiveTableViewDataSourceSectionGroup *>*)priorSectionGroups;

- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;
- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation;

- (void)insertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
- (void)deleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
- (void)reloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;

It is possible to do this yourself in a simple, single-level implementation using translateInternalIndexPathToTopLevel:indexPath: and then calling the appropriate methods on your UITableView, but when using subsections in any section group, there is some internal mapping/shifting of indexes required. These methods will take care of that for you.

Inserting or Removing Entire Section Groups

You can add/remove entire blocks of sections and their subsections by calling these methods on the BGRecursiveTableViewDataSource:

- (void)insertSectionGroup:(BGRecursiveTableViewDataSourceSectionGroup *)sectionGroup atIndexForSectionGroup:(BGRecursiveTableViewDataSourceSectionGroup *)sectionGroupForIndex insertAfter:(BOOL)insertAfter;
- (void)removeSectionGroupAndItsDisplayedSections:(BGRecursiveTableViewDataSourceSectionGroup *)sectionGroup;

During the initial loading of the BGRecursiveTableViewDataSource only (before the UITableView has loaded or displayed any data), section groups can and should simply be appended:

- (void)appendSectionGroupToNewDataSource:(BGRecursiveTableViewDataSourceSectionGroup *)sectionGroup;

Recursion

All methods are designed to work both on top-level sections and their subsections at multiple levels, hence the "recursive" nature and design of the module. However, keep in mind that each level of recursion bears some compounding performance overhead. Unnecessary recursion should be avoided to ensure both optimal scrolling-performance at run-time and the optimal code organization/structure benefits from this module.

Enhancements and optimizations to the codebase are always welcome.

Introducing Toggleable, Recursive “Subsections”

The simplest application of BGRecursiveTableViewDataSource is without any recursion or "subsections". However, if you want toggleable or dynamic subsections that appear or disappear within another section (with the touch of a switch, for example), you may find the subsection functionality to be quite useful.

Demo of switches and recursive subsections

As with the standard, single-level basic implementation of BGRecursiveTableViewDataSource, the standard BGRecursiveTableViewDataSourceSectionGroup class (which implements the UITableViewDataSource protocol and can be subclassed) is used for subsections.

Subsections allow you to “pin” a BGRecursiveTableViewDataSourceSectionGroup to an NSIndexPath in another BGRecursiveTableViewDataSourceSectionGroup, and insert or hide all rows dynamically at run-time with a single method call. Its initial state of being expanded or hidden is configurable.

Usage

To set another BGRecursiveTableViewDataSourceSectionGroup to appear at an NSIndexPath within another BGRecursiveTableViewDataSourceSectionGroup, call this method on the parent (not the child):

- (void)setInnerSectionGroup:(BGRecursiveTableViewDataSourceSectionGroup *)innerSectionGroup forRowAtNonSubsectionIndexPath:(NSIndexPath *)indexPath isInitiallyActive:(BOOL)active;

If your UITableView has NOT loaded its data yet, that’s all there is to it. 👍🏻 — If your UITableView has already loaded its data, or if you want to show/hide the contents of a subsection BGRecursiveTableViewDataSourceSectionGroup at any point later on, you can use this method:

- (void)insertOrRemoveRowsForInnerSectionGroupAtNonSubsectionIndexPath:(NSIndexPath *)indexPath isActive:(BOOL)active;

See the "Example" project for a demonstration.

Core Data & NSFetchedResultsController

If you’re using Apple’s Core Data, you probably already know what you’re doing. Support for this is built-in using a provided subclass. Check out the "Example" project bundled with this pod/repo, and imagine subclassing and initializing BGRecursiveTableViewDataSourceFetchedResultsSectionGroup instead with a NSFetchedResultsController as a property. — More information on NSFetchedResultsController can be found here: https://developer.apple.com/reference/coredata/nsfetchedresultscontroller

BGRecursiveTableViewDataSourceFetchedResultsSectionGroup is a subclass of BGRecursiveTableViewDataSourceSectionGroup and not only inherits all of its parent’s methods and properties, but also implements all of the standard section/row handling code for NSFetchedResultsController. Using it is easy:

- (instancetype)initWithTableView:(UITableView *)tableView fetchedResultsController:(NSFetchedResultsController *)fetchedResultsController;

You can also replace the NSFetchedResultsController with another one (or nil) whenever you want:

- (void)replaceFetchedResultsController:(NSFetchedResultsController *)fetchedResultsController;

Last, a subclassable convenience method is exposed for when objects are updated… allowing you to return true and avoid reloading cells versus just making manual changes to their content directly:

- (BOOL)updateCellAtIndexPathWithoutReloading:(NSIndexPath *)indexPath indexPathForFetchedResultControllerIfDifferent:(NSIndexPath *)newIndexPath becauseDidChangeObject:(id)anObject;

Implementation of this method automatically avoids calling beginUpdates: on the UITableView if true is returned, so scrolling behavior/performance is not affected by updates made directly to cells (by you).

Using Core Data to generate empty sections

An additional subclass variant besides the standard one for using NSFetchedResultsController is also available: BGRecursiveTableViewDataSourceFetchedResultsEmptySectionGroup

This subclass will instead create empty sections for each fetched object if subclassed to return true from its displayEmptySectionsForFetchedResultsControllerObjects: method. It does this based on the NSFetchedResultsController fetched results, but adds no rows. The fetched objects are still accessible from your code within the BGRecursiveTableViewDataSourceFetchedResultsEmptySectionGroup itself, and you can instead choose to display other static or dynamic content in their place as you wish.

Skipping the empty NSFetchedResultsController section on an empty data-set

When using NSFetchedResultsController, there is a quirk that even with 0 rows (and even 0 sections), there will always be 1 section displayed. This can create some undesirable or unexpected behavior.

Utilizing the BGRecursiveTableViewDataSourceFetchedResultsOptionalHideFirstSectionIfEmptySectionGroup subclass, and returning YES for hideFirstSectionIfEmpty:, will work around this behavior with no additional code required.

Debugging Tips

Debugging exceptions and crashes with UITableView has always been kind of tedious since you can be somewhat in a void of information. BGRecursiveTableViewDataSource makes this a little easier, since you can toggle sections on/off or comment them out of your code entirely to see if issues persist. Likely, issues are in a much simpler format given the simplification and modularization of each BGRecursiveTableViewDataSourceSectionGroup implementation.

Installation

BGRecursiveTableViewDataSource is available through CocoaPods. To install
it, simply add the following line to your Podfile:

pod "BGRecursiveTableViewDataSource"

Author

Ben Guild, [email protected]

License

BGRecursiveTableViewDataSource is available under the MIT license. See the LICENSE file for more info.

Latest podspec

{
    "name": "BGRecursiveTableViewDataSource",
    "version": "1.7.1",
    "homepage": "https://github.com/benguild/BGRecursiveTableViewDataSource",
    "screenshots": "https://raw.github.com/benguild/BGRecursiveTableViewDataSource/master/demo.png",
    "summary": "Recursive u201cstackingu201d and modularization of `UITableViewDataSource(s)` with Apple iOS's `UIKit`.",
    "license": "MIT",
    "authors": {
        "Ben Guild": "[email protected]"
    },
    "source": {
        "git": "https://github.com/benguild/BGRecursiveTableViewDataSource.git",
        "tag": "1.7.1"
    },
    "social_media_url": "https://twitter.com/benguild",
    "source_files": [
        "BGRecursiveTableViewDataSource.{h,m}",
        "BGRecursiveTableViewDataSourceSectionGroup.{h,m}",
        "BGRecursiveTableViewDataSourceSectionGroup/BGRecursiveTableViewDataSourceFetchedResultsSectionGroup.{h,m}",
        "BGRecursiveTableViewDataSourceSectionGroup/BGRecursiveTableViewDataSourceFetchedResultsSectionGroup/BGRecursiveTableViewDataSourceFetchedResultsEmptySectionGroup.{h,m}",
        "BGRecursiveTableViewDataSourceSectionGroup/BGRecursiveTableViewDataSourceFetchedResultsSectionGroup/BGRecursiveTableViewDataSourceFetchedResultsEmptySectionGroup/BGRecursiveTableViewDataSourceFetchedResultsOptionalHideFirstSectionIfEmptySectionGroup.{h,m}"
    ],
    "platforms": {
        "ios": "8.0"
    },
    "requires_arc": true,
    "frameworks": "CoreData"
}

Pin It on Pinterest

Share This