it-swarm.com.ru

Как я могу нажать кнопку за прозрачным UIView?

Допустим, у нас есть контроллер представления с одним вложенным представлением. подпредставление занимает центр экрана с полями 100 пикселей со всех сторон. Затем мы добавляем кучу маленьких вещей, чтобы щелкнуть внутри этого подпредставления. Мы используем подпредставление только для того, чтобы воспользоваться новым кадром (x = 0, y = 0 внутри подпредставления на самом деле 100,100 в родительском представлении). 

Затем представьте, что у нас есть что-то позади подпредставления, например, меню. Я хочу, чтобы пользователь мог выбрать любой «маленький материал» в подпредставлении, но если там ничего нет, я хочу, чтобы касания проходили через него (так как фон в любом случае ясен) к кнопкам позади него. 

Как я могу это сделать? Похоже, прикосновения Беган проходит, но кнопки не работают. 

161
Sean Clark Hess

Создайте пользовательское представление для своего контейнера и переопределите сообщение pointInside: для возврата false, если точка не находится в допустимом дочернем представлении, например так:

Swift:

class PassThroughView: UIView {
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        for subview in subviews {
            if !subview.isHidden && subview.isUserInteractionEnabled && subview.point(inside: convert(point, to: subview), with: event) {
                return true
            }
        }
        return false
    }
}

Цель C:

@interface PassthroughView : UIView
@end

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

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

286
John Stephen

Я также использую 

myView.userInteractionEnabled = NO;

Нет необходимости в подклассе. Работает отлично.

88
akw

От Apple:

Пересылка событий - это метод, используемый некоторыми приложениями. Вы пересылаете события касания, вызывая методы обработки событий другого объекта респондента. Хотя это может быть эффективным методом, вы должны использовать его с осторожностью. Классы инфраструктуры UIKit не предназначены для получения касаний, которые не связаны с ними .... Если вы хотите условно пересылать касания другим респондентам в вашем приложении, все эти респонденты должны быть экземплярами ваших собственных подклассов UIView.

Лучшая практика яблок :

Не отправляйте явно события вверх по цепочке респондента (через nextResponder); вместо этого, вызовите реализацию суперкласса и позвольте UIKit обрабатывать обход цепочки респондента.

вместо этого вы можете переопределить:

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event

в своем подклассе UIView и верните NO, если вы хотите, чтобы это прикосновение было отправлено вверх по цепочке респондента (т.е. к представлениям позади вашего представления, в которых ничего нет).

18
jackslash

Гораздо более простой способ - «отключить» взаимодействие с пользователем, включенное в конструкторе интерфейсов. «Если вы используете раскадровку»

 enter image description here

7
Jeff

Основываясь на том, что написал Джон, вот пример, который позволит сенсорным событиям проходить через все подпредставления представления, кроме кнопок:

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    // Allow buttons to receive press events.  All other views will get ignored
    for( id foundView in self.subviews )
    {
        if( [foundView isKindOfClass:[UIButton class]] )
        {
            UIButton *foundButton = foundView;

            if( foundButton.isEnabled  &&  !foundButton.hidden &&  [foundButton pointInside:[self convertPoint:point toView:foundButton] withEvent:event] )
                return YES;
        }        
    }
    return NO;
}
6
Tod Cunningham

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

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

GIF

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

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

6
Segev

Решение, получившее наибольшее количество голосов, не работало полностью для меня, я думаю, это потому, что у меня был TabBarController в иерархии (как указывает один из комментариев), он фактически передавал касания некоторым частям пользовательского интерфейса, но это мешало моему способность tableView перехватывать события касания, что, в конце концов, было переопределено hitTest в представлении, которое я хочу игнорировать касания и позволять подпредставлениям этого представления обрабатывать их

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    UIView *view = [super hitTest:point withEvent:event];
    if (view == self) {
        return nil; //avoid delivering touch events to the container view (self)
    }
    else{
        return view; //the subviews will still receive touch events
    }
}
3
Emilio

Согласно «Руководству по программированию приложений iPhone»:

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

http://developer.Apple.com/iphone/library/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/EventHandling/EventHandling.html

Обновлено: удален пример - перечитайте вопрос ...

Есть ли у вас какие-либо жесты обработки представлений, которые могут обрабатывать касания до того, как кнопка их получит? Работает ли кнопка, когда у вас нет прозрачного вида на нее?

Какие-нибудь примеры неработающего кода?

2
LK.

Я создал категорию, чтобы сделать это.

маленький метод извергает и вид золотой.

Заголовок

//UIView+PassthroughParent.h
@interface UIView (PassthroughParent)

- (BOOL) passthroughParent;
- (void) setPassthroughParent:(BOOL) passthroughParent;

@end

Файл реализации

#import "UIView+PassthroughParent.h"

@implementation UIView (PassthroughParent)

+ (void)load{
    Swizz([UIView class], @selector(pointInside:withEvent:), @selector(passthroughPointInside:withEvent:));
}

- (BOOL)passthroughParent{
    NSNumber *passthrough = [self propertyValueForKey:@"passthroughParent"];
    if (passthrough) return passthrough.boolValue;
    return NO;
}
- (void)setPassthroughParent:(BOOL)passthroughParent{
    [self setPropertyValue:[NSNumber numberWithBool:passthroughParent] forKey:@"passthroughParent"];
}

- (BOOL)passthroughPointInside:(CGPoint)point withEvent:(UIEvent *)event{
    // Allow buttons to receive press events.  All other views will get ignored
    if (self.passthroughParent){
        if (self.alpha != 0 && !self.isHidden){
            for( id foundView in self.subviews )
            {
                if ([foundView alpha] != 0 && ![foundView isHidden] && [foundView pointInside:[self convertPoint:point toView:foundView] withEvent:event])
                    return YES;
            }
        }
        return NO;
    }
    else {
        return [self passthroughPointInside:point withEvent:event];// Swizzled
    }
}

@end

Вам нужно будет добавить мои Swizz.h и Swizz.m

находится Здесь

После этого вы просто импортируете UIView + PassthroughParent.h в свой файл {Project} -Prefix.pch, и у каждого представления будет эта возможность.

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

Я также рекомендую использовать чистый фон.

myView.passthroughParent = YES;
myView.backgroundColor = [UIColor clearColor];

ПРАВКА 

Я создал свою собственную сумку собственности, и это не было включено ранее.

Заголовочный файл

// NSObject+PropertyBag.h

#import <Foundation/Foundation.h>

@interface NSObject (PropertyBag)

- (id) propertyValueForKey:(NSString*) key;
- (void) setPropertyValue:(id) value forKey:(NSString*) key;

@end

Файл реализации

// NSObject+PropertyBag.m

#import "NSObject+PropertyBag.h"



@implementation NSObject (PropertyBag)

+ (void) load{
    [self loadPropertyBag];
}

+ (void) loadPropertyBag{
    @autoreleasepool {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Swizz([NSObject class], NSSelectorFromString(@"dealloc"), @selector(propertyBagDealloc));
        });
    }
}

__strong NSMutableDictionary *_propertyBagHolder; // Properties for every class will go in this property bag
- (id) propertyValueForKey:(NSString*) key{
    return [[self propertyBag] valueForKey:key];
}
- (void) setPropertyValue:(id) value forKey:(NSString*) key{
    [[self propertyBag] setValue:value forKey:key];
}
- (NSMutableDictionary*) propertyBag{
    if (_propertyBagHolder == nil) _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100];
    NSMutableDictionary *propBag = [_propertyBagHolder valueForKey:[[NSString alloc] initWithFormat:@"%p",self]];
    if (propBag == nil){
        propBag = [NSMutableDictionary dictionary];
        [self setPropertyBag:propBag];
    }
    return propBag;
}
- (void) setPropertyBag:(NSDictionary*) propertyBag{
    if (_propertyBagHolder == nil) _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100];
    [_propertyBagHolder setValue:propertyBag forKey:[[NSString alloc] initWithFormat:@"%p",self]];
}

- (void)propertyBagDealloc{
    [self setPropertyBag:nil];
    [self propertyBagDealloc];//Swizzled
}

@end
2
The Lazy Coder

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
}
2
Barath

Насколько я знаю, вы должны быть в состоянии сделать это, переопределив метод hitTest: . Я попробовал, но не смог заставить его работать должным образом. 

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

1
Liam

Используя советы из других ответов и читая документацию Apple, я создал простую библиотеку для решения вашей проблемы:
https://github.com/natrosoft/NATouchThroughView
Это упрощает рисование представлений в Интерфейсном Разработчике, которые должны передавать касания основному представлению.

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

Есть демонстрационный проект, и, надеюсь, README хорошо объяснит, что делать. Чтобы обратиться к OP, вы должны изменить прозрачный UIView, который содержит кнопки для класса NATouchThroughView в Интерфейсном Разработчике. Затем найдите чистый UIView, который перекрывает меню, которое вы хотите, чтобы можно было нажать. Измените этот UIView на класс NARootTouchThroughView в Интерфейсном Разработчике. Он может даже быть корневым UIView вашего контроллера представления, если вы намерены передать эти прикосновения к базовому контроллеру представления. Посмотрите демонстрационный проект, чтобы увидеть, как он работает. Это действительно довольно просто, безопасно и неинвазивно 

1
n8tr

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

0
Shaked Sayag

Попробуйте установить backgroundColor вашего transparentView как UIColor(white:0.000, alpha:0.020). Затем вы можете получить сенсорные события в методах touchesBegan/touchesMoved. Поместите код ниже где-то, что ваш взгляд инициирован:

self.alpha = 1
self.backgroundColor = UIColor(white: 0.0, alpha: 0.02)
self.isMultipleTouchEnabled = true
self.isUserInteractionEnabled = true
0
Agisight

Я использую это вместо переопределения точки метода (внутри: CGPoint, с: UIEvent)

  override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        guard self.point(inside: point, with: event) else { return nil }
        return self
    }
0
Wei

Попробуй это 

class PassthroughToWindowView: UIView {
        override func test(_ point: CGPoint, with event: UIEvent?) -> UIView? {
            var view = super.hitTest(point, with: event)
            if view != self {
                return view
            }

            while !(view is PassthroughWindow) {
                view = view?.superview
            }
            return view
        }
    } 
0
Psajho Nub