UITapGestureRecognizer Programmatically trigger a tap in my view

前端 未结 8 1104
耶瑟儿~
耶瑟儿~ 2020-12-09 10:05

Edit: Updated to make question more obvious

Edit 2: Made question more accurate to my real-world problem. I\'m actually looking to take a

8条回答
  •  北荒
    北荒 (楼主)
    2020-12-09 10:20

    Okay, I've turned the above into a category that works.

    Interesting bits:

    • Categories can't add member variables. Anything you add becomes static to the class and thus is clobbered by Apple's many UITapGestureRecognizers.
      • So, use associated_object to make the magic happen.
      • NSValue for storing non-objects
    • Apple's init method contains important configuration logic; we could guess at what is set (number of taps, number of touches, what else?
      • But this is doomed. So, we swizzle in our init method that preserves the mocks.

    The header file is trivial; here's the implementation.

    #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
    

提交回复
热议问题