Skip to content

Instantly share code, notes, and snippets.

@angelolloqui
Last active April 26, 2016 06:17
Show Gist options
  • Save angelolloqui/88647fdfaae19f9d6c89 to your computer and use it in GitHub Desktop.
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, …
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
#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
#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
#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
@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
#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
@jerry-sl
Copy link

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?

  • (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]];
    }
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment