Skip to content

Instantly share code, notes, and snippets.

@jerry-sl
Forked from angelolloqui/AGCommand.h
Last active August 29, 2015 14:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jerry-sl/a65ab38524a9cc537f20 to your computer and use it in GitHub Desktop.
Save jerry-sl/a65ab38524a9cc537f20 to your computer and use it in GitHub Desktop.
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment