【发布时间】:2010-10-11 14:41:44
【问题描述】:
我正在尝试处理 iPhone 的 UITextView 上的触摸。例如,通过创建 UIImageViews 的子类并实现 touchesBegan 方法,我成功地处理了点击和其他触摸事件......但是这显然不适用于 UITextView :(
UITextView 启用了用户交互和多点触控,只是为了确保......没有不开心。有人设法解决这个问题吗?
【问题讨论】:
标签: iphone uitextview
我正在尝试处理 iPhone 的 UITextView 上的触摸。例如,通过创建 UIImageViews 的子类并实现 touchesBegan 方法,我成功地处理了点击和其他触摸事件......但是这显然不适用于 UITextView :(
UITextView 启用了用户交互和多点触控,只是为了确保......没有不开心。有人设法解决这个问题吗?
【问题讨论】:
标签: iphone uitextview
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");
}
【讨论】:
如果您想在 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 有自己的手势识别器,特别是有一个 (class=UITextTapRecognizer) 可以处理单击 (action=oneFingerTap:)。所以他们正在碰撞,这个UITextTapRecognizer 优先于你的UITapGestureRecognizer。在添加我自己的UITapGestureRecognizer 之前,我尝试删除UITextTapRecognizer,但UITextView 将再次重新创建它,并且我的处理程序永远不会被调用。就像 Apple 不希望我们自定义 UITextView 的单击行为一样。
更好的解决方案 (无需调整任何东西或使用任何私有 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]; ...我希望它有所帮助
您需要分配 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;
【讨论】:
我使用文本视图作为大视图的子视图。我需要用户能够滚动文本视图,但不能编辑它。我想检测对文本视图的超级视图的单击,包括文本视图本身。
当然,我遇到了 textview 吞噬了从它开始的触摸的问题。禁用用户交互可以解决此问题,但用户将无法滚动文本视图。
我的解决方案是让 textview 可编辑并使用 textview 的 shouldBeginEditing 委托方法来检测 textview 中的点击。我只是返回 NO,从而阻止编辑,但现在我知道 textview(以及超级视图)已被点击。在这个方法和 superview 的 touchesEnded 方法之间,我有我需要的。
我知道这对想要获得实际触摸的人不起作用,但如果您只想检测点击,那么这种方法有效!
【讨论】:
textViewDidChangeSelection:,但是在使用蓝牙键盘时也会调用它并给我误报。
制作一个 UIScrollView 和 [scrollView addSubview: textview] 可以滚动 textview 怎么样?
【讨论】:
您也可以发送Touch Down 事件。通过 Interface Builder 连接此事件。
然后在您的事件处理程序中添加代码
- (IBAction)onAppIDTap:(id)sender {
//Your code
}
【讨论】: