Разрешение взаимодействия с UIView под другим UIView



есть ли простой способ разрешить взаимодействие с кнопкой в UIView, который находится под другим UIView-где нет фактических объектов из верхнего UIView поверх кнопки?



например, на данный момент у меня есть UIView (A) с объектом вверху и объектом внизу экрана и ничего посередине. Это находится поверх другого UIView, который имеет кнопки в середине (B). Тем не менее, я не могу взаимодействовать с кнопками в середине Б.



Я вижу кнопки в B-я установил фон A в clearColor - но кнопки в B, похоже, не получают прикосновений, несмотря на то, что на самом деле нет объектов из A поверх этих кнопок.



EDIT - Я все еще хочу иметь возможность взаимодействовать с объектами в верхнем UIView



конечно, есть простой способ сделать это?

518   18  

18 ответов:

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

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    // UIView will be "transparent" for touch events if we return NO
    return (point.y < MIDDLE_Y1 || point.y > MIDDLE_Y2);
}

вы также можете посмотреть на метод hitTest:event:.

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

этот ответ взят из ответа, который я дал на аналогичный вопрос,здесь.

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *hitView = [super hitTest:point withEvent:event];
    if (hitView == self) return nil;
    return hitView;
}

[super hitTest:point withEvent:event] вернет самое глубокое представление в иерархии этого представления, которое было затронуто. Если hitView == self (т. е. если под точкой касания нет подвида), верните nil, указав, что это представление должно не получать ощупь. Способ работы цепочки ответчиков означает, что иерархия представлений выше этой точки будет продолжать пересекаться до тех пор, пока не будет найдено представление, которое будет реагировать на прикосновение. не верните супервизор, так как это не зависит от этого представления, должен ли его супервизор принимать прикосновения или нет!

это решение это:

  • удобно, потому что он не требует ссылок на любые другие виды/подвиды/объекты;
  • generic, потому что он применяется к любому представлению, которое действует исключительно как контейнер для осязаемых подвидов, и конфигурация подвидов не влияет на то, как он работает (как это происходит, если вы переопределяете pointInside:withEvent: для возврата конкретной модели).
  • надежный, там не так много кода... и понятие не трудно получить вашу голову вокруг.

Я использую это достаточно часто, чтобы я абстрагировал его в подкласс, чтобы сохранить бессмысленные подклассы представления для одного переопределения. В качестве бонуса, добавьте свойство, чтобы сделать его настраиваемым:

@interface ISView : UIView
@property(nonatomic, assign) BOOL onlyRespondToTouchesInSubviews;
@end

@implementation ISView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *hitView = [super hitTest:point withEvent:event];
    if (hitView == self && onlyRespondToTouchesInSubviews) return nil;
    return hitView;
}
@end

затем перейти дикий и использовать этот вид везде, где вы могли бы использовать равнину UIView. Настройка его так же просто, как установка onlyRespondToTouchesInSubviews до YES.

есть несколько способов справиться с этим. Мой любимый-переопределить hitTest: withEvent: в представлении, которое является общим супервизором (возможно, косвенно) для конфликтующих представлений (похоже, вы называете их A и B). Например, что-то вроде этого (здесь A и B-указатели UIView, где B - "скрытый", который обычно игнорируется):

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    CGPoint pointInB = [B convertPoint:point fromView:self];

    if ([B pointInside:pointInB withEvent:event])
        return B;

    return [super hitTest:point withEvent:event];
}

вы также можете изменить pointInside:withEvent: метод, как предложил ГИМ. Это позволяет достичь практически того же результата путем эффективно "тыкать дырку" в себя, хотя бы для прикосновений.

другой подход-это переадресация событий, что означает переопределение touchesBegan:withEvent: и похожие методы (например,touchesMoved:withEvent: etc), чтобы отправить некоторые прикосновения к другому объекту, чем там, где они сначала идут. Например, можно написать что-то вроде этого:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    if ([self shouldForwardTouches:touches]) {
        [B touchesBegan:touches withEvent:event];
    }
    else {
        // Do whatever A does with touches.
    }
}

однако, это не всегда будет работать так, как вы ожидаете! Главное, что встроенные элементы управления, такие как UIButton, всегда будут игнорировать переадресованные прикосновений. Из-за этого, первый подход является более надежным.

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

http://bynomial.com/blog/?p=74

вы должны установить upperView.userInteractionEnabled = NO;, в противном случае вид сверху будет перехватывать прикосновения.

версия построителя интерфейса - это флажок в нижней части панели "атрибуты представления"под названием" Взаимодействие с пользователем включено". Снимите его и вы должны быть хорошо идти.

пользовательская реализация pointInside: withEvent: действительно, казалось, что путь, но работа с жестко закодированными координатами казалась мне странной. Поэтому я закончил проверку, была ли CGPoint внутри кнопки CGRect с помощью функции CGRectContainsPoint ():

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    return (CGRectContainsPoint(disclosureButton.frame, point));
}

недавно я написал класс, который поможет мне именно в этом. Использование его в качестве пользовательского класса для UIButton или UIView передаст события касания, которые были выполнены на прозрачном пикселе.

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

GIF

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

ссылка на класс

Я думаю, я немного опоздал на эту вечеринку, но я принял решение:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *hitView = [super hitTest:point withEvent:event];
    if (hitView != self) return hitView;
    return [self superview];
}

Если вы используете этот код для переопределения стандартной функции hitTest пользовательского UIView, он будет игнорировать только само представление. Любые подвиды этого представления будут возвращать свои хиты нормально, и любые хиты, которые пошли бы в само представление, передаются до его супервизора.

-зольность

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

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    // If one of our subviews wants it, return YES
    for (UIView *subview in self.subviews) {
        CGPoint pointInSubview = [subview convertPoint:point fromView:self];
        if ([subview pointInside:pointInSubview withEvent:event]) {
            return YES;
        }
    }
    // otherwise return NO, as if userInteractionEnabled were NO
    return NO;
}

Примечание: вам даже не нужно делать рекурсию в дереве подвидов, потому что каждый pointInside:withEvent: метод будет обрабатывать это для вас.

отключение свойства userInteraction может помочь. Например:

UIView * topView = [[TOPView alloc] initWithFrame:[self bounds]];
[self addSubview:topView];
[topView setUserInteractionEnabled:NO];

(Примечание: В приведенном выше коде 'self' относится к представлению)

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

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

@implementation PassThroughUIView

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    for (UIView *v in self.subviews) {
        CGPoint localPoint = [v convertPoint:point fromView:self];
        if (v.alpha > 0.01 && ![v isHidden] && v.userInteractionEnabled && [v pointInside:localPoint withEvent:event])
            return YES;
    }
    return NO;
}

@end

мое решение здесь:

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    CGPoint pointInView = [self.toolkitController.toolbar convertPoint:point fromView:self];

    if ([self.toolkitController.toolbar pointInside:pointInView withEvent:event]) {
       self.userInteractionEnabled = YES;
    } else {
       self.userInteractionEnabled = NO;
    }

    return [super hitTest:point withEvent:event];
}

надеюсь, что это помогает

есть что-то, что вы можете сделать, чтобы перехватить прикосновение в обоих видах.

вид сверху:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
   // Do code in the top view
   [bottomView touchesBegan:touches withEvent:event]; // And pass them on to bottomView
   // You have to implement the code for touchesBegan, touchesEnded, touchesCancelled in top/bottom view.
}

а это идея.

вот быстрая версия:

override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
    return !CGRectContainsPoint(buttonView.frame, point)
}

Swift 3

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    for subview in subviews {
        if subview.frame.contains(point) {
            return true
        }
    }
    return false
}

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

каждый UIView, и это UIWindow, имеет свойство subviews, который является NSArray, содержащий все подвиды.

первое вложенное представление, которое вы добавляете в представление, получит индекс 0, а следующий индекс 1 и так далее. Вы также можете заменить addSubview: С insertSubview: atIndex: или insertSubview:aboveSubview: и такие методы, которые могут определить положение вашего подвида в иерархии.

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

UIView * theOtherView = [[[self superview] subviews] objectAtIndex: 0];
// or using the properties syntax
UIView * theOtherView = [self.superview.subviews objectAtIndex:0];

Дайте мне знать, если это работает для вашего случая!


(ниже этого маркера мой предыдущий ответ):

если взгляды должны общаться друг с другом, они должны делать это через контроллер (то есть, с помощью популярного MVC модель).

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

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

если ваш взгляд не имеет свяжите обратно контроллер (что может быть так), то вы можете использовать синглтоны и/или методы класса, чтобы получить ссылку на контроллер.

Я думаю, что правильный способ-использовать цепочку представлений, встроенную в иерархию представлений. Для ваших подвидов, которые помещаются в основное представление, не используйте общий UIView, а вместо этого подкласс UIView (или один из его вариантов, таких как UIImageView), чтобы сделать MYView : UIView (или любой супертип, который вы хотите, например UIImageView). В реализации для YourView реализуйте метод touchesBegan. Затем этот метод будет вызван при касании этого представления. Все, что вам нужно иметь в этой реализации это метод экземпляра:

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event ;
{   // cannot handle this event. pass off to super
    [self.superview touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event]; }

этот touchesBegan является api-интерфейсом ответчика, поэтому вам не нужно объявлять его в своем публичном или частном интерфейсе; это один из тех волшебных api, о которых вам просто нужно знать. Эта самость.superview будет пузыриться запрос в конечном итоге к viewController. Затем в viewController реализуйте этот touchesBegan для обработки касания.

обратите внимание, что расположение касаний (CGPoint) автоматически настраивается относительно охватывающего вида для вы как бы отскочили вверх по цепочке иерархии представлений.

просто хочу опубликовать это, потому что у меня была несколько похожая проблема, потратил значительное количество времени, пытаясь реализовать ответы здесь без какой-либо удачи. Что я в конечном итоге сделал:

 for(UIGestureRecognizer *recognizer in topView.gestureRecognizers)
 {
     recognizer.delegate=self;
     [bottomView addGestureRecognizer:recognizer];   
 }
 topView.abView.userInteractionEnabled=NO; 

и осуществляет UIGestureRecognizerDelegate:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

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

реализация Swift 4 для решения на основе HitTest

let hitView = super.hitTest(point, with: event)
if hitView == self { return nil }
return hitView

Comments

    Ничего не найдено.