【问题标题】:(iPhone) How to handle touches on a UITextView?(iPhone) 如何处理 UITextView 上的触摸?
【发布时间】:2010-10-11 14:41:44
【问题描述】:

我正在尝试处理 iPhone 的 UITextView 上的触摸。例如,通过创建 UIImageViews 的子类并实现 touchesBegan 方法,我成功地处理了点击和其他触摸事件......但是这显然不适用于 UITextView :(

UITextView 启用了用户交互和多点触控,只是为了确保......没有不开心。有人设法解决这个问题吗?

【问题讨论】:

    标签: iphone uitextview


    【解决方案1】:

    UITextView(UIScrollView 的子类)包含很多事件处理。它处理复制和粘贴以及数据检测器。也就是说,它可能是一个错误,它不会传递未处理的事件。

    有一个简单的解决方案:您可以继承 UITextView 并在自己的版本中实现自己的 touchesEnded(和其他事件处理消息),您应该在每个触摸处理方法中调用[super touchesBegan:touches withEvent:event];

    #import "MyTextView.h"  //MyTextView:UITextView
    @implementation MyTextView
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
        NSLog(@"touchesBegan");
    }
    
    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
            [super touchesBegan:touches withEvent:event];
        NSLog(@"touchesMoved");
    }
    
    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
        NSLog(@"****touchesEnded");
        [self.nextResponder touchesEnded: touches withEvent:event]; 
        NSLog(@"****touchesEnded");
        [super touchesEnded:touches withEvent:event];
        NSLog(@"****touchesEnded");
    }
    
    - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{
    [super touches... etc]; 
    NSLog(@"touchesCancelled");
    }
    

    【讨论】:

    • 你能详细说明一下吗?你的 touchesEnded 方法是否被调用过?
    • 已编辑以使其更清晰。 Ben - 子类 touchesEnded 被调用,然后有效地复制事件 - 一次到吞噬事件的超类 (UITextView),一次到下一个响应者。
    • 这对我来说仍然很奇怪。为什么在 touchesMoved 上调用 touchesBegan 超级?这不是意味着你会一遍又一遍地调用 touchesBegan 并且 touchesMoved 永远不会被 super 处理吗?
    • 嗨..我尝试做这件事,但在我的应用程序中没有调用方法,有人可以告诉我为什么吗?
    • 关闭 delayContentTouches。无论如何,默认的点击行为仍然会触发。
    【解决方案2】:

    如果您想在 UITextView 上处理单/双/三次点击,您可以委托 UIGestureRecongnizer 并在您的 textview 上添加手势识别器。

    这里有相同的代码(在 viewDidLoad 中):

    UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap)];
    
    //modify this number to recognizer number of tap
    [singleTap setNumberOfTapsRequired:1];
    [self.textView addGestureRecognizer:singleTap];
    [singleTap release];
    

    -(void)handleSingleTap{
       //handle tap in here 
       NSLog(@"Single tap on view");
    }
    

    希望有帮助:D

    【讨论】:

    • 即使我在 UITextView 上点击/触摸/拖动,该功能也不会触发。你确定它有效吗?
    • @ShivanRaptor 你在你的头文件中添加了 UIGestureRecognizerDelegate 吗?因为这正是我在我的应用程序中所做的并且它有效
    • 好的,我会仔细检查一下。委托是否有任何需要实现的功能?
    • UITextView 有自己的手势识别器,特别是有一个 (class=UITextTapRecognizer) 可以处理单击 (action=oneFingerTap:)。所以他们正在碰撞,这个UITextTapRecognizer 优先于你的UITapGestureRecognizer。在添加我自己的UITapGestureRecognizer 之前,我尝试删除UITextTapRecognizer,但UITextView 将再次重新创建它,并且我的处理程序永远不会被调用。就像 Apple 不希望我们自定义 UITextView 的单击行为一样。
    • @nacho4d 感谢您的想法。但是我能够使用 UIGestureRecognizer 处理对 TextView 的单击(单击以更改 textview 中文本的上下文)。
    【解决方案3】:

    更好的解决方案 (无需调整任何东西或使用任何私有 API :D)

    如下所述,将新的UITapGestureRecognizers 添加到文本视图不会产生预期的结果,处理程序方法永远不会被调用。那是因为UITextView 已经设置了一些轻击手势识别器,我认为他们的代理不允许我的手势识别器正常工作,我相信更改他们的代理可能会导致更糟糕的结果。

    幸运的是,UITextView 已经设置了我想要的手势识别器,问题是它会根据视图的状态而变化(即:输入日语时的手势识别器集合与输入英语时的手势识别器不同,当不输入时也不同处于编辑模式)。 我通过在 UITextView 的子类中覆盖这些来解决这个问题:

    - (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 &&
                [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 &&
                [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 ...
    }
    

    通过这种方式,无论 textview 何时更改其手势识别器,我们都会始终捕获我们想要的点击手势识别器 → 因此,我们的处理程序方法将被相应地调用:)

    结论: 如果你想给UITextView添加一个手势识别器,你必须检查文本视图是否已经有了它。

    • 如果没有,请按照常规方式进行。 (创建您的手势识别器,进行设置,然后将其添加到文本视图),您就完成了!
    • 如果它确实拥有它,那么您可能需要执行与上述类似的操作。



    旧答案

    我通过使用私有方法想出了这个答案,因为以前的答案有缺点,而且它们不能按预期工作。这里我并没有修改UITextView的点击行为,而是截取调用的方法,然后调用原来的方法。

    进一步说明

    UITextView 有一堆专门的UIGestureRecognizers,每个都有一个target 和一个action,但它们的target 不是UITextView 本身,它是前向类@987654333 的对象@。 (此助手是UITextView@package ivar,但前向定义在公共标头中:UITextField.h)。

    UITextTapRecognizer 识别轻击并在UITextInteractionAssistant 上调用oneFingerTap:,因此我们想要拦截该调用:)

    #import <objc/runtime.h>
    
    // 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) && (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));
    }
    

    【讨论】:

    • 我已经更新了我的答案:)。对于滑动手势,就像往常一样:r = [[UISwipeGestureRecognizer alloc] init... ];[textView addGestureRecognizer:r]; ...我希望它有所帮助
    • 新答案效果很好!我什至可以使用该技术将默认编辑从单击更改为双击,而无需添加手势识别器。
    【解决方案4】:

    您需要分配 UITextView instance.delegate = self(假设您要处理同一控制器中的事件)

    并确保在界面中实现 UITextViewDelegate 协议...例如:

    @interface myController : UIViewController <UITextViewDelegate>{
    }
    

    然后你可以实现以下任何一个

    
    - (BOOL)textViewShouldBeginEditing:(UITextView *)textView;
    - (BOOL)textViewShouldEndEditing:(UITextView *)textView;
    
    - (void)textViewDidBeginEditing:(UITextView *)textView;
    - (void)textViewDidEndEditing:(UITextView *)textView;
    
    - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text;
    - (void)textViewDidChange:(UITextView *)textView;
    
    - (void)textViewDidChangeSelection:(UITextView *)textView;
    
    

    【讨论】:

    • 这个答案没有解决正常 UITextView 吞下触摸事件的问题。
    • 我听从了这个建议。好消息是它运行良好。坏消息是,这导致了一些 EXC_BAD_ACCESS 确实花了我几天时间来确定它。我正在使用 ARC 并且 EXC_BAD_ACCESS 没有出现在我自己的代码正在执行中。在某些情况下,它没有出现几分钟,在大多数情况下,应用程序根本没有崩溃。我并不是说这个解决方案是错误的。该错误可能在我的实现范围内。但是,如果您想听从该建议,请尝试对该功能进行压力测试,并注意可能会在稍后发生的突然崩溃。
    【解决方案5】:

    我使用文本视图作为大视图的子视图。我需要用户能够滚动文本视图,但不能编辑它。我想检测对文本视图的超级视图的单击,包括文本视图本身。

    当然,我遇到了 textview 吞噬了从它开始的触摸的问题。禁用用户交互可以解决此问题,但用户将无法滚动文本视图。

    我的解决方案是让 textview 可编辑并使用 textview 的 shouldBeginEditing 委托方法来检测 textview 中的点击。我只是返回 NO,从而阻止编辑,但现在我知道 textview(以及超级视图)已被点击。在这个方法和 superview 的 touchesEnded 方法之间,我有我需要的。

    我知道这对想要获得实际触摸的人不起作用,但如果您只想检测点击,那么这种方法有效!

    【讨论】:

    • 这个答案接近解决方案,但在拖动过程中不起作用,这是文本视图用户通常执行的操作。
    • 在我的情况下,我有一个可编辑的 textView,一旦 textview 是第一响应者,我就无法捕捉到点击。我尝试过使用textViewDidChangeSelection:,但是在使用蓝牙键盘时也会调用它并给我误报。
    【解决方案6】:

    制作一个 UIScrollView 和 [scrollView addSubview: textview] 可以滚动 textview 怎么样?

    【讨论】:

      【解决方案7】:

      您也可以发送Touch Down 事件。通过 Interface Builder 连接此事件。

      然后在您的事件处理程序中添加代码

      - (IBAction)onAppIDTap:(id)sender {
          //Your code
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-21
        • 1970-01-01
        • 1970-01-01
        • 2014-03-19
        • 1970-01-01
        相关资源
        最近更新 更多