Лучшее решение (без мошенничества и без использования какого-либо частного 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 ...
}
Поступая таким образом, независимо от того, когда текстовое представление меняет свои распознаватели жестов, мы всегда будем ловить желаемый распознаватель жестов касания → Следовательно, наш метод-обработчик будет вызываться соответственно:)
Вывод:
Если вы хотите добавить распознаватели жестов к UITextView
, вам нужно проверить, что текстовое представление еще не имело.
- Если у него его нет, просто делай как обычно. (Создайте свой распознаватель жестов, настройте его и добавьте в текстовое представление) и все готово!.
- Если он имеет , то вам, вероятно, нужно сделать что-то подобное, как указано выше.
<ч />
Старый ответ
Я придумал этот ответ: мошенническим приватным методом , потому что в предыдущих ответах были недостатки, и они не работают, как ожидалось. Здесь вместо того, чтобы изменять поведение при нажатии UITextView
, я просто перехватываю вызываемый метод и затем вызываю оригинальный метод.
Дополнительные пояснения
UITextView
имеет кучу специализированных UIGestureRecognizers
, каждый из них имеет target
и action
, но их target
не сам UITextView
, это объект передового класса UITextInteractionAssistant
. (Этот помощник является @package
иваром UITextView
, но прямое определение находится в общедоступном заголовке: UITextField.h).
UITextTapRecognizer
распознает нажатия и звонки oneFingerTap:
на UITextInteractionAssistant
, поэтому мы хотим перехватить этот вызов:)
#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));
}