iOS - пересылать все касания через вид

141

У меня есть наложение поверх многих других представлений. Я использую только избыточно для обнаружения некоторого количества касаний на экране, но кроме этого я не хочу, чтобы представление останавливало поведение других представлений внизу, таких как прокрутки и т. Д. Как я могу переслать все касания через это наложение вид? Это подкалка UIView.

золь
источник
2
Вы решили свою проблему? Если да, то примите ответ, чтобы другим было проще найти решение, потому что я столкнулся с той же проблемой.
Хушбу Десаи
этот ответ хорошо работает stackoverflow.com/a/4010809/4833705
Ланс Самария

Ответы:

121

Отключение взаимодействия с пользователем - все, что мне было нужно!

Objective-C:

myWebView.userInteractionEnabled = NO;

Swift:

myWebView.isUserInteractionEnabled = false
rmp251
источник
Это сработало в моей ситуации, когда у меня было подпредставление кнопки. Я отключил взаимодействие на подпредставлении и кнопка сработала.
simple_code
2
В Swift 4,myWebView.isUserInteractionEnabled = false
Луи 'LYRO' Дюпон
117

Для передачи касаний из наложенного вида в представления внизу, реализуйте следующий метод в UIView:

Objective-C:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    NSLog(@"Passing all touches to the next view (if any), in the view stack.");
    return NO;
}

Свифт 5:

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    print("Passing all touches to the next view (if any), in the view stack.")
    return false
}
PixelCloudSt
источник
3
У меня та же проблема, но я хочу, чтобы при масштабировании / повороте / перемещении вида жестом он возвращал «да», а в случае отдыха - «нет», как я могу этого добиться?
Минакши
@Minakshi, это совершенно другой вопрос, задайте новый вопрос для этого.
Толстяк
но знайте, что этот простой подход НЕ ПОЗВОЛЯЕТ ВАМ ИМЕТЬ кнопки и т. д. НА ВЕРХЕ рассматриваемого слоя!
Толстяк
56

Это старая ветка, но она возникла при поиске, поэтому я решил добавить свой 2c. У меня есть покрытие UIView с подпредставлениями, и я хочу перехватывать только касания, попавшие в одно из подпредставлений, поэтому я изменил ответ PixelCloudSt на

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    for (UIView* subview in self.subviews ) {
        if ( [subview hitTest:[self convertPoint:point toView:subview] withEvent:event] != nil ) {
            return YES;
        }
    }
    return NO;
}
fresidue
источник
1
Вам необходимо создать пользовательский подкласс UIView (или подкласс любого другого подкласса UIView, такого как UIImageView или любой другой) и переопределить эту функцию. По сути, у подкласса может быть совершенно пустой «@interface» в файле .h, а функция выше - это все содержимое «@implementation».
Февраль
Спасибо, это именно то, что мне нужно, чтобы проходить прикосновения через пустое пространство моего UIView, чтобы быть обработанным представлением позади. +100
Дэнни
41

Улучшенная версия ответа @fresidue. Вы можете использовать этот UIViewподкласс как прозрачный вид, передавая прикосновения за пределы его подпредставления. Реализация в Objective-C :

@interface PassthroughView : UIView

@end

@implementation PassthroughView

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    for (UIView *view in self.subviews) {
        if (!view.hidden && [view pointInside:[self convertPoint:point toView:view] withEvent:event]) {
            return YES;
        }
    }
    return NO;
}

@end

,

и в Свифте:

class PassthroughView: UIView {
  override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    return subviews.contains(where: {
      !$0.isHidden
      && $0.isUserInteractionEnabled
      && $0.point(inside: self.convert(point, to: $0), with: event)
    })
  }
}

НАКОНЕЧНИК:

Скажем, у вас есть большая панель "держатель", возможно, с видом на стол сзади. Вы делаете панель "держатель" PassthroughView. Теперь он будет работать, вы можете прокручивать стол «через» «держатель».

Но!

  1. В верхней части панели «Держатель» у вас есть несколько ярлыков или значков. Не забывайте, конечно, они должны быть просто помечены как взаимодействие с пользователем отключено!

  2. В верхней части панели «держатель» у вас есть несколько кнопок. Не забывайте, конечно, что они должны быть просто помечены как включено взаимодействие с пользователем!

  3. Обратите внимание , что несколько смутно, то «владелец» сам по себе - вид вы используете PassthroughViewна - должны быть отмечены взаимодействия с пользователем включен ON ! Это включено ! (В противном случае код в PassthroughView просто никогда не будет вызван.)

Тимур Берникович
источник
1
Я использовал решение, данное для Swift. Он работает на ios 11, но каждый раз на iOS 10 вылетает сбой. Отображается неверный доступ. Любая идея?
Soumen
Вы спасли мои дни, это сработало для передачи штрихов к ниже UIViews.
AaoIi
1
Не забудьте также проверить$0.isUserInteractionEnabled
Шон Тилен
7

У меня была похожая проблема с UIStackView (но может быть любое другое представление). Моя конфигурация была следующей: Контроллер представления с containerView и StackView

Это классический случай, когда у меня есть контейнер, который нужно разместить на заднем плане, с кнопками сбоку. Для макета я включил кнопки в UIStackView, но теперь средняя (пустая) часть stackView перехватывает прикосновения :-(

Я создал подкласс UIStackView со свойством, определяющим подвид, который должен быть сенсорным. Теперь любое прикосновение к боковым кнопкам (включенным в массив * viewsWithActiveTouch *) будет предоставлено кнопкам, в то время как любое прикосновение к стековому обзору в любом месте, кроме этих представлений, не будет перехвачено и, следовательно, передано любому объекту, находящемуся ниже стека. Посмотреть.

/** Subclass of UIStackView that does not accept touches, except for specific subviews given in the viewsWithActiveTouch array */
class NoTouchStackView: UIStackView {

  var viewsWithActiveTouch: [UIView]?

  override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {

    if let activeViews = viewsWithActiveTouch {
        for view in activeViews {
           if CGRectContainsPoint(view.frame, point) {
                return view

           }
        }
     }
     return nil
   }
}
Фредерик Адда
источник
6

Если представление, которому вы хотите переслать касания, не является подпредставлением / суперпредставлением, вы можете настроить собственное свойство в своем подклассе UIView следующим образом:

@interface SomeViewSubclass : UIView {

    id forwardableTouchee;

}
@property (retain) id forwardableTouchee;

Убедитесь, что синтезировали это в своем .m:

@synthesize forwardableTouchee;

А затем включите следующее в любой из ваших методов UIResponder, таких как:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    [self.forwardableTouchee touchesBegan:touches withEvent:event];

}

Где бы вы ни создавали свой экземпляр UIView, установите свойство forwardableTouchee для любого представления, в которое вы хотите пересылать события:

    SomeViewSubclass *view = [[[SomeViewSubclass alloc] initWithFrame:someRect] autorelease];
    view.forwardableTouchee = someOtherView;
Крис Лэдд
источник
4

В Свифт 5

class ThroughView: UIView {
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        guard let slideView = subviews.first else {
            return false
        }

        return slideView.hitTest(convert(point, to: slideView), with: event) != nil
    }
}
onmyway133
источник
4

Мне нужно было пройти через UIStackView. UIView внутри был прозрачным, но UIStackView потреблял все прикосновения. Это сработало для меня:

class PassThrouStackView: UIStackView {

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let view = super.hitTest(point, with: event)
        if view == self {
            return nil
        }
        return view
    }
}

Все упорядоченные подпредставления все еще получают прикосновения, но прикосновения к самому UIStackView перешли к представлению ниже (для меня mapView).

Gorkem
источник
2

Похоже, даже если здесь довольно много ответов, мне не нужно никого убирать быстро. Поэтому я взял ответ от @fresidue здесь и преобразовал его в swift, так как это то, что сейчас большинство разработчиков хотят использовать здесь.

Это решило мою проблему, когда у меня есть прозрачная панель инструментов с кнопкой, но я хочу, чтобы панель инструментов была невидимой для пользователя, и сенсорные события должны проходить.

isUserInteractionEnabled = false, поскольку некоторые из них не являются вариантом, основанным на моем тестировании.

 override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    for subview in subviews {
        if subview.hitTest(convert(point, to: subview), with: event) != nil {
            return true
        }
    }
    return false
}
Renetik
источник
1

У меня было несколько ярлыков внутри StackView, и у меня не было большого успеха с вышеуказанными решениями, вместо этого я решил свою проблему, используя следующий код:

    let item = ResponsiveLabel()

    // Configure label

    stackView.addArrangedSubview(item)

Подкласс UIStackView:

class PassThrouStackView:UIStackView{
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        for subview in self.arrangedSubviews {
            let convertedPoint = convert(point, to: subview)
            let labelPoint = subview.point(inside: convertedPoint, with: event)
            if (labelPoint){
                return subview
            }

        }
        return nil
    }

}

Тогда вы можете сделать что-то вроде:

class ResponsiveLabel:UILabel{
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // Respond to touch
    }
}
Amir.n3t
источник
0

Попробуйте что-то вроде этого ...

for (UIView *view in subviews)
  [view touchesBegan:touches withEvent:event];

Код выше, например, в вашем методе touchesBegan передаст касания всем подпредставлениям представления.

Иордания
источник
6
Проблема с этим подходом AFAIK заключается в том, что попытка перенаправить касания на классы фреймворка UIKit чаще всего не будет иметь никакого эффекта. Согласно документации Apple, классы фреймворков UIKit не предназначены для получения прикосновений, в которых для свойства view установлено какое-либо другое представление. Так что этот подход работает в основном для пользовательских подклассов UIView.
Maciejs
0

Ситуация, которую я пытался сделать, это создать панель управления с использованием элементов управления внутри вложенного UIStackView. Некоторые из элементов управления были UITextField другие с UIButton's. Также были ярлыки для идентификации элементов управления. Я хотел поместить большую «невидимую» кнопку за панелью управления, чтобы, если пользователь нажал на область за пределами кнопки или текстового поля, я мог поймать это и предпринять необходимые действия - в первую очередь отклонить любую клавиатуру, если текст поле было активным (resignFirstResponder). Тем не менее, нажатие на ярлык или другую пустую область на панели управления не будет проходить через все. Вышеприведенные обсуждения были полезны для придания моего ответа ниже.

По сути, я подклассифицировал UIStackView и переписал подпрограмму «point (inside: with)», чтобы найти тип элементов управления, которым необходимо касание, и «игнорировать» такие вещи, как метки, которые я хотел игнорировать. Он также проверяет внутри UIStackView, чтобы вещи могли вернуться в структуру панели управления.

Код, возможно, немного более подробный, чем должен быть. Но это было полезно при отладке и, надеюсь, даст больше ясности в том, что делает рутина. Просто убедитесь, что в Интерфейсном Разработчике измените класс UIStackView на PassThruStack.

class PassThruStack: UIStackView {

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {

    for view in self.subviews {
        if !view.isHidden {
            let isStack = view is UIStackView
            let isButton = view is UIButton
            let isText = view is UITextField
            if isStack || isButton || isText {
                let pointInside = view.point(inside: self.convert(point, to: view), with: event)
                if pointInside {
                    return true
                }
            }
        }
    }
    return false
}

}

anorskdev
источник
-1

По предложению @PixelCloudStv, если вы хотите перебрасывать прикосновения от одного вида к другому, но с некоторым дополнительным контролем над этим процессом - подкласс UIView

// заголовок

@interface TouchView : UIView

@property (assign, nonatomic) CGRect activeRect;

@end

//реализация

#import "TouchView.h"

@implementation TouchView

#pragma mark - Ovverride

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    BOOL moveTouch = YES;
    if (CGRectContainsPoint(self.activeRect, point)) {
        moveTouch = NO;
    }
    return moveTouch;
}

@end

После в interfaceBuilder просто установите класс View в TouchView и установите активный прямоугольник с вашим прямоугольником. Также вы можете изменить и реализовать другую логику.

GBK
источник