Skip to content

Instantly share code, notes, and snippets.

@Goos
Last active August 29, 2015 14:02
Show Gist options
  • Save Goos/dc7e2bbf33341877787f to your computer and use it in GitHub Desktop.
Save Goos/dc7e2bbf33341877787f to your computer and use it in GitHub Desktop.
A category for dynamically overriding -hitTest:withEvent: on UIView, in order to enable interaction with views during animations.
//
// UIView+EnableUserInteraction.h
// Pods
//
// Created by Robin Goos on 06/06/14.
//
//
#import <UIKit/UIKit.h>
@interface UIView (PresentationLayerHitTesting)
- (void)setHitTestUsingPresentationLayer:(BOOL)enabled;
- (BOOL)hitTestUsingPresentationLayer;
@end
//
// UIView+PresentationLayerHitTesting.m
// Pods
//
// Created by Robin Goos on 06/06/14.
//
//
#import "UIView+PresentationLayerHitTesting.h"
#import <objc/runtime.h>
#import <pthread.h>
NSMutableDictionary *dynamicSubclassesDictionary() {
static NSMutableDictionary *dynamicSubclassDictionary = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dynamicSubclassDictionary = [NSMutableDictionary dictionary];
});
return dynamicSubclassDictionary;
}
NS_INLINE NSString *dynamicSubclassNameForClass(Class cl) {
return [NSString stringWithFormat:@"%@_UsingPresentationLayer", NSStringFromClass(cl)];
}
NS_INLINE Class getDynamicSubclassForClass(Class cl) {
NSMutableDictionary *dynamicSubclasses = dynamicSubclassesDictionary();
Class dynamicSubclass = nil;
@synchronized(dynamicSubclasses) {
dynamicSubclass = dynamicSubclasses[NSStringFromClass(cl)];
}
return dynamicSubclass;
}
NS_INLINE void addDynamicSubclassForClass(Class subclass, Class cl) {
NSMutableDictionary *dynamicSubclasses = dynamicSubclassesDictionary();
dynamicSubclasses[NSStringFromClass(cl)] = subclass;
}
NS_INLINE void swizzleMethodForClass(Class cls, Method method, IMP newImp) {
if (!class_addMethod(cls, method_getName(method), newImp, method_getTypeEncoding(method))) {
// class already has implementation, swizzle it instead
method_setImplementation(method, newImp);
}
}
NS_INLINE Class createDynamicSubclassForClass(Class cl) {
@synchronized(dynamicSubclassesDictionary()) {
Class swizzledClass = objc_allocateClassPair(cl, dynamicSubclassNameForClass(cl).UTF8String, 0);
objc_registerClassPair(swizzledClass);
// Swizzling the 'class'-method to return the original class type
Method m = class_getInstanceMethod(swizzledClass, @selector(class));
id swizzledClassBlock = ^Class (__unsafe_unretained id self) {
return cl;
};
swizzleMethodForClass(swizzledClass, m, imp_implementationWithBlock(swizzledClassBlock));
addDynamicSubclassForClass(swizzledClass, cl);
return swizzledClass;
}
}
NS_INLINE void swizzleHitTestForView(UIView *view) {
Class dynamicSubclass = getDynamicSubclassForClass(view.class);
// View already
if (dynamicSubclass) {
object_setClass(view, dynamicSubclass);
return;
}
dynamicSubclass = createDynamicSubclassForClass(view.class);
SEL selector = @selector(hitTest:withEvent:);
Method hitTestMethod = class_getInstanceMethod(view.class, selector);
IMP hitTestImp = method_getImplementation(hitTestMethod);
IMP newHitTestImplementation = imp_implementationWithBlock(^UIView *(UIView *view, CGPoint point, UIEvent *event) {
CGPoint presentationLayerPoint = point;
if (view.hitTestUsingPresentationLayer) {
// translate point to presentation layer
presentationLayerPoint = [view.layer.presentationLayer convertPoint:point fromLayer:view.layer];
}
// Call the original implementation with the translated origin
return ((UIView *(*)(void *, SEL, CGPoint, UIEvent *)) hitTestImp)((__bridge void *)(view), selector, presentationLayerPoint, event);
});
swizzleMethodForClass(dynamicSubclass, hitTestMethod, newHitTestImplementation);
object_setClass(view, dynamicSubclass);
}
@implementation UIView (PresentationLayerHitTesting)
- (void)setHitTestUsingPresentationLayer:(BOOL)enabled
{
SEL selector = @selector(hitTestUsingPresentationLayer);
id previousVal = objc_getAssociatedObject(self, selector);
objc_setAssociatedObject(self, selector, @(enabled), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (!previousVal) {
swizzleHitTestForView(self);
}
}
- (BOOL)hitTestUsingPresentationLayer
{
NSNumber *boxedVal = objc_getAssociatedObject(self, _cmd);
return (boxedVal) ? boxedVal.boolValue : NO;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment