Jak mogę kliknąć przycisk za przezroczystym UIView?

Załóżmy, że mamy kontroler widoku z jednym podglądem podrzędnym. subview zajmuje środek ekranu z marginesami 100 px ze wszystkich stron. Następnie dodajemy kilka małych rzeczy do kliknięcia wewnątrz tego subview. Używamy tylko subview do wykorzystania nowej ramki (x=0, y=0 wewnątrz subview jest w rzeczywistości 100,100 w widoku nadrzędnym).

Wtedy wyobraź sobie, że mamy coś za subview, jak menu. Chcę, aby użytkownik mógł wybrać dowolny z "małe rzeczy" w subview, ale jeśli tam nic nie ma, chcę dotknięcia przejść przez to (ponieważ tło jest jasne i tak) do przycisków za nim.

Jak mogę to zrobić? Wygląda na to, że touchesBegan przechodzi, ale przyciski nie działają.

Author: TheNeil, 2010-06-15

16 answers

Utwórz własny widok dla kontenera i Nadpisz komunikat pointInside:, aby zwrócił false, gdy punkt nie znajduje się w uprawnionym widoku potomnym, jak to:

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

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.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event])
            return YES;
    }
    return NO;
}
@end

Użycie tego widoku jako kontenera pozwoli dowolnemu z jego potomków na otrzymanie dotknięć, ale sam widok będzie przezroczysty dla zdarzeń.

 322
Author: John Stephen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-04 17:40:01

Używam również

myView.userInteractionEnabled = NO;

Nie ma potrzeby podklasowania. Działa dobrze.

 113
Author: akw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-01 20:07:15

Od Apple:

Przekazywanie zdarzeń jest techniką używaną przez niektóre aplikacje. Przekazujesz zdarzenia dotykowe, wywołując metody obsługi zdarzeń innego obiektu odpowiedzi. Chociaż może to być skuteczna technika, należy używać go z ostrożnością. Klasy frameworku UIKit nie są zaprojektowane do odbierania dotknięć, które nie są z nimi związane .... Jeśli chcesz warunkowo przekazać dotknięcia innym respondentom w aplikacji, wszystkie te respondenci powinny bądź instancjami własnych podklas UIView.

:

Nie wysyłaj zdarzeń bezpośrednio do łańcucha odpowiedzi (przez nextResponder); zamiast tego wywołaj implementację klasy superclass i pozwól UIKit obsłużyć Przejście łańcucha odpowiedzi.

Zamiast tego możesz nadpisać:

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

W podklasie UIView i zwróć NO, jeśli chcesz, aby ten dotyk był wysyłany do łańcucha odpowiedzi (tj. do widoków za twoim widokiem bez niczego w it).

 20
Author: jackslash,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-14 11:40:53

O wiele prostszym sposobem jest "odblokowanie" interakcji użytkownika włączonej w kreatorze interfejsów. "Jeśli używasz storyboard"

Tutaj wpisz opis obrazka

 8
Author: Jeff,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-03-14 17:55:13

Ostatnio napisałem zajęcia, które mi w tym pomogą. Użycie jej jako niestandardowej klasy dla UIButton LUB UIView przekaże zdarzenia dotykowe, które zostały wykonane na przezroczystym pikselu.

To rozwiązanie jest nieco lepsze niż zaakceptowana odpowiedź, ponieważ nadal można kliknąć UIButton, który znajduje się pod półprzezroczystym UIView, podczas gdy nieprzezroczysta Część UIView nadal będzie reagować na zdarzenia dotykowe.

GIF

Jak widać w gifie, przycisk Żyrafa jest prosty prostokąt, ale zdarzenia dotykowe na przezroczystych obszarach są przekazywane do żółtego UIButton pod spodem.

Link do klasy

 7
Author: Segev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-08 10:51:11

Opierając się na tym, co napisał John, oto przykład, który pozwoli zdarzeniom dotykowym przejść przez wszystkie podwidia widoku z wyjątkiem przycisków:

-(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
Author: Tod Cunningham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-02 05:25:02

Top głosowane rozwiązanie nie w pełni działa dla mnie, myślę, że to dlatego, że miałem TabBarController w hierarchii (jak jeden z komentarzy wskazuje) to było w rzeczywistości przekazywanie dotknięć do niektórych części interfejsu użytkownika, ale to mieszało się z moim tableView zdolność do przechwytywania zdarzeń dotykowych, co w końcu zrobił to było nadrzędne hitTest w widoku chcę ignorować dotknięcia i niech podview tego widoku obsłużyć

- (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
    }
}
 5
Author: PakitoV,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-12 11:32:11

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
}
 3
Author: Barath,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-16 08:33:30

Zgodnie z "iPhone Application Programming Guide":

Wyłączanie dostarczania zdarzeń dotykowych. Domyślnie widok otrzymuje dotyk zdarzenia, ale można ustawić jego właściwość userInteractionEnabled NA NO aby wyłączyć dostarczanie wydarzeń. widok również nie odbiera zdarzeń, jeśli jest ukryty lub jeśli jest przezroczysty .

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

Zaktualizowano : usunięto przykład-przeczytaj ponownie pytanie...

Czy masz jakiekolwiek przetwarzanie gest na widoki, które mogą być przetwarzanie taps zanim przycisk Go dostanie? Czy przycisk działa, gdy nie masz przezroczystego widoku nad nim?

Jakieś próbki kodu niedziałającego?

 2
Author: LK.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-16 16:54:23

Z tego co wiem, powinieneś być w stanie to zrobić poprzez nadpisanie metody hitTest:. Próbowałem, ale nie mogłem go uruchomić prawidłowo.

W końcu stworzyłem serię przezroczystych widoków wokół dotykalnego obiektu, aby go nie zakrywały. Trochę hack dla mojego problemu to działało dobrze.

 1
Author: Liam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-15 16:10:08

Biorąc pod uwagę wskazówki z innych odpowiedzi i czytając dokumentację Apple, stworzyłem tę prostą bibliotekę do rozwiązania Twojego problemu:
https://github.com/natrosoft/NATouchThroughView
Ułatwia to rysowanie widoków w Kreatorze interfejsów, które powinny przejść do podstawowego widoku.

Myślę, że metoda swizzling jest przesadna i bardzo niebezpieczna do zrobienia w kodzie produkcyjnym, ponieważ bezpośrednio mieszasz się z implementacją bazową Apple i robisz zmiana w całej aplikacji, która może spowodować niezamierzone konsekwencje.

Jest projekt demo i mam nadzieję, że README zrobi dobrą robotę wyjaśniając, co robić. Aby zająć się OP, należy zmienić Wyczyść UIView, który zawiera przyciski do klasy NATouchThroughView w Interface Builder. Następnie znajdź przezroczysty Widok interfejsu, który nakłada się na menu, które chcesz stuknąć. Zmień ten UIView na klasę NARootTouchThroughView w Interface Builder. Może to być nawet root UIView twojego widoku kontroler, jeśli te dotknięcia mają przejść do podstawowego kontrolera widoku. Sprawdź projekt demo, aby zobaczyć, jak to działa. To naprawdę dość proste, bezpieczne i nieinwazyjne

 1
Author: n8tr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-22 11:35:31

Stworzyłem kategorię, aby to zrobić.

Mała metoda i widok jest złoty.

Nagłówek

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

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

@end

Plik implementacji

#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
Musisz dodać mój Swizz.h I Swizz.m

Znajduje Się Tutaj

Następnie wystarczy zaimportować UIView + PassthroughParent.h w twoim {Project}-prefiksie.plik pch, a każdy widok będzie miał taką możliwość.

Każdy widok zajmie punkty, ale żaden z pustych spacji nie będzie.

Ja również zaleca się używanie wyraźnego tła.

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

EDIT

Stworzyłem własną torbę z nieruchomościami, która nie była wcześniej uwzględniona.

Plik nagłówkowy

// NSObject+PropertyBag.h

#import <Foundation/Foundation.h>

@interface NSObject (PropertyBag)

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

@end

Plik Implementacji

// 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
 1
Author: The Lazy Coder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:32

Jeśli nie możesz użyć kategorii lub podklasy UIView, możesz również po prostu przesunąć przycisk do przodu, aby znajdował się przed przezroczystym widokiem. Nie zawsze będzie to możliwe w zależności od twojej aplikacji, ale u mnie zadziałało. Zawsze możesz przywrócić przycisk z powrotem lub go ukryć.

 0
Author: Shaked Sayag,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-10 15:36:37

Spróbuj tego

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
Author: tBug,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-22 11:41:09

Spróbuj ustawić backgroundColor swojego transparentView jako UIColor(white:0.000, alpha:0.020). Następnie możesz uzyskać wydarzenia dotykowe w touchesBegan/touchesMoved Metody. Umieść poniższy kod gdzieś w widoku:

self.alpha = 1
self.backgroundColor = UIColor(white: 0.0, alpha: 0.02)
self.isMultipleTouchEnabled = true
self.isUserInteractionEnabled = true
 0
Author: Agisight,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-01-22 22:35:46

Używam tego zamiast override metody point (inside: CGPoint, with: UIEvent)

  override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        guard self.point(inside: point, with: event) else { return nil }
        return self
    }
 0
Author: Wei,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-03-27 09:23:10