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

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

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

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

Ответ 1

Создайте настраиваемое представление для вашего контейнера и переопределите сообщение 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

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

Ответ 2

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

myView.userInteractionEnabled = NO;

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

Ответ 3

От Apple:

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

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

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

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

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

в вашем подклассе UIView и вернуть НЕТ, если вы хотите, чтобы это прикосновение было отправлено в цепочку ответчиков (I.E. для просмотра за вашим представлением без ничего в нем).

Ответ 4

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

enter image description here

Ответ 5

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

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

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

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

GIF

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

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

Ответ 7

Решение, получившее наибольшее количество голосов, не работало полностью для меня, я полагаю, это потому, что у меня был 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
    }
}

Ответ 8

Свифт 3

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

Ответ 9

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

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

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

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

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

Любые примеры кода нерабочего кода?

Ответ 10

Я создал категорию для этого.

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

Заголовок

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

EDIT

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

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

// 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

Ответ 11

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

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

Ответ 12

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

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

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

Ответ 13

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

Ответ 14

Попробуй это

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

Ответ 15

Попробуйте установить 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

Ответ 16

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

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