Взаимодействие за пределами UIView - PullRequest
83 голосов
/ 25 марта 2011

Возможно ли для UIButton (или любого другого элемента управления в этом отношении) получать сенсорные события, когда кадр UIButton находится вне кадра его родителя?Потому что, когда я пытаюсь это сделать, моя UIButton не может принимать какие-либо события.Как мне обойти это?

Ответы [ 8 ]

91 голосов
/ 25 марта 2011

Да.Вы можете переопределить метод 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, и вы могли бы просмотреть список подпредставлений для этих классов.) Надеюсь, это поможет.

23 голосов
/ 02 февраля 2012

@ 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, а все дочерние элементы также являются UIButton, которые могут выходить за пределы родительского UIButton.

Лучший

19 голосов
/ 25 мая 2015

В родительском представлении вы можете переопределить метод проверки попадания:

- (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];

}

В этом случае, если точка попадает в пределы вашей кнопки, вы перенаправляете вызов туда;если нет, вернитесь к исходной реализации.

8 голосов
/ 01 декабря 2015

В моем случае у меня был подкласс UICollectionViewCell, который содержал UIButton.Я отключил clipsToBounds для ячейки, и кнопка была видна за пределами ячейки.Однако кнопка не получала сенсорные события.Я смог обнаружить сенсорные события на кнопке, используя ответ Джека: https://stackoverflow.com/a/30431157/3344977

Вот версия 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)
}
8 голосов
/ 25 апреля 2015

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 голосов
/ 11 января 2018

Почему это происходит?

Это потому, что когда ваше подпредставление находится за пределами вашего суперпредставления, события касания, которые фактически происходят в этом подпредставлении, не будут доставлены этому подпредставлению.Однако WILL будет доставлено в его суперпредставление.

Независимо от того, визуально ли обрезаны подпредставления, события касания всегда относятся к прямоугольнику границ суперпредставления целевого представления.Другими словами, события касания, происходящие в части представления, которая находится за пределами прямоугольника границ его суперпредставления, не доставляются в это представление. Ссылка

Что вам нужно сделать?

Когда ваше суперпредставление получит упомянутое выше событие касания, вам нужно будет сообщитьUIKit явно, что мое подпредставление должно быть тем, чтобы получить это сенсорное событие.

А как насчет кода?

В вашем суперпредставлении реализовать func hitTest(_ point: CGPoint, with event: UIEvent?)

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        if isHidden || alpha == 0 || clipsToBounds { return nil }
        // convert the point into subview's coordinate system
        let subviewPoint = self.convert(point, to: subview)
        // if the converted point lies in subview's bound, tell UIKit that subview should be the one that receives this event
        if ! subview.isHidden && subview.bounds.contains(subviewPoint) { return subview }
        return nil
    }
3 голосов
/ 09 января 2017

Для меня (как и для других) ответ был не , чтобы переопределить метод 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];
    }
}

Этот ответ (и несколько других на тот же вопрос) может помочь прояснить ваше понимание.

2 голосов
/ 26 сентября 2018

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)
}
...