|
#import "JAScrollView.h" |
|
#import <objc/runtime.h> |
|
|
|
@interface JAScrollView () <UIScrollViewDelegate> |
|
{ |
|
__weak id<UIScrollViewDelegate> _externalDelegate; |
|
} |
|
|
|
@property(nonatomic) NSSet *delegateSelectorStrings; |
|
|
|
- (NSSet *)setOfSelectorStringsImplementedByProtocol:(Protocol *)protocol; |
|
|
|
@end |
|
|
|
|
|
// This class proxies UIScrollViewDelegate. |
|
// |
|
// * If this class implements a method in that protocol, the implementation here will be called. |
|
// * If this class doesn't implement a method in that protocol, the message will be forwarded to |
|
// _externalDelegate, automatically. |
|
// |
|
// !: For any UIScrollViewDelegate methods implemented here, it is imperative that, at some point in the |
|
// implementation, _externalDelegate is checked to respond to that selector and, if so, the method is |
|
// manually called on _externalDelegate. This ensures that all protocol methods will eventually pass through |
|
// to the externally set delegate. |
|
|
|
@implementation JAScrollView |
|
|
|
- (id)initWithFrame:(CGRect)frame |
|
{ |
|
if(self = [super initWithFrame:frame]) |
|
{ |
|
// set .delegateSelectorStrings, before assigning .delegate |
|
self.delegateSelectorStrings = [self setOfSelectorStringsImplementedByProtocol:@protocol(UIScrollViewDelegate)]; |
|
self.delegate = self; |
|
} |
|
|
|
return self; |
|
} |
|
|
|
|
|
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate |
|
{ |
|
// do mitm stuff here |
|
|
|
// check if _externalDelegate responds and, if so, manually forward the message |
|
if([_externalDelegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)]) |
|
{ |
|
[_externalDelegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; |
|
} |
|
} |
|
|
|
|
|
#pragma mark - UIScrollViewDelegate Proxying |
|
- (void)setDelegate:(id<UIScrollViewDelegate>)delegate |
|
{ |
|
[super setDelegate:self]; // Cocoa caches delegate capabilities so this triggers recaching |
|
if(delegate != self) _externalDelegate = delegate; |
|
} |
|
|
|
- (NSSet *)setOfSelectorStringsImplementedByProtocol:(Protocol *)protocol |
|
{ |
|
NSMutableSet *result = [NSMutableSet set]; |
|
|
|
unsigned int requiredInstanceCount = 0; |
|
unsigned int requiredClassCount = 0; |
|
unsigned int optionalInstanceCount = 0; |
|
unsigned int optionalClassCount = 0; |
|
struct objc_method_description *requiredInstance = protocol_copyMethodDescriptionList(protocol, YES, YES, &requiredInstanceCount); |
|
struct objc_method_description *requiredClass = protocol_copyMethodDescriptionList(protocol, YES, NO, &requiredClassCount); |
|
struct objc_method_description *optionalInstance = protocol_copyMethodDescriptionList(protocol, NO, YES, &optionalInstanceCount); |
|
struct objc_method_description *optionalClass = protocol_copyMethodDescriptionList(protocol, NO, NO, &optionalClassCount); |
|
|
|
for(int i = 0; i < requiredInstanceCount; i++) |
|
{ |
|
[result addObject:NSStringFromSelector(requiredInstance[i].name)]; |
|
} |
|
|
|
for(int i = 0; i < requiredClassCount; i++) |
|
{ |
|
[result addObject:NSStringFromSelector(requiredClass[i].name)]; |
|
} |
|
|
|
for(int i = 0; i < optionalInstanceCount; i++) |
|
{ |
|
[result addObject:NSStringFromSelector(optionalInstance[i].name)]; |
|
} |
|
|
|
for(int i = 0; i < optionalClassCount; i++) |
|
{ |
|
[result addObject:NSStringFromSelector(optionalClass[i].name)]; |
|
} |
|
|
|
free(requiredInstance); |
|
free(requiredClass); |
|
free(optionalInstance); |
|
free(optionalClass); |
|
|
|
return result; |
|
} |
|
|
|
- (BOOL)respondsToSelector:(SEL)aSelector |
|
{ |
|
if([self.delegateSelectorStrings containsObject:NSStringFromSelector(aSelector)]) |
|
{ |
|
if([_externalDelegate conformsToProtocol:@protocol(UIScrollViewDelegate)] && |
|
[_externalDelegate respondsToSelector:aSelector]) |
|
{ |
|
return YES; |
|
} |
|
} |
|
|
|
return [super respondsToSelector:aSelector]; |
|
} |
|
|
|
- (id)forwardingTargetForSelector:(SEL)aSelector |
|
{ |
|
if([self.delegateSelectorStrings containsObject:NSStringFromSelector(aSelector)]) |
|
{ |
|
return _externalDelegate; |
|
} |
|
|
|
return nil; // we shouldn't get down here |
|
} |
|
|
|
@end |