Last active
April 26, 2016 06:17
-
-
Save angelolloqui/88647fdfaae19f9d6c89 to your computer and use it in GitHub Desktop.
Example combining generic DataSources with the Chain Of Responsility command with custom command objects. DataSource can fully configure any table or collection view, and fires commands by calling the factory method on click. Code is taken from a real project where advance features have been implemented like customizable chain, cell animations, …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
typedef enum { | |
AGCommandPriorityLow = -10, | |
AGCommandPriorityDefault = 0, | |
AGCommandPriorityHigh = 10 | |
} AGCommandPriority; | |
@interface AGCommand : NSObject | |
@property(nonatomic, weak) NSObject *firingObject; | |
@property(nonatomic, readonly) SEL action; | |
//Set the object with user info for the command | |
@property(nonatomic, strong) id object; | |
//If multiple commands available for an object, fire the one with higher priority. Defaults to AGCommandPriorityDefault | |
@property(nonatomic, assign) AGCommandPriority priority; | |
//Factory method that will look on all possible commands and create one that accepts the input object | |
+ (void)registerForFactory; | |
+ (instancetype)factoryCommandWithObject:(id)object; | |
//Create a specific command with different parameters | |
+ (instancetype)command; | |
+ (instancetype)commandWithObject:(id)object; | |
- (UIViewController *)firstViewController; | |
@end | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "AGCommand.h" | |
#import <objc/runtime.h> | |
@implementation AGCommand | |
static NSMutableArray *commandClasses = nil; | |
static NSSortDescriptor *prioritySortDescriptor = nil; | |
+ (void)initialize { | |
// commandClasses = [self discoverAllCommands]; | |
prioritySortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"priority" ascending:NO]; | |
} | |
+ (instancetype)command { | |
AGCommand *cmd = [[self alloc] init]; | |
//Generic commands have less priority | |
if ([cmd isMemberOfClass:[AGCommand class]]) { | |
cmd.priority = AGCommandPriorityLow; | |
} | |
return cmd; | |
} | |
+ (instancetype)commandWithObject:(id)object { | |
return nil; | |
} | |
+ (void)registerForFactory { | |
static dispatch_once_t onceToken; | |
dispatch_once(&onceToken, ^{ | |
commandClasses = [NSMutableArray array]; | |
}); | |
if (![commandClasses containsObject:self]) { | |
[commandClasses addObject:self]; | |
} | |
} | |
+ (instancetype)factoryCommandWithObject:(id)object { | |
NSMutableArray *commands = [NSMutableArray array]; | |
for (Class commandClass in commandClasses) { | |
id command = [commandClass commandWithObject:object]; | |
if (command) { | |
[commands addObject:command]; | |
} | |
} | |
return [[commands sortedArrayUsingDescriptors:@[prioritySortDescriptor]] firstObject]; | |
} | |
- (SEL)action { | |
return @selector(performGenericCommand:); | |
} | |
- (UIViewController *)firstViewController { | |
UIViewController *firingVC = (UIViewController *)self.firingObject; | |
while (firingVC && ![firingVC isKindOfClass:[UIViewController class]]) { | |
firingVC = (UIViewController *)[firingVC nextCommandResponder]; | |
} | |
return firingVC; | |
} | |
+ (NSArray *)discoverAllCommands { | |
int numClasses = objc_getClassList(NULL, 0); | |
__unsafe_unretained Class *classes = (Class *)malloc(sizeof(Class) * numClasses); | |
numClasses = objc_getClassList(classes, numClasses); | |
NSMutableArray *result = [NSMutableArray array]; | |
for (NSInteger i = 0; i < numClasses; i++) | |
{ | |
Class superClass = classes[i]; | |
do | |
{ | |
superClass = class_getSuperclass(superClass); | |
} while(superClass && superClass != self); | |
if (superClass == nil) | |
{ | |
continue; | |
} | |
[result addObject:classes[i]]; | |
} | |
free(classes); | |
return result; | |
} | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <Foundation/Foundation.h> | |
@class AGDataSource; | |
@class AGCommand; | |
@protocol AGDataSourceProtocol <UITableViewDelegate, UICollectionViewDelegate> | |
@optional | |
//Generic methods | |
- (void)dataSource:(AGDataSource *)dataSource configureCell:(id)cell withObject:(id)object forIndexPath:(NSIndexPath *)indexPath; | |
- (void)dataSource:(AGDataSource *)dataSource didSelectCellWithOject:(id)object atIndexPath:(NSIndexPath *)indexPath; | |
- (UIView *)dataSource:(AGDataSource *)dataSource viewForHeaderInSection:(NSInteger)section; | |
- (UIView *)dataSource:(AGDataSource *)dataSource viewForFooterInSection:(NSInteger)section; | |
//Table related methods | |
- (UITableViewCell *)dataSource:(AGDataSource *)dataSource createTableCellWithObject:(id)object forIndexPath:(NSIndexPath *)indexPath; | |
- (CGFloat)dataSource:(AGDataSource *)dataSource tableCellHeightWithObject:(id)object forIndexPath:(NSIndexPath *)indexPath; | |
//Collection related methods | |
- (UICollectionViewCell *)dataSource:(AGDataSource *)dataSource createCollectionCellWithObject:(id)object forIndexPath:(NSIndexPath *)indexPath; | |
- (CGSize)dataSource:(AGDataSource *)dataSource collectionCellSizeWithObject:(id)object forIndexPath:(NSIndexPath *)indexPath; | |
@end | |
@protocol AGCellProtocol <NSObject> | |
- (void)setObject:(id)object; | |
@optional | |
- (AGCommand *)didSelectCommand; | |
@end | |
#pragma mark - Generic datasource | |
@interface AGDataSource : NSObject <UITableViewDataSource, UITableViewDelegate, UICollectionViewDataSource, UICollectionViewDelegate, AGDataSourceProtocol> | |
//Generic properties | |
@property (nonatomic, strong) NSArray *sectionObjects; | |
@property (weak, nonatomic) IBOutlet id<AGDataSourceProtocol> delegate; | |
@property (nonatomic, assign) BOOL useIndexAsReuseIdentifier; | |
- (void)reloadWithData:(NSArray *)sectionObjects; | |
- (void)reloadData; | |
- (void)updateWithData:(NSArray *)sectionObjects; | |
- (void)updateWithData:(NSArray *)sectionObjects animation:(UITableViewRowAnimation)animation; | |
- (NSArray *)objectsAtSection:(NSInteger)section; | |
- (id)objectAtIndexPath:(NSIndexPath *)indexPath; | |
- (NSIndexPath *)indexPathOfObject:(id)object; | |
@end | |
#pragma mark - Table Specific | |
@interface AGDataSource () | |
@property (weak, nonatomic) IBOutlet UITableView *tableView; | |
//Registers a specific table view cell class to use with the object class specified. Pass nil to set default | |
- (void)registerTableCellClass:(Class)cellClass forObjectClass:(Class)objectClass; | |
//Registers a specific table view cell nib to use with the object class specified. Pass nil to set default | |
- (void)registerTableCellNib:(UINib *)cellNib forObjectClass:(Class)objectClass; | |
//Returns the specific cell or nib registered for this object | |
- (id)registeredTableCellNibOrClassForObject:(id)object; | |
@end | |
#pragma mark - Collection Specific | |
@interface AGDataSource () | |
@property (weak, nonatomic) IBOutlet UICollectionView *collectionView; | |
- (void)registerCollectionCellClass:(Class)cellClass forObjectClass:(Class)objectClass; | |
- (void)registerCollectionCellNib:(UINib *)cellNib forObjectClass:(Class)objectClass; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "AGDataSource.h" | |
@interface AGDataSource() | |
@property (nonatomic, strong) NSMutableDictionary *collectionCellClassNibs; | |
@property (nonatomic, strong) NSCache *cachedValues; | |
@property (nonatomic, assign) CGFloat averageHeight; | |
@property (nonatomic, strong) NSMutableDictionary *tableCellClassNibs; | |
@end | |
static NSString *undefinedObjectClass = @"NSObject"; | |
static NSInteger collectionViewHeaderTag = 100; | |
static NSInteger collectionViewFooterTag = 101; | |
@implementation AGDataSource | |
- (id)init { | |
self = [super init]; | |
if (self) { | |
[self setupGenericDataSource]; | |
} | |
return self; | |
} | |
- (void)awakeFromNib { | |
[super awakeFromNib]; | |
[self setupGenericDataSource]; | |
} | |
- (void)dealloc { | |
//Unsafe delegates and datasource should be nullified manually (non-weak) | |
if (self.tableView.dataSource == self) { | |
self.tableView.dataSource = nil; | |
} | |
if (self.tableView.delegate == self) { | |
self.tableView.delegate = nil; | |
} | |
if (self.collectionView.delegate == self) { | |
self.collectionView.delegate = nil; | |
} | |
if (self.collectionView.dataSource == self) { | |
self.collectionView.dataSource = nil; | |
} | |
} | |
- (void)setupGenericDataSource { | |
_tableCellClassNibs = [NSMutableDictionary dictionary]; | |
_tableCellClassNibs[undefinedObjectClass] = [UITableViewCell class]; | |
_collectionCellClassNibs = [NSMutableDictionary dictionary]; | |
_collectionCellClassNibs[undefinedObjectClass] = [UICollectionViewCell class]; | |
_cachedValues = [[NSCache alloc] init]; | |
} | |
- (void)setSectionObjects:(NSArray *)sectionObjects { | |
if ([_sectionObjects isEqualToArray:sectionObjects]) return; | |
_sectionObjects = sectionObjects; | |
} | |
- (void)setTableView:(UITableView *)tableView { | |
_tableView = tableView; | |
_tableView.delegate = self; | |
_tableView.dataSource = self; | |
} | |
- (void)setCollectionView:(UICollectionView *)collectionView { | |
_collectionView = collectionView; | |
_collectionView.delegate = self; | |
_collectionView.dataSource = self; | |
//Register headers and footers | |
[_collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"HeaderView"]; | |
[_collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"FooterView"]; | |
//Add previously registered classes | |
for (NSString *key in self.collectionCellClassNibs) { | |
id classNib = self.collectionCellClassNibs[key]; | |
if ([classNib isKindOfClass:[UINib class]]) { | |
[_collectionView registerNib:classNib forCellWithReuseIdentifier:key]; | |
} | |
else { | |
[_collectionView registerClass:classNib forCellWithReuseIdentifier:key]; | |
} | |
} | |
} | |
- (void)reloadWithData:(NSArray *)sectionObjects { | |
self.sectionObjects = sectionObjects; | |
[self reloadData]; | |
} | |
- (void)updateWithData:(NSArray *)sectionObjects { | |
[self updateWithData:sectionObjects animation:UITableViewRowAnimationAutomatic]; | |
} | |
- (void)updateWithData:(NSArray *)sectionObjects animation:(UITableViewRowAnimation)animation { | |
if (self.tableView.window) { | |
//For different amount of sections just refresh | |
if ([sectionObjects count] != [self.sectionObjects count]) { | |
[self reloadWithData:sectionObjects]; | |
return; | |
} | |
NSArray *oldSections = self.sectionObjects; | |
self.sectionObjects = sectionObjects; | |
sectionObjects = self.sectionObjects; | |
[self.tableView beginUpdates]; | |
for (NSInteger s = 0; s < [sectionObjects count]; s++) { | |
NSMutableArray *removeIndexes = [NSMutableArray array]; | |
NSMutableArray *insertIndexes = [NSMutableArray array]; | |
NSArray *section = sectionObjects[s]; | |
NSArray *oldSection = oldSections[s]; | |
NSMutableArray *modifiedSection = [oldSection mutableCopy]; | |
//Removed non existing objects | |
for (NSInteger r = 0; r < [oldSection count]; r++) { | |
if (![section containsObject:oldSection[r]]) { | |
[removeIndexes addObject:[NSIndexPath indexPathForRow:r inSection:s]]; | |
[modifiedSection removeObject:oldSection[r]]; | |
} | |
} | |
[self.tableView deleteRowsAtIndexPaths:removeIndexes withRowAnimation:animation]; | |
//Add new objects | |
for (NSInteger r = 0; r < [section count]; r++) { | |
if (![modifiedSection containsObject:section[r]]) { | |
[insertIndexes addObject:[NSIndexPath indexPathForRow:r inSection:s]]; | |
[modifiedSection insertObject:section[r] atIndex:r]; | |
} | |
} | |
[self.tableView insertRowsAtIndexPaths:insertIndexes withRowAnimation:animation]; | |
//Move objects | |
for (NSInteger r = 0; r < [modifiedSection count]; r++) { | |
id obj = modifiedSection[r]; | |
if (![obj isEqual:section[r]]) { | |
NSInteger newPos = [section indexOfObject:obj]; | |
if (newPos != NSNotFound) { | |
[self.tableView moveRowAtIndexPath:[NSIndexPath indexPathForRow:r inSection:s] toIndexPath:[NSIndexPath indexPathForRow:newPos inSection:s]]; | |
} | |
} | |
} | |
} | |
[self.tableView endUpdates]; | |
} | |
else { | |
[self reloadWithData:sectionObjects]; | |
} | |
} | |
- (void)reloadData { | |
self.averageHeight = (self.tableView.rowHeight > 0? self.tableView.rowHeight : 44); | |
[self.cachedValues removeAllObjects]; | |
[self.tableView reloadData]; | |
[self.collectionView reloadData]; | |
} | |
- (NSArray *)objectsAtSection:(NSInteger)section { | |
if (section >= 0 && [self.sectionObjects count] > section) { | |
return self.sectionObjects[section]; | |
} | |
return nil; | |
} | |
- (id)objectAtIndexPath:(NSIndexPath *)indexPath { | |
NSArray *objects = [self objectsAtSection:indexPath.section]; | |
if (indexPath.row >= 0 && [objects count] > indexPath.row) { | |
return objects[indexPath.row]; | |
} | |
return nil; | |
} | |
- (NSIndexPath *)indexPathOfObject:(id)object { | |
if (!object) return nil; | |
for (NSInteger section = 0; section < [self.sectionObjects count]; section++) { | |
NSArray *objects = [self objectsAtSection:section]; | |
NSInteger row = [objects indexOfObject:object]; | |
if (row != NSNotFound) { | |
return [NSIndexPath indexPathForRow:row inSection:section]; | |
} | |
} | |
return nil; | |
} | |
- (void)registerTableCellClass:(Class)cellClass forObjectClass:(Class)objectClass { | |
NSString *key = NSStringFromClass(objectClass)? : undefinedObjectClass; | |
self.tableCellClassNibs[key] = cellClass; | |
NSString *cachedValueKey = [NSString stringWithFormat:@"height-table-cell-%@", NSStringFromClass(objectClass)]; | |
[self.cachedValues removeObjectForKey:cachedValueKey]; | |
} | |
- (void)registerTableCellNib:(UINib *)cellNib forObjectClass:(Class)objectClass { | |
NSString *key = NSStringFromClass(objectClass)? : undefinedObjectClass; | |
self.tableCellClassNibs[key] = cellNib; | |
NSString *cachedValueKey = [NSString stringWithFormat:@"height-table-cell-%@", NSStringFromClass(objectClass)]; | |
[self.cachedValues removeObjectForKey:cachedValueKey]; | |
} | |
- (void)registerCollectionCellClass:(Class)cellClass forObjectClass:(Class)objectClass { | |
NSString *key = NSStringFromClass(objectClass)? : undefinedObjectClass; | |
self.collectionCellClassNibs[key] = cellClass; | |
[self.collectionView registerClass:cellClass forCellWithReuseIdentifier:key]; | |
NSString *cachedValueKey = [NSString stringWithFormat:@"size-grid-cell-%@", NSStringFromClass(objectClass)]; | |
[self.cachedValues removeObjectForKey:cachedValueKey]; | |
} | |
- (void)registerCollectionCellNib:(UINib *)cellNib forObjectClass:(Class)objectClass { | |
NSString *key = NSStringFromClass(objectClass)? : undefinedObjectClass; | |
self.collectionCellClassNibs[key] = cellNib; | |
[self.collectionView registerNib:cellNib forCellWithReuseIdentifier:key]; | |
NSString *cachedValueKey = [NSString stringWithFormat:@"size-grid-cell-%@", NSStringFromClass(objectClass)]; | |
[self.cachedValues removeObjectForKey:cachedValueKey]; | |
} | |
- (id)registeredTableCellNibOrClassForObject:(id)object { | |
Class cls = [object class]; | |
while (cls) { | |
id registeredNibOrClass = self.tableCellClassNibs[NSStringFromClass(cls)]; | |
if (registeredNibOrClass) return registeredNibOrClass; | |
cls = [cls superclass]; | |
} | |
return self.tableCellClassNibs[undefinedObjectClass]? : [UITableViewCell class]; | |
} | |
#pragma mark - AGDataSourceProtocol proxies | |
- (UITableViewCell *)dataSource:(AGDataSource *)dataSource createTableCellWithObject:(id)object forIndexPath:(NSIndexPath *)indexPath { | |
NSString *cellIdentifier = NSStringFromClass([object class]) ?: @"Cell"; | |
if (self.useIndexAsReuseIdentifier) { | |
cellIdentifier = [cellIdentifier stringByAppendingFormat:@"-%ld-%ld", (long)indexPath.section, (long)indexPath.row]; | |
} | |
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:cellIdentifier]; | |
if (!cell) { | |
id classNib = [self registeredTableCellNibOrClassForObject:object]; | |
if ([classNib isKindOfClass:[UINib class]]) { | |
NSArray *nibContents = [classNib instantiateWithOwner:self options:nil]; | |
if (([nibContents count]) && ([nibContents[0] isKindOfClass:[UITableViewCell class]])) { | |
cell = nibContents[0]; | |
[cell setValue:cellIdentifier forKey:@"reuseIdentifier"]; | |
} | |
} | |
else { | |
cell = [[classNib alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; | |
} | |
} | |
return cell; | |
} | |
- (UICollectionViewCell *)dataSource:(AGDataSource *)dataSource createCollectionCellWithObject:(id)object forIndexPath:(NSIndexPath *)indexPath { | |
NSString *cellIdentifier = NSStringFromClass([object class]); | |
if (self.collectionCellClassNibs[cellIdentifier] == nil) { | |
cellIdentifier = undefinedObjectClass; | |
} | |
UICollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath]; | |
return cell; | |
} | |
- (void)dataSource:(AGDataSource *)dataSource configureCell:(id)cell withObject:(id)object forIndexPath:(NSIndexPath *)indexPath { | |
if ([cell conformsToProtocol:@protocol(AGDataSource)]) { | |
[cell setObject:object]; | |
} | |
else if ([cell respondsToSelector:@selector(textLabel)]){ | |
[[cell textLabel] setText:[object description]]; | |
} | |
} | |
- (void)dataSource:(AGDataSource *)dataSource didSelectCellWithOject:(id)object atIndexPath:(NSIndexPath *)indexPath { | |
AGCommand *command = nil; | |
if (self.tableView) { | |
[self.tableView deselectRowAtIndexPath:indexPath animated:YES]; | |
UITableViewCell<AGCellProtocol> *cell = (UITableViewCell<AGCellProtocol> *)[self.tableView cellForRowAtIndexPath:indexPath]; | |
if ([cell respondsToSelector:@selector(didSelectCommand)]) { | |
command = [cell didSelectCommand]; | |
} | |
} | |
else { | |
[self.collectionView deselectItemAtIndexPath:indexPath animated:YES]; | |
UICollectionViewCell<AGCellProtocol> *cell = (UICollectionViewCell<AGCellProtocol> *)[self.collectionView cellForItemAtIndexPath:indexPath]; | |
if ([cell respondsToSelector:@selector(didSelectCommand)]) { | |
command = [cell didSelectCommand]; | |
} | |
} | |
if (!command) { | |
command = [AGCommand factoryCommandWithObject:object]; | |
} | |
[self.tableView sendCommand:command]; | |
[self.collectionView sendCommand:command]; | |
} | |
- (UIView *)dataSource:(AGDataSource *)dataSource viewForHeaderInSection:(NSInteger)section { | |
return nil; | |
} | |
- (UIView *)dataSource:(AGDataSource *)dataSource viewForFooterInSection:(NSInteger)section { | |
return nil; | |
} | |
- (CGFloat)dataSource:(AGDataSource *)dataSource tableCellHeightWithObject:(id)object forIndexPath:(NSIndexPath *)indexPath { | |
//Fetch from cache first | |
NSString *cachedValueKey = [NSString stringWithFormat:@"height-table-cell-%@", NSStringFromClass([object class])]; | |
if ([self.cachedValues objectForKey:cachedValueKey]) { | |
return [[self.cachedValues objectForKey:cachedValueKey] floatValue]; | |
} | |
//Fetch from cell size | |
UITableViewCell *cell = nil; | |
if ([self.delegate respondsToSelector:@selector(dataSource:createTableCellWithObject:forIndexPath:)]) { | |
cell = [self.delegate dataSource:self createTableCellWithObject:object forIndexPath:indexPath]; | |
} | |
else { | |
cell = [self dataSource:self createTableCellWithObject:object forIndexPath:indexPath]; | |
} | |
CGFloat height = cell.frame.size.height; | |
[self.cachedValues setObject:@(height) forKey:cachedValueKey]; | |
return height; | |
} | |
- (CGSize)dataSource:(AGDataSource *)dataSource collectionCellSizeWithObject:(id)object forIndexPath:(NSIndexPath *)indexPath { | |
//Fetch from cache first | |
NSString *cachedValueKey = [NSString stringWithFormat:@"size-grid-cell-%@", NSStringFromClass([object class])]; | |
if ([self.cachedValues objectForKey:cachedValueKey]) { | |
return [[self.cachedValues objectForKey:cachedValueKey] CGSizeValue]; | |
} | |
//Fetch from cell size | |
UICollectionViewCell *cell = nil; | |
NSString *objectClass = NSStringFromClass([object class]); | |
id classNib = self.collectionCellClassNibs[objectClass]? : self.collectionCellClassNibs[undefinedObjectClass]; | |
if ([classNib isKindOfClass:[UINib class]]) { | |
NSArray *nibContents = [classNib instantiateWithOwner:self options:nil]; | |
if (([nibContents count]) && ([nibContents[0] isKindOfClass:[UICollectionViewCell class]])) { | |
cell = nibContents[0]; | |
} | |
} | |
else { | |
cell = [[classNib alloc] init]; | |
} | |
[self.cachedValues setObject:[NSValue valueWithCGSize:cell.frame.size] forKey:cachedValueKey]; | |
return cell.frame.size; | |
} | |
#pragma mark - UITableViewDataSource | |
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { | |
return [self.sectionObjects count]; | |
} | |
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { | |
return [[self objectsAtSection:section] count]; | |
} | |
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { | |
id object = [self objectAtIndexPath:indexPath]; | |
//Create cell | |
UITableViewCell *cell = nil; | |
if ([self.delegate respondsToSelector:@selector(dataSource:createTableCellWithObject:forIndexPath:)]) { | |
cell = [self.delegate dataSource:self createTableCellWithObject:object forIndexPath:indexPath]; | |
} | |
else { | |
cell = [self dataSource:self createTableCellWithObject:object forIndexPath:indexPath]; | |
} | |
//Configure cell | |
if ([self.delegate respondsToSelector:@selector(dataSource:configureCell:withObject:forIndexPath:)]) { | |
[self.delegate dataSource:self configureCell:cell withObject:object forIndexPath:indexPath]; | |
} | |
else { | |
[self dataSource:self configureCell:cell withObject:object forIndexPath:indexPath]; | |
} | |
return cell; | |
} | |
#pragma mark - UITableViewDelegate | |
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { | |
id object = [self objectAtIndexPath:indexPath]; | |
if ([self.delegate respondsToSelector:@selector(dataSource:didSelectCellWithOject:atIndexPath:)]) { | |
[self.delegate dataSource:self didSelectCellWithOject:object atIndexPath:indexPath]; | |
} | |
else { | |
[self dataSource:self didSelectCellWithOject:object atIndexPath:indexPath]; | |
} | |
} | |
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { | |
CGFloat height = 0; | |
id object = [self objectAtIndexPath:indexPath]; | |
if ([self.delegate respondsToSelector:@selector(dataSource:tableCellHeightWithObject:forIndexPath:)]) { | |
height = [self.delegate dataSource:self tableCellHeightWithObject:object forIndexPath:indexPath]; | |
} | |
else { | |
height = [self dataSource:self tableCellHeightWithObject:object forIndexPath:indexPath]; | |
} | |
self.averageHeight += (height - self.averageHeight) * .3; | |
return height; | |
} | |
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { | |
id object = [self objectAtIndexPath:indexPath]; | |
NSString *cachedValueKey = [NSString stringWithFormat:@"height-table-cell-%@", NSStringFromClass([object class])]; | |
if ([self.cachedValues objectForKey:cachedValueKey]) { | |
return [[self.cachedValues objectForKey:cachedValueKey] floatValue]; | |
} | |
return [self tableView:tableView heightForRowAtIndexPath:indexPath]; | |
} | |
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { | |
if ([self.delegate respondsToSelector:@selector(dataSource:viewForHeaderInSection:)]) { | |
return [self.delegate dataSource:self viewForHeaderInSection:section]; | |
} | |
else { | |
return [self dataSource:self viewForHeaderInSection:section]; | |
} | |
} | |
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { | |
UIView *header = [self tableView:tableView viewForHeaderInSection:section]; | |
if (header) return header.frame.size.height; | |
return UITableViewAutomaticDimension; | |
} | |
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { | |
if ([self.delegate respondsToSelector:@selector(dataSource:viewForFooterInSection:)]) { | |
return [self.delegate dataSource:self viewForFooterInSection:section]; | |
} | |
else { | |
return [self dataSource:self viewForFooterInSection:section]; | |
} | |
} | |
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { | |
UIView *footer = [self tableView:tableView viewForFooterInSection:section]; | |
if (footer) return footer.frame.size.height; | |
return UITableViewAutomaticDimension; | |
} | |
#pragma mark - UICollectionViewDataSource | |
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { | |
return [self numberOfSectionsInTableView:nil]; | |
} | |
// Populating subview items | |
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { | |
return [self tableView:nil numberOfRowsInSection:section]; | |
} | |
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { | |
id object = [self objectAtIndexPath:indexPath]; | |
//Create cell | |
UICollectionViewCell *cell = nil; | |
if ([self.delegate respondsToSelector:@selector(dataSource:createCollectionCellWithObject:forIndexPath:)]) { | |
cell = [self.delegate dataSource:self createCollectionCellWithObject:object forIndexPath:indexPath]; | |
} | |
else { | |
cell = [self dataSource:self createCollectionCellWithObject:object forIndexPath:indexPath]; | |
} | |
//Configure cell | |
if ([self.delegate respondsToSelector:@selector(dataSource:configureCell:withObject:forIndexPath:)]) { | |
[self.delegate dataSource:self configureCell:cell withObject:object forIndexPath:indexPath]; | |
} | |
else { | |
[self dataSource:self configureCell:cell withObject:object forIndexPath:indexPath]; | |
} | |
return cell; | |
} | |
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath | |
{ | |
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { | |
UIView *view = nil; | |
if ([self.delegate respondsToSelector:@selector(dataSource:viewForHeaderInSection:)]) { | |
view = [self.delegate dataSource:self viewForHeaderInSection:indexPath.section]; | |
} | |
else { | |
view = [self dataSource:self viewForHeaderInSection:indexPath.section]; | |
} | |
if (!view) return nil; | |
UICollectionReusableView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"HeaderView" forIndexPath:indexPath]; | |
[[headerView viewWithTag:collectionViewHeaderTag] removeFromSuperview]; | |
view.frame = headerView.bounds; | |
view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; | |
[view setTag:collectionViewHeaderTag]; | |
[headerView addSubview:view]; | |
return headerView; | |
} | |
if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { | |
UIView *view = nil; | |
if ([self.delegate respondsToSelector:@selector(dataSource:viewForFooterInSection:)]) { | |
view = [self.delegate dataSource:self viewForFooterInSection:indexPath.section]; | |
} | |
else { | |
view = [self dataSource:self viewForFooterInSection:indexPath.section]; | |
} | |
if (!view) return nil; | |
UICollectionReusableView *footerCellView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"FooterView" forIndexPath:indexPath]; | |
[[footerCellView viewWithTag:collectionViewFooterTag] removeFromSuperview]; | |
view.frame = footerCellView.bounds; | |
view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; | |
[view setTag:collectionViewFooterTag]; | |
[footerCellView addSubview:view]; | |
return footerCellView; | |
} | |
return nil; | |
} | |
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { | |
UIView *view = nil; | |
if ([self.delegate respondsToSelector:@selector(dataSource:viewForHeaderInSection:)]) { | |
view = [self.delegate dataSource:self viewForHeaderInSection:section]; | |
} | |
else { | |
view = [self dataSource:self viewForHeaderInSection:section]; | |
} | |
if (view) { | |
return view.frame.size; | |
} | |
return CGSizeZero; | |
} | |
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section { | |
UIView *view = nil; | |
if ([self.delegate respondsToSelector:@selector(dataSource:viewForFooterInSection:)]) { | |
view = [self.delegate dataSource:self viewForFooterInSection:section]; | |
} | |
else { | |
view = [self dataSource:self viewForFooterInSection:section]; | |
} | |
if (view) { | |
return view.frame.size; | |
} | |
return CGSizeZero; | |
} | |
#pragma mark - UICollectionViewDelegate | |
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { | |
id object = [self objectAtIndexPath:indexPath]; | |
if ([self.delegate respondsToSelector:@selector(dataSource:didSelectCellWithOject:atIndexPath:)]) { | |
[self.delegate dataSource:self didSelectCellWithOject:object atIndexPath:indexPath]; | |
} | |
else { | |
[self dataSource:self didSelectCellWithOject:object atIndexPath:indexPath]; | |
} | |
} | |
#pragma mark - UICollectionViewFlowLayoutDelegate | |
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { | |
id object = [self objectAtIndexPath:indexPath]; | |
if ([self.delegate respondsToSelector:@selector(dataSource:collectionCellSizeWithObject:forIndexPath:)]) { | |
return [self.delegate dataSource:self collectionCellSizeWithObject:object forIndexPath:indexPath]; | |
} | |
else { | |
return [self dataSource:self collectionCellSizeWithObject:object forIndexPath:indexPath]; | |
} | |
} | |
@end | |
//Custom forwarding to allow extension of the delegate methods (scroll view specially) | |
#import <objc/runtime.h> | |
@implementation AGDataSource(ForwardInvocation) | |
#pragma clang diagnostic push | |
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation" | |
- (BOOL)respondsToSelector:(SEL)aSelector { | |
BOOL ret = [super respondsToSelector:aSelector]; | |
if (ret || self.delegate == self) { | |
return ret; | |
} | |
return [self methodCanBeForwardedToCustomDelegate:aSelector]; | |
} | |
#pragma clang diagnostic pop | |
- (id)forwardingTargetForSelector:(SEL)aSelector { | |
//Send to delegate first | |
if ([self methodCanBeForwardedToCustomDelegate:aSelector]) { | |
return self.delegate; | |
} | |
return self; | |
} | |
- (BOOL)methodCanBeForwardedToCustomDelegate:(SEL)aSelector { | |
// BOOL methodInProtocol = protocol_getMethodDescription(@protocol(AGDataSourceProtocol), aSelector, NO, YES).name != NULL; | |
return [self.delegate respondsToSelector:aSelector]; | |
} | |
@end | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@interface NSObject (AGCommands) | |
@property (nonatomic, assign) NSObject *nextCommandResponder; | |
- (BOOL)canPerformCommand:(AGCommand *)command; | |
- (BOOL)shouldPerformCommand:(AGCommand *)command; | |
- (NSObject *)targetForCommand:(AGCommand *)command; | |
- (BOOL)sendCommand:(AGCommand *)command; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "NSObject+AGCommands.h" | |
#import <objc/runtime.h> | |
@implementation NSObject (AGCommands) | |
#pragma mark - Public API | |
- (BOOL)canPerformCommand:(AGCommand *)command { | |
return [self respondsToSelector:command.action]; | |
} | |
- (BOOL)shouldPerformCommand:(AGCommand *)command { | |
return YES; | |
} | |
- (NSObject *)targetForCommand:(AGCommand *)command { | |
if (!command) return nil; | |
NSObject *implementor = self; | |
while (implementor) { | |
if ([implementor canPerformCommand:command] == YES) { | |
return implementor; | |
} | |
implementor = [implementor nextCommandResponder]; | |
} | |
return nil; | |
} | |
- (BOOL)sendCommand:(AGCommand *)command { | |
if (!command) return NO; | |
if (command.firingObject == nil) { | |
command.firingObject = self; | |
} | |
NSObject *target = [self targetForCommand:command]; | |
while (target) { | |
if ([target shouldPerformCommand:command]) { | |
[self performCommand:command onResponder:target]; | |
return YES; | |
} | |
target = [target.nextCommandResponder targetForCommand:command]; | |
} | |
return NO; | |
} | |
#pragma mark - Properties | |
static void *const kNextCommandResponderKey = (void *)&kNextCommandResponderKey; | |
- (NSObject *)nextCommandResponder { | |
NSObject *responder = objc_getAssociatedObject(self, kNextCommandResponderKey); | |
if (responder) return responder; | |
if ([self respondsToSelector:@selector(nextResponder)]) { | |
return [self performSelector:@selector(nextResponder)]; | |
} | |
return nil; | |
} | |
- (void)setNextCommandResponder:(NSObject *)object { | |
objc_setAssociatedObject(self, kNextCommandResponderKey, object, OBJC_ASSOCIATION_ASSIGN); | |
} | |
#pragma mark - Helpers | |
- (void)performCommand:(AGCommand *)command onResponder:(NSObject *)responder { | |
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[[responder class] instanceMethodSignatureForSelector:command.action]]; | |
[invocation setSelector:command.action]; | |
[invocation setTarget:responder]; | |
[invocation setArgument:&command atIndex:2]; | |
[invocation invoke]; | |
} | |
@end | |
@implementation UIViewController (AGCommands) | |
- (NSObject *)nextCommandResponder { | |
NSObject *nextResponder = [super nextCommandResponder]; | |
if (nextResponder) return nextResponder; | |
return self.navigationController; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi , angelolloqui .
I had download this gist and test it in a Demo. It seems there are some compile error: leak headers like UIKit.h etc.
And configureCell definition seems test for wrong protocol,does it mean AGCellProtocol?
if ([cell conformsToProtocol:@protocol(AGDataSource)]) {
[cell setObject:object];
}
else if ([cell respondsToSelector:@selector(textLabel)]){
[[cell textLabel] setText:[object description]];
}
}