Возможно ли, чтобы UIButton (или любой другой элемент управления) получал события касания, когда кадр UIButton находится вне его родительского кадра? Причина, когда я пытаюсь это сделать, мой UIButton, похоже, не может получать какие-либо события. Как мне обойти это?
Взаимодействие за пределами UIView
Ответ 1
Да. Вы можете переопределить метод hitTest:withEvent:
, чтобы вернуть представление для большего набора точек, чем в этом представлении. См. Ссылка на класс UIView.
Изменить: Пример:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
CGFloat radius = 100.0;
CGRect frame = CGRectMake(-radius, -radius,
self.frame.size.width + radius,
self.frame.size.height + radius);
if (CGRectContainsPoint(frame, point)) {
return self;
}
return nil;
}
Изменить 2: (После уточнения:) Чтобы гарантировать, что кнопка рассматривается как находящаяся в пределах родительских границ, вам необходимо переопределить pointInside:withEvent:
в родительском объекте, чтобы включить рамку кнопки.
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if (CGRectContainsPoint(self.view.bounds, point) ||
CGRectContainsPoint(button.view.frame, point))
{
return YES;
}
return NO;
}
Примечание код для переопределения pointInside не совсем корректен. Как объясняет Summon ниже, сделайте следующее:
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if ( CGRectContainsPoint(self.oversizeButton.frame, point) )
return YES;
return [super pointInside:point withEvent:event];
}
Обратите внимание, что вы, скорее всего, сделаете это с помощью self.oversizeButton
как IBOutlet в этом подклассе UIView; то вы можете просто перетащить кнопку "негабаритного типа", о которой идет речь, к специальному виду, о котором идет речь. (Или, если по какой-то причине вы делали это много в проекте, у вас был бы специальный подкласс UIButton, и вы могли бы просмотреть свой список подпрограмм для этих классов.) Надеюсь, что это поможет.
Ответ 2
@jnic, я работаю над iOS SDK 5.0 и для того, чтобы правильно работать с вашим кодом, я должен был сделать это:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
if (CGRectContainsPoint(button.frame, point)) {
return YES;
}
return [super pointInside:point withEvent:event]; }
В моем случае контейнер представляет собой UIButton, и все дочерние элементы также являются UIButtons, которые могут перемещаться за пределы родительского UIButton.
Лучшие
Ответ 3
В родительском представлении вы можете переопределить метод тестового теста:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
CGPoint translatedPoint = [_myButton convertPoint:point fromView:self];
if (CGRectContainsPoint(_myButton.bounds, translatedPoint)) {
return [_myButton hitTest:translatedPoint withEvent:event];
}
return [super hitTest:point withEvent:event];
}
В этом случае, если точка попадает в пределы вашей кнопки, вы переадресовываете ее туда; если нет, верните исходную реализацию.
Ответ 4
В моем случае у меня был подкласс UICollectionViewCell
, который содержал UIButton
. Я отключил clipsToBounds
для ячейки, и кнопка была видна за пределами ячейки. Однако кнопка не получала сенсорные события. Мне удалось обнаружить сенсорные события на кнопке с помощью ответа Джека: fooobar.com/questions/81380/...
Вот версия Swift:
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
let translatedPoint = button.convertPoint(point, fromView: self)
if (CGRectContainsPoint(button.bounds, translatedPoint)) {
print("Your button was pressed")
return button.hitTest(translatedPoint, withEvent: event)
}
return super.hitTest(point, withEvent: event)
}
Swift 4:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let translatedPoint = button.convert(point, from: self)
if (button.bounds.contains(translatedPoint)) {
print("Your button was pressed")
return button.hitTest(translatedPoint, with: event)
}
return super.hitTest(point, with: event)
}
Ответ 5
Swift:
Где targetView - это представление, которое вы хотите получить, которое может быть полностью или частично расположено вне рамки его родительского представления.
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
let pointForTargetView: CGPoint = targetView.convertPoint(point, fromView: self)
if CGRectContainsPoint(targetView.bounds, pointForTargetView) {
return closeButton
}
return super.hitTest(point, withEvent: event)
}
Ответ 6
Почему это происходит?
Это происходит потому, что когда ваше подпредставление находится за пределами ваших суперпредставлений, события касания, которые фактически происходят в этом подпредставлении, не будут доставлены в это подпредставление. Тем не менее, он будет доставлен своему супервизору.
Независимо от того, обрезаны ли подпредставления визуально, сенсорные события всегда относятся к прямоangularьнику границ суперпредставления целевых видов. Другими словами, сенсорные события, происходящие в части представления, которая находится за пределами прямоangularьника границ его суперпредставлений, не доставляются в это представление. Link
Что тебе нужно сделать?
Когда ваше суперпредставление получит упомянутое выше событие касания, вам нужно будет явно указать UIKit, что мое подпредставление должно быть тем, которое получит это событие касания.
А как насчет кода?
В вашем супервидении внедрите func hitTest(_ point: CGPoint, with event: UIEvent?)
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if isHidden || alpha == 0 || clipsToBounds { return super.hitTest(point, with: event) }
// convert the point into subview coordinate system
let subviewPoint = self.convert(point, to: subview)
// if the converted point lies in subview bound, tell UIKit that subview should be the one that receives this event
if !subview.isHidden && subview.bounds.contains(subviewPoint) { return subview }
return super.hitTest(point, with: event)
}
Увлекательная гочья: вы должны перейти к "самому высокому-маленькому супервизору"
Вы должны перейти "вверх" к "самому высокому" представлению, за которым находится проблемное представление.
Типичный пример:
Скажем, у вас есть экран S с видом контейнера. Вид контейнера контроллера контейнера - это V. (Напомним, V будет находиться внутри C и будет идентичного размера.) У V есть подпредставление (возможно, кнопка) B. Проблема в B вид, который на самом деле находится за пределами V.
Но обратите внимание, что B также находится за пределами C.
В этом примере вы должны применить решение override hitTest
на самом деле к C, а не к V. Если вы примените его к V - он ничего не делает.
Ответ 7
Для меня (как и для других) ответ был не, чтобы переопределить метод hitTest
, а скорее метод pointInside
. Я должен был сделать это только в двух местах.
Супервизор
Это мнение, что если бы у него было clipsToBounds
установлено значение true, это полностью устранило бы эту проблему. Итак, вот мой простой UIView
в Swift 3:
class PromiscuousView : UIView {
override func point(inside point: CGPoint,
with event: UIEvent?) -> Bool {
return true
}
}
Subview
Ваш пробег может отличаться, но в моем случае мне также пришлось перезаписать метод pointInside
для подзаголовка, на котором было принято решение о том, что прикосновение было вне пределов. Мой находится в Objective-C, поэтому:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
WEPopoverController *controller = (id) self.delegate;
if (self.takeHitsOutside) {
return YES;
} else {
return [super pointInside:point withEvent:event];
}
}
Этот ответ (и несколько других вопросов по тому же вопросу) поможет вам понять ваше понимание.
Ответ 8
Swift 4.1 обновлен
Чтобы получить сенсорные события в том же UIView, вам просто нужно добавить следующий метод переопределения, он будет идентифицировать конкретный вид, повернутый в UIView, который находится вне границы
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let pointforTargetview:CGPoint = self.convert(point, to: btnAngle)
if btnAngle.bounds.contains(pointforTargetview) {
return self
}
return super.hitTest(point, with: event)
}