Skip to content

Instantly share code, notes, and snippets.

@royratcliffe
Last active August 29, 2015 14:20
Show Gist options
  • Save royratcliffe/3fcb51f1249a08f17680 to your computer and use it in GitHub Desktop.
Save royratcliffe/3fcb51f1249a08f17680 to your computer and use it in GitHub Desktop.
// UITableView TableViewData.h
//
// Copyright © 2015, Roy Ratcliffe, Pioneering Software, United Kingdom
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the “Software”), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER
// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO
// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
//------------------------------------------------------------------------------
#import <UIKit/UIKit.h>
@class TableViewDataSection;
@interface TableViewDataRow : NSObject
@property (nonatomic, strong) NSMutableDictionary *info;
@property (nonatomic, weak) TableViewDataSection *section;
- (instancetype)initWithSection:(TableViewDataSection *)section NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithSection:(TableViewDataSection *)section info:(NSDictionary *)info;
/// Answers the row information combined with section and data information. The
/// answer is a read-only dictionary that you cannot mutate. The resulting
/// key-value pairs collate information from the enclosing table data, table
/// section and table row. Keys from the row override keys from the section;
/// keys from the section override keys from the data. Precedence goes from row
/// to section to data. Hence, section information becomes fall-back information
/// for every row and data information becomes fall-back information for every
/// section.
@property (nonatomic, copy, readonly) NSDictionary *defaults;
@end
@class TableViewData;
@interface TableViewDataSection : NSObject
@property (nonatomic, strong) NSMutableDictionary *info;
@property (nonatomic, strong) NSMutableArray *rows;
@property (nonatomic, weak) TableViewData *data;
- (instancetype)initWithData:(TableViewData *)data NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithData:(TableViewData *)data info:(NSDictionary *)info;
- (TableViewDataRow *)newRowWithInfo:(NSDictionary *)info;
- (TableViewDataRow *)rowAtIndex:(NSUInteger)index;
- (NSArray *)rowsOfValue:(id)value forInfoKey:(id<NSCopying>)key;
@property (nonatomic, copy, readonly) NSDictionary *defaults;
@end
/// Implements the data model for a UI kit table view using arrays and
/// dictionaries. Table views display an ordered collection of sections, each
/// comprising an ordered collection of rows. Sections of rows. The data model
/// coordinates an array of sections, each an array of rows. Mutable
/// dictionaries carry section-specific and row-specific pieces of
/// information. Sections carry information as well as rows.
@interface TableViewData : NSObject <UITableViewDataSource>
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithInfo:(NSDictionary *)info;
@property (nonatomic, strong) NSMutableDictionary *info;
@property (nonatomic, strong) NSMutableArray *sections;
- (TableViewDataSection *)newSectionWithInfo:(NSDictionary *)info;
- (TableViewDataSection *)sectionAtIndex:(NSUInteger)index;
/// Answers an array of sections whose information dictionaries carry a given
/// key whose value matches a given value. Values match when @c -isEqual:
/// answers @c YES. What happens when the section does not have a matching key?
/// The answer excludes all sections without the key.
- (NSArray *)sectionsOfValue:(id)value forInfoKey:(id<NSCopying>)key;
@property (nonatomic, copy, readonly) NSDictionary *defaults;
@end
extern NSString *const TableViewDataCellIdentifierKey;
extern NSString *const TableViewDataConfigureCellBlockKey;
extern NSString *const TableViewDataHeaderTitleKey;
extern NSString *const TableViewDataFooterTitleKey;
typedef void (^TableViewDataConfigureCellBlock)(UITableViewCell *cell, TableViewDataRow *row);
@interface UITableViewCell (TableViewData)
- (void)configureCellForTableViewDataRow:(TableViewDataRow *)row;
@end
// UITableView TableViewData.m
//
// Copyright © 2015, Roy Ratcliffe, Pioneering Software, United Kingdom
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the “Software”), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER
// EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO
// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
//------------------------------------------------------------------------------
#import "TableViewData.h"
@implementation TableViewDataRow
// designated initialiser
- (instancetype)initWithSection:(TableViewDataSection *)section
{
self = [super init];
if (self) {
self.info = [NSMutableDictionary dictionary];
self.section = section;
}
return self;
}
// convenience initialiser
- (instancetype)initWithSection:(TableViewDataSection *)section info:(NSDictionary *)info
{
self = [self initWithSection:section];
if (self) {
[self.info addEntriesFromDictionary:info];
}
return self;
}
- (NSDictionary *)defaults
{
NSMutableDictionary *defaults = [NSMutableDictionary dictionary];
[defaults addEntriesFromDictionary:self.section.defaults];
[defaults addEntriesFromDictionary:self.info];
return [defaults copy];
}
@end
@implementation TableViewDataSection
// designated initialiser
- (instancetype)initWithData:(TableViewData *)data
{
self = [super init];
if (self) {
self.info = [NSMutableDictionary dictionary];
self.rows = [NSMutableArray array];
self.data = data;
}
return self;
}
// convenience initialiser
- (instancetype)initWithData:(TableViewData *)data info:(NSDictionary *)info
{
self = [self initWithData:data];
if (self) {
[self.info addEntriesFromDictionary:info];
}
return self;
}
- (TableViewDataRow *)newRowWithInfo:(NSDictionary *)info
{
TableViewDataRow *row = [[TableViewDataRow alloc] initWithSection:self info:info];
[self.rows addObject:row];
return row;
}
- (TableViewDataRow *)rowAtIndex:(NSUInteger)index { return self.rows[index]; }
- (NSArray *)rowsOfValue:(id)value forInfoKey:(id<NSCopying>)key
{
return [self.rows filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(TableViewDataRow *evaluatedObject, NSDictionary *bindings) {
id infoValue = evaluatedObject.info[key];
return infoValue != nil && [infoValue isEqual:value];
}]];
}
- (NSDictionary *)defaults
{
NSMutableDictionary *defaults = [NSMutableDictionary dictionary];
[defaults addEntriesFromDictionary:self.data.defaults];
[defaults addEntriesFromDictionary:self.info];
return [defaults copy];
}
@end
@implementation TableViewData
// designated initialiser
- (instancetype)init
{
self = [super init];
if (self) {
self.info = [NSMutableDictionary dictionary];
self.sections = [NSMutableArray array];
}
return self;
}
// convenience initialiser
- (instancetype)initWithInfo:(NSDictionary *)info
{
self = [self init];
if (self) {
[self.info addEntriesFromDictionary:info];
}
return self;
}
- (TableViewDataSection *)newSectionWithInfo:(NSDictionary *)info
{
TableViewDataSection *section = [[TableViewDataSection alloc] initWithData:self info:info];
[self.sections addObject:section];
return section;
}
- (TableViewDataSection *)sectionAtIndex:(NSUInteger)index { return self.sections[index]; }
- (NSArray *)sectionsOfValue:(id)value forInfoKey:(id<NSCopying>)key
{
return [self.sections filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(TableViewDataSection *evaluatedObject, NSDictionary *bindings) {
id infoValue = evaluatedObject.info[key];
return infoValue != nil && [infoValue isEqual:value];
}]];
}
- (NSDictionary *)defaults { return [self.info copy]; }
//------------------------------------------------------------------------------
#pragma mark - Table View Data Source
//------------------------------------------------------------------------------
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self sectionAtIndex:section].rows.count; }
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
TableViewDataSection *section = [self sectionAtIndex:indexPath.section];
TableViewDataRow *row = [section rowAtIndex:indexPath.row];
NSString *identifier = row.info[TableViewDataCellIdentifierKey];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
// Configure the cell after de-queuing it if the row information contains a
// cell configuration block. The block's arguments define the cell
// configuration context. This includes the cell to configure and its row.
TableViewDataConfigureCellBlock configureCellBlock = row.info[TableViewDataConfigureCellBlockKey];
if (configureCellBlock) {
configureCellBlock(cell, row);
}
// If the cell class responds to -configureCellForTableViewDataRow: then
// send that message passing the table view data row. From the row, the cell
// can determine the section and from the section the data. Rows have weak
// back-links to their section, sections have weak back-links to the root
// data object. From just the row therefore, cells have access to section
// and data information.
if ([cell respondsToSelector:@selector(configureCellForTableViewDataRow:)]) {
[cell configureCellForTableViewDataRow:row];
}
return cell;
}
//--------------------------------------------------------------------- optional
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return self.sections.count; }
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [self sectionAtIndex:section].info[TableViewDataHeaderTitleKey];
}
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
{
return [self sectionAtIndex:section].info[TableViewDataFooterTitleKey];
}
@end
NSString *const TableViewDataCellIdentifierKey = @"cellIdentifier";
NSString *const TableViewDataConfigureCellBlockKey = @"configureCellBlock";
NSString *const TableViewDataHeaderTitleKey = @"headerTitle";
NSString *const TableViewDataFooterTitleKey = @"footerTitle";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment