Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p><strong>Better solution</strong> <em>(Without swizzling anything or using any Private API :D )</em></p> <p>As explained below, adding new <code>UITapGestureRecognizers</code> to the textview does not have the expected results, handler methods are never called. That is because the <code>UITextView</code> has some tap gesture recognizer setup already and I think their delegate does not allow my gesture recognizer to <em>work</em> properly and changing their delegate could lead to even worse results, I believe.</p> <p>Luckily the <code>UITextView</code> has the gesture recognizer I want already setup, the problem is that it changes according to the state of the view (i.e.: set of gesture recognizers are different when inputing Japanese than when inputing English and also when not being in editing mode). I solved this by overriding these in a subclass of UITextView:</p> <pre><code>- (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer { [super addGestureRecognizer:gestureRecognizer]; // Check the new gesture recognizer is the same kind as the one we want to implement // Note: // This works because `UITextTapRecognizer` is a subclass of `UITapGestureRecognizer` // and the text view has some `UITextTapRecognizer` added :) if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) { UITapGestureRecognizer *tgr = (UITapGestureRecognizer *)gestureRecognizer; if ([tgr numberOfTapsRequired] == 1 &amp;&amp; [tgr numberOfTouchesRequired] == 1) { // If found then add self to its targets/actions [tgr addTarget:self action:@selector(_handleOneFingerTap:)]; } } } - (void)removeGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer { // Check the new gesture recognizer is the same kind as the one we want to implement // Read above note if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) { UITapGestureRecognizer *tgr = (UITapGestureRecognizer *)gestureRecognizer; if ([tgr numberOfTapsRequired] == 1 &amp;&amp; [tgr numberOfTouchesRequired] == 1) { // If found then remove self from its targets/actions [tgr removeTarget:self action:@selector(_handleOneFingerTap:)]; } } [super removeGestureRecognizer:gestureRecognizer]; } - (void)_handleOneFingerTap:(UITapGestureRecognizer *)tgr { NSDictionary *userInfo = [NSDictionary dictionaryWithObject:tgr forKey:@"UITapGestureRecognizer"]; [[NSNotificationCenter defaultCenter] postNotificationName:@"TextViewOneFingerTapNotification" object:self userInfo:userInfo]; // Or I could have handled the action here directly ... } </code></pre> <p>By doing this way, no matter when the textview changes its gesture recognizers, we will always catch the tap gesture recognizer we want → Hence, our handler method will be called accordingly :)</p> <p><strong>Conclusion:</strong> If you want to add a gesture recognizers to the <code>UITextView</code>, you have to check the text view does not have it already.</p> <ul> <li>If it does not have it, just do the regular way. (Create your gesture recognizer, set it up, and add it to the text view) and you are done!.</li> <li>If it <em>does</em> have it, then you probably need to do something similar as above.</li> </ul> <p><br /> <br /></p> <hr /> <p><strong>Old Answer</strong></p> <p>I came up with this answer by <strong><em>swizzling a private method</em></strong> because previous answers have cons and they don't work as expected. Here, rather than modifying the tapping behavior of the <code>UITextView</code>, I just intercept the called method and then call the original method.</p> <p><strong>Further Explanation</strong></p> <p><code>UITextView</code> has a bunch of specialized <code>UIGestureRecognizers</code>, each of these has a <code>target</code> and a <code>action</code> but their <code>target</code> is not the <code>UITextView</code> itself, it's an object of the forward class <code>UITextInteractionAssistant</code>. (This assistant is a <code>@package</code> ivar of <code>UITextView</code> but is forward definition is in the public header: UITextField.h).</p> <p><code>UITextTapRecognizer</code> recognizes taps and calls <code>oneFingerTap:</code> on the <code>UITextInteractionAssistant</code> so we want to intercept that call :)</p> <pre><code>#import &lt;objc/runtime.h&gt; // Prototype and declaration of method that is going be swizzled // When called: self and sender are supposed to be UITextInteractionAssistant and UITextTapRecognizer objects respectively void proxy_oneFingerTap(id self, SEL _cmd, id sender); void proxy_oneFingerTap(id self, SEL _cmd, id sender){ [[NSNotificationCenter defaultCenter] postNotificationName:@"TextViewOneFinderTap" object:self userInfo:nil]; if ([self respondsToSelector:@selector(proxy_oneFingerTap:)]) { [self performSelector:@selector(proxy_oneFingerTap:) withObject:sender]; } } ... // subclass of UITextView // Add above method and swizzle it with. - (void)doTrickForCatchingTaps { Class class = [UITextInteractionAssistant class]; // or below line to avoid ugly warnings //Class class = NSClassFromString(@"UITextInteractionAssistant"); SEL new_selector = @selector(proxy_oneFingerTap:); SEL orig_selector = @selector(oneFingerTap:); // Add method dynamically because UITextInteractionAssistant is a private class BOOL success = class_addMethod(class, new_selector, (IMP)proxy_oneFingerTap, "v@:@"); if (success) { Method originalMethod = class_getInstanceMethod(class, orig_selector); Method newMethod = class_getInstanceMethod(class, new_selector); if ((originalMethod != nil) &amp;&amp; (newMethod != nil)){ method_exchangeImplementations(originalMethod, newMethod); // Method swizzle } } } //... And in the UIViewController, let's say [textView doTrickForCatchingTaps]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textViewWasTapped:) name:@"TextViewOneFinderTap" object:nil]; - (void)textViewWasTapped:(NSNotification *)noti{ NSLog(@"%@", NSStringFromSelector:@selector(_cmd)); } </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload