Взаємодія поза межами UIView


92

Чи можливо, щоб UIB-кнопка (або будь-який інший елемент керування з цього приводу) отримував події дотику, коли кадр UIB-кнопки знаходиться поза його батьківським кадром? Тому що, коли я намагаюся це зробити, моя кнопка UIB, здається, не може приймати жодних подій. Як мені обійти це?


1
Для мене це чудово працює. developer.apple.com/library/ios/qa/qa2013/qa1812.html Найкраще
Френк Лі

2
Це також добре і доречно (або, можливо, дурень): stackoverflow.com/a/31420820/8047
Ден Розенстарк,

Відповіді:


93

Так. Ви можете замінити 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, і ви можете переглянути список своїх підпрограм для цих класів.) Сподіваюся, це допоможе.


Чи можете ви допомогти мені в подальшій правильній реалізації цього з деяким зразком коду? Також я прочитав посилання і через наступний рядок мене бентежить: "Точки, що знаходяться поза межами приймача, ніколи не повідомляються як потрапляння, навіть якщо вони насправді лежать в одному з підпрограм приймача. Підпрограми можуть візуально виходити за межі їх батьків, якщо для властивості clipsToBounds батьківського подання встановлено значення NO. Проте тестування звернень завжди ігнорує точки поза межами батьківського подання. "
ThomasM

Особливо в цій частині здається, ніби це не те рішення, яке мені потрібно: "Однак тестування звернень завжди ігнорує точки поза межами батьківського подання" .. Але я можу помилятися, звичайно, я вважаю, що посилання на яблуко іноді дійсно заплутані ..
ThomasM

Ах, я зараз зрозумів. Вам потрібно замінити батьківські, pointInside:withEvent:щоб включити рамку кнопки. Я додаю зразок коду до моєї відповіді вище.
jnic

Чи є спосіб зробити це, не замінюючи методи UIView? Наприклад, якщо ваші подання є повністю загальними для UIKit та ініціалізовані за допомогою Nib, чи можете ви замінити ці методи у своєму UIViewController?
RLH

1
hitTest:withEvent:і pointInside:withEvent:область, яка не викликається для мене, коли я натискаю кнопку, яка знаходиться за межами батьківських меж. Що може бути не так?
iosdude

24

@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.

Найкраще


20

У батьківському поданні ви можете замінити метод перевірки звернення:

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

}

У цьому випадку, якщо точка потрапляє в межі вашої кнопки, ви переадресовуєте виклик туди; якщо ні, поверніться до початкової реалізації.


17

У моєму випадку у мене був 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)
}

Свіфт 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)
}

Це був саме мій сценарій. Інший момент - ви повинні помістити цей метод всередину класу CollectionViewCell.
Хамід Реза Ансарі

Але навіщо вам вводити кнопку перевизначеному методу ???
rommex

10

Чому це відбувається?

Це пов’язано з тим, що коли ваш підзапис знаходиться поза межами вашого суперпогляду, події дотику, які насправді відбуваються в цьому підпрогляді, не будуть доставлені до цього підпрогляду. Однак він БУДЕ доставлений до свого супервізу.

Незалежно від того, підзаголовки відсікаються візуально, події дотику завжди поважають прямокутник меж суперперегляду цільового подання. Іншими словами, події дотику, що відбуваються в частині подання, що лежить за межами прямокутника меж нагляду, не доставляються до цього подання. Посилання

Що потрібно зробити?

Коли ваша суперперегляд отримує згадану вище подію дотику, вам потрібно буде прямо сказати 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'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 super.hitTest(point, with: event)
    }

Захоплююче chотчія: ви повинні перейти до "найвищого занадто малого суперпогляду"

Ви повинні піднятися "вгору" до "найвищого" подання, яке знаходиться поза зоною проблеми.

Типовий приклад:

Скажімо, у вас є екран S, з видом на контейнер C. Вигляд контролера перегляду контейнера - V. (Згадайте, V буде сидіти всередині C і буде однакового розміру.) V має підпрогляд (можливо, кнопку) B. B - проблема погляд, який насправді знаходиться поза межами В.

Але зверніть увагу, що B також знаходиться поза межами C.

У цьому прикладі ви повинні застосувати рішення override hitTestнасправді С, не до V . Якщо застосувати його до V - це нічого не робить.


4
Дякую за це! Я витратив години і години на проблему, що випливає з цього. Я дуже вдячний, що ви знайшли час, щоб опублікувати це. :)
ClayJ

@ScottZhu: Я додав до вашої відповіді важливий ґотчія. Звичайно, ви можете сміливо видаляти чи змінювати моє доповнення! Знову дякую!
Fattie

9

Стрімкий:

Де 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)
}

5

Для мене (як і для інших) відповідь полягала не в тому, щоб замінити hitTestметод, а, скоріше, у pointInsideметоді. Мені довелося це зробити лише у двох місцях.

Superview Це погляд, що якби він clipsToBoundsвстановив істину, це змусило б всю цю проблему зникнути. Отже, ось мій простий UIViewу Swift 3:

class PromiscuousView : UIView {
    override func point(inside point: CGPoint,
               with event: UIEvent?) -> Bool {
        return true
    }
} 

Підпрогляд Ваш пробіг може відрізнятися, але в моєму випадку мені також довелося переписати 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

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)
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.