好的,我已将上述内容转换为有效的类别。
有趣的位:
- 类别不能添加成员变量。你添加的任何东西都会变成静态的类,因此会被 Apple 的许多 UITapGestureRecognizers 破坏。
- 因此,使用 associated_object 来实现魔法。
- NSValue 用于存储非对象
- 苹果的
init
方法包含重要的配置逻辑;我们可以猜测设置了什么(点击次数,触摸次数,还有什么?
- 但这是注定的。因此,我们在保留模拟的 init 方法中进行调配。
头文件很简单;这是实现。
#import "UITapGestureRecognizer+Spec.h"
#import "objc/runtime.h"
/*
* With great contributions from Matt Gallagher (http://www.cocoawithlove.com/2008/10/synthesizing-touch-event-on-iphone.html)
* And Glauco Aquino (http://stackoverflow.com/users/2276639/glauco-aquino)
* And Codeshaker (http://codeshaker.blogspot.com/2012/01/calling-original-overridden-method-from.html)
*/
@interface UITapGestureRecognizer (SpecPrivate)
@property (strong, nonatomic, readwrite) UIView *mockTappedView_;
@property (assign, nonatomic, readwrite) CGPoint mockTappedPoint_;
@property (strong, nonatomic, readwrite) id mockTarget_;
@property (assign, nonatomic, readwrite) SEL mockAction_;
@end
NSString const *MockTappedViewKey = @"MockTappedViewKey";
NSString const *MockTappedPointKey = @"MockTappedPointKey";
NSString const *MockTargetKey = @"MockTargetKey";
NSString const *MockActionKey = @"MockActionKey";
@implementation UITapGestureRecognizer (Spec)
// It is necessary to call the original init method; super does not set appropriate variables.
// (eg, number of taps, number of touches, gods know what else)
// Swizzle our own method into its place. Note that Apple misspells 'swizzle' as 'exchangeImplementation'.
+(void)load {
method_exchangeImplementations(class_getInstanceMethod(self, @selector(initWithTarget:action:)),
class_getInstanceMethod(self, @selector(initWithMockTarget:mockAction:)));
}
-(id)initWithMockTarget:(id)target mockAction:(SEL)action {
self = [self initWithMockTarget:target mockAction:action];
self.mockTarget_ = target;
self.mockAction_ = action;
self.mockTappedView_ = nil;
return self;
}
-(UIView *)view {
return self.mockTappedView_;
}
-(CGPoint)locationInView:(UIView *)view {
return [view convertPoint:self.mockTappedPoint_ fromView:self.mockTappedView_];
}
//-(UIGestureRecognizerState)state {
// return UIGestureRecognizerStateEnded;
//}
-(void)performTapWithView:(UIView *)view andPoint:(CGPoint)point {
self.mockTappedView_ = view;
self.mockTappedPoint_ = point;
// warning because a leak is possible because the compiler can't tell whether this method
// adheres to standard naming conventions and make the right behavioral decision. Suppress it.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.mockTarget_ performSelector:self.mockAction_];
#pragma clang diagnostic pop
}
# pragma mark - Who says we can't add members in a category?
- (void)setMockTappedView_:(UIView *)mockTappedView {
objc_setAssociatedObject(self, &MockTappedViewKey, mockTappedView, OBJC_ASSOCIATION_ASSIGN);
}
-(UIView *)mockTappedView_ {
return objc_getAssociatedObject(self, &MockTappedViewKey);
}
- (void)setMockTappedPoint_:(CGPoint)mockTappedPoint {
objc_setAssociatedObject(self, &MockTappedPointKey, [NSValue value:&mockTappedPoint withObjCType:@encode(CGPoint)], OBJC_ASSOCIATION_COPY);
}
- (CGPoint)mockTappedPoint_ {
NSValue *value = objc_getAssociatedObject(self, &MockTappedPointKey);
CGPoint aPoint;
[value getValue:&aPoint];
return aPoint;
}
- (void)setMockTarget_:(id)mockTarget {
objc_setAssociatedObject(self, &MockTargetKey, mockTarget, OBJC_ASSOCIATION_ASSIGN);
}
- (id)mockTarget_ {
return objc_getAssociatedObject(self, &MockTargetKey);
}
- (void)setMockAction_:(SEL)mockAction {
objc_setAssociatedObject(self, &MockActionKey, NSStringFromSelector(mockAction), OBJC_ASSOCIATION_COPY);
}
- (SEL)mockAction_ {
NSString *selectorString = objc_getAssociatedObject(self, &MockActionKey);
return NSSelectorFromString(selectorString);
}
@end