Касания на прозрачном UIControl игнорируются - не обрабатываются добавленными функциями действия

У меня есть собственный UIControl, содержащий еще несколько элементов управления. Между этими элементами управления есть пустое пространство, и фон моего UIControl должен быть прозрачным.

Мне нужно перехватить все события касания, которые происходят в моем пользовательском UIControl, даже если они происходят только между другими элементами управления (над прозрачными областями). Я не могу использовать распознаватели жестов, мне нужно больше контроля, чем они предоставляют. Вместо этого я хотел бы зарегистрировать такие функции обработки касаний:

myControl.addTarget(self, action: "handleTouchDown:event:", forControlEvents: UIControlEvents.TouchDown)

При таком подходе я получаю штрихи, которые случаются с непрозрачными областями myControl, но не те, которые происходят на прозрачном фоне.

Я попытался переопределить hitTest::point:withEvent в моем пользовательском элементе управления, чтобы не проверять альфа-значение. Но hitTest::point:withEvent даже не вызывается, когда прикосновение происходит к прозрачной области управления. Я заменил свой элемент управления layer на пользовательский CALayer и переопределил hitTest и для него, но без результата (hitTest на уровне, похоже, не вызывается вообще).


Подробнее (РЕДАКТИРОВАТЬ)

Все, что вам нужно сделать, чтобы дать идеальный ответ (и выиграть награду), это:

  1. Создайте простое приложение, добавьте одно UIControl (например, UIButton).
  2. Удалите все содержимое из UIControl (текст из UIButton) и сделайте его фон прозрачным (либо установите чистый цвет, либо установите альфа-канал на 0).
  3. Используйте метод addTarget::action:forControlEvents: для регистрации UIControlEvents.TouchDown событий в элементе управления. В методе обработчика выведите что-нибудь на консоль.
  4. Запускаем приложение, нажимаем элемент управления. На консоль ничего не выводится. Сделайте так, чтобы это работало - не используйте распознаватели жестов. Мне нужна детализация, обеспечиваемая addTarget::action:forControlEvents:. Никакие хакерские решения не предпочтительны. Я знаю, что установка фонового альфа-канала на элементе управления на 0.01 заставит его работать внезапно, но это своего рода хакерство, которого я не хочу. Опишите здесь, что вы сделали.

person Rasto    schedule 30.10.2014    source источник
comment
Если я установлю цвет фона моего UIControl на какой-то цвет с альфа-каналом 0.01, все будет работать нормально, я получу все события касания, которые мне нужны. Но это взлом, и он влияет на цвета представлений, которые находятся за этим прозрачным элементом управления.   -  person Rasto    schedule 30.10.2014
comment
Вы пробовали view.backgroundColor = [UIColor clearColor]?   -  person Michael    schedule 30.10.2014
comment
@Nikita Да, действительно. Это не помогает.   -  person Rasto    schedule 30.10.2014
comment
Вы уверены, что ваш UIControl находится на нужном месте в иерархии представлений? Проверьте порядок иерархии представлений.   -  person Michael    schedule 30.10.2014
comment
@Nikita Если бы это было с другого ракурса, я бы не получил никаких прикосновений к непрозрачным областям. Но я получаю штрихи для непрозрачных участков. Это не какое-то странное поведение - обычно элементы управления ведут себя так. Попробуйте простой проект, поместите несколько UIControl, чтобы сделать его полностью прозрачным, и попробуйте addTarget зарегистрироваться для некоторых штрихов. Она не будет работать. Просто глупо по замыслу ...   -  person Rasto    schedule 30.10.2014
comment
Я пробовал использовать UIControl и заметил ту же проблему. Я также пробовал hitTest:Point:withEvent согласно this SO post, он также не работал должным образом. Возможно, вы можете установить белый цвет фона и очень низкое значение альфа-канала? Или даже попробовать UIButton?   -  person Michael    schedule 30.10.2014
comment
@Nikita Этот пост не имеет отношения к моей проблеме. У меня нет закрывающего представления - представьте, что в иерархии есть только одно прозрачное представление, и я хочу обнаруживать прикосновения к нему.   -  person Rasto    schedule 30.10.2014
comment
С помощью метода делегата распознавателя жестов - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch вы можете точно указать, где жест будет передан распознавателю. Не могли бы вы указать только вид вашего элемента управления для получения касания? (и откажитесь от касания, если оно касается одного из ваших субконтроллеров.)   -  person Tim Quinn    schedule 31.10.2014
comment
@TimQuinn В заголовке моего вопроса сказано, что решение может не нуждаться в распознавателях жестов (причину см. В редактировании).   -  person Rasto    schedule 02.11.2014


Ответы (3)


После раздела ИЗМЕНИТЬ:

https://github.com/soxjke/TransparentControl

1) Если я установил цвет фона на +[UIColor clearColor], штрихи будут работать замечательно. Так что вам не нужно больше ничего делать, используйте чистый цвет. (верхняя кнопка)
2) Если я установил альфа = 0, касания не обрабатываются. ОК (средняя кнопка)
3) Чтобы справиться с этим, есть простое решение (нижняя кнопка), подкласс UIButton (на самом деле вы можете использовать что угодно в иерархии до UIView). Заменить hitTest:withEvent:

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    return CGRectContainsPoint(self.bounds, point) ? self : nil;
}

ПРИБЫЛЬ
4) Если вам нужно углубиться, используйте

touchesBegan:withEvent:
touchesMoved:withEvent:
touchesEnded:withEvent:
touchesCancelled:withEvent:

в вашем подклассе UIResponder, например Роб Глесси, предложенный в его ответ

P.S. Я закончу не по теме. Не знаю, какова ваша задача на самом деле, но сообщение о том, что вы не можете использовать распознаватели, потому что вам нужен «больший контроль» над всеми событиями касания, обнаруживает, что вы не знаете возможностей распознавателей жестов. Итак, исходя из своего опыта, я бы сказал, что вы скорее изобретаете велосипед, чем придумываете хорошее решение.

P.P.S. Если предложенные мной и другими ребятами методы вам не подходят - проверьте userInteractionEnabled вашего контроля

Приложение (посмотрите код контроллера для тестирования :):

#import "ViewController.h"
#import "TransparentControl.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UIButton *buttonClearColor;
@property (weak, nonatomic) IBOutlet UIButton *buttonAlpha0;
@property (weak, nonatomic) IBOutlet TransparentControl *customButtonAlpha0;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.buttonClearColor addTarget:self action:@selector(touchUpInside:) forControlEvents:UIControlEventTouchUpInside];
    [self.buttonClearColor addTarget:self action:@selector(touchUpOutside:) forControlEvents:UIControlEventTouchUpOutside];
    [self.buttonClearColor addTarget:self action:@selector(touchDown:) forControlEvents:UIControlEventTouchDown];

    [self.buttonAlpha0 addTarget:self action:@selector(touchUpInside:) forControlEvents:UIControlEventTouchUpInside];
    [self.buttonAlpha0 addTarget:self action:@selector(touchUpOutside:) forControlEvents:UIControlEventTouchUpOutside];
    [self.buttonAlpha0 addTarget:self action:@selector(touchDown:) forControlEvents:UIControlEventTouchDown];

    [self.customButtonAlpha0 addTarget:self action:@selector(touchUpInside:) forControlEvents:UIControlEventTouchUpInside];
    [self.customButtonAlpha0 addTarget:self action:@selector(touchUpOutside:) forControlEvents:UIControlEventTouchUpOutside];
    [self.customButtonAlpha0 addTarget:self action:@selector(touchDown:) forControlEvents:UIControlEventTouchDown];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}

- (void)touchUpInside:(id)sender
{
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

- (void)touchDown:(id)sender
{
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

- (void)touchUpOutside:(id)sender
{
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

@end
person Petro Korienev    schedule 02.11.2014

В документации для hitTest упоминаются полностью прозрачные вещи, которые игнорируются, поэтому вполне возможно, что переопределения самого hitTest недостаточно, чтобы обойти это, поскольку любые вызовы hitTest не вызывают его, когда объект прозрачен.

Вместо этого я предлагаю вам попробовать перейти к методам касания UIResponder нижнего уровня, если вам нужно получить доступ к необработанным событиям касания, несмотря ни на что (они наследуются UIView и UIControl, поэтому они доступны вам).

Они есть:

touchesBegan:withEvent:
touchesMoved:withEvent:
touchesEnded:withEvent:
touchesCancelled:withEvent:

Первый параметр - это NSSet касаний, второй - UIEvent, как то, на что вы ссылались в других своих методах ...

При этом вам не нужно добавлять целевое действие, но вы переопределяете эти методы в своем настраиваемом элементе управления. Это более низкий уровень (и в высшей степени олдскульный), но они должны дать вам полный контроль над событиями касания.

person Rob Glassey    schedule 01.11.2014
comment
Спасибо за ответ. Я тоже пробовал это. UIResponder методы касания (touchesBegan:withEvent: и т. Д.) Тоже не вызываются (!) На прозрачных UIControl. При падении до UIResponder это хорошая идея, но, к сожалению, она не сработает. - person Rasto; 02.11.2014
comment
В этом случае, я думаю, проблема может заключаться в блокировке прикосновений указанными выше элементами управления (где вы говорите «содержит несколько других элементов управления»)? Они справляются с собственными прикосновениями? Это все еще не удается, если у вас есть только один фоновый элемент управления (удаление этих других элементов управления)? Следует обратить внимание на то, что Петро упомянул в своем ответе о userInteractionEnabled - это очень просто для представлений любого вида выше чтобы блокировать касания, даже если эти представления прозрачны, если они не совсем правильно настроены. - person Rob Glassey; 02.11.2014

Я создал подкласс UIControl с помощью пустого drawRect метода, и он сработал.

Согласно документации, opaque игнорируется UIButton и некоторыми другими элементами управления, поэтому его нельзя использовать в качестве контрольной точки для этого метода. Любопытно, однако, что цвет фона по умолчанию для представления прозрачный (ноль).

Создав подкласс UIControl и установив opaque = NO, вы можете создать drawRect метод, который не полностью заполняет фрейм и допускает "прозрачные" области без установки alpha = 0, позволяющей hitTest:withEvent: по-прежнему собирать события. Поскольку элементом является UIView, вы должны иметь возможность добавлять представления, а затем реализовывать свои собственные drawRect, которые вызывают все эквивалентные функции подпредставлений, не отрисовывая области, которые должны быть прозрачными.

Мои основные элементы ViewController, ImageView, должны были убедиться, что он работает.

@implementation MyViewController
- (void)viewDidLoad {
    [ super viewDidLoad ];

    transparentControl = [ [ TransparentControl alloc ] initWithFrame:CGRectMake( 0, 0, 400, 400 ) ];
    [ transparentControl addTarget:self action:@selector(printText) forControlEvents:UIControlEventTouchUpInside];

    // Create an image view below the button for proof the control is transparent
    UIImageView * imageView = [ [ UIImageView alloc ] initWithImage:[ UIImage imageNamed:@"BGImage.jpg" ] ];
    imageView.frame = self.view.frame;

    [ self.view addSubview:imageView ];
    [ self.view addSubview:transparentControl ];
}

 -( void )printText {
    NSLog( @"Hello, this is a transparent button." );
}
@end

И мой прозрачный контроль.

@implementation TransparentControl

- ( instancetype )initWithFrame:( CGRect )frame {
    if( self = [ super initWithFrame:frame ] ) {
        self.opaque = NO;
        self.userInteractionEnabled = YES;
    }
    return self;
}

- ( void )drawRect:(CGRect)rect {
}
@end
person pyj    schedule 01.11.2014