iOS - naprzód wszystkie dotknięcia przez widok

141

Mam widok nałożony na wiele innych widoków. Używam tylko overaly, aby wykryć pewną liczbę dotknięć ekranu, ale poza tym nie chcę, aby widok zatrzymywał zachowanie innych widoków pod spodem, którymi są przewijane widoki itp. Jak mogę przekazać wszystkie dotknięcia przez ten widok nakładki? Jest to podskala UIView.

sol
źródło
2
Czy rozwiązałeś swój problem? Jeśli tak, to zaakceptuj odpowiedź, aby innym łatwiej było znaleźć rozwiązanie, ponieważ mam ten sam problem.
Khushbu Desai
ta odpowiedź działa dobrze stackoverflow.com/a/4010809/4833705
Lance Samaria

Odpowiedzi:

121

Wyłączenie interakcji z użytkownikiem było wszystkim, czego potrzebowałem!

Cel C:

myWebView.userInteractionEnabled = NO;

Szybki:

myWebView.isUserInteractionEnabled = false
rmp251
źródło
To zadziałało w mojej sytuacji, w której miałem podgląd przycisku. Wyłączyłem interakcję w podglądzie i przycisk zadziałał.
simple_code
2
W Swift 4,myWebView.isUserInteractionEnabled = false
Louis „LYRO” Dupont
117

Aby przekazać dotknięcia z widoku nakładki do widoków poniżej, zaimplementuj następującą metodę w UIView:

Cel C:

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

Swift 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
źródło
3
Mam ten sam problem, ale to, czego chcę, to jeśli powiększam / obracam / przesuwam widok za pomocą gestu, to powinien zwrócić tak, a po spoczynku powinien zwrócić nie, jak mogę to osiągnąć?
Minakshi
@Minakshi to zupełnie inne pytanie, zadaj nowe pytanie.
Fattie
ale pamiętaj, że to proste podejście NIE POZWALA MIEĆ przycisków i tak dalej NA GÓRĘ danej warstwy!
Fattie
56

To stary wątek, ale pojawił się podczas wyszukiwania, więc pomyślałem, że dodam 2c. Mam obejmujące UIView z podglądami podrzędnymi i chcę przechwytywać tylko dotknięcia, które trafiły w jeden z podglądów podrzędnych, więc zmodyfikowałem odpowiedź PixelCloudSt na

-(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;
}
świeży
źródło
1
Musisz utworzyć niestandardową podklasę UIView (lub podklasę dowolnej innej podklasy UIView, takiej jak UIImageView lub cokolwiek innego) i nadpisać tę funkcję. W istocie podklasa może mieć całkowicie pusty „@interface” w pliku .h, a powyższa funkcja może być całą zawartością „@implementation”.
Fresidue
Dzięki, to jest dokładnie to, czego potrzebowałem, aby przejść przez pustą przestrzeń mojego UIView, aby był obsługiwany przez widok z tyłu. +100
Danny
40

Ulepszona wersja odpowiedzi @fresidue. Tej UIViewpodklasy można używać jako widoku przezroczystego, który przechodzi przez dotknięcia poza jego podwidok. Wdrożenie w celu 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

.

iw Swift:

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

WSKAZÓWKA:

Powiedzmy, że masz duży panel z uchwytami, być może z widokiem na stół z tyłu. Tworzysz panel „uchwyt” PassthroughView. Teraz będzie działać, możesz przewijać tabelę „przez” „uchwyt”.

Ale!

  1. W górnej części panelu „uchwyt” znajdują się etykiety lub ikony. Nie zapominaj, oczywiście, że muszą one być po prostu oznaczone jako opcja interakcji użytkownika włączona.

  2. W górnej części panelu „uchwytu” znajdują się przyciski. Nie zapomnij, oczywiście, że muszą one być po prostu zaznaczone jako włączone interakcja użytkownika!

  3. Uwaga że nieco myląco, „obsady” Sam - widok użyć PassthroughViewna - muszą być oznaczone interakcji użytkownika włączona ON ! To jest włączone !! (W przeciwnym razie kod w PassthroughView po prostu nigdy nie zostanie wywołany).

Timur Bernikovich
źródło
1
Skorzystałem z rozwiązania podanego dla Swift. Działa na iOS 11, ale za każdym razem się zawiesza na iOS 10. Pokazuje zły dostęp. Dowolny pomysł?
Soumen
Uratowałeś mi dni, wyszło na przekazanie poprawek do poniższych UIViews.
AaoIi
1
Nie zapomnij również sprawdzić$0.isUserInteractionEnabled
Sean Thielen
7

Miałem podobny problem z UIStackView (ale może to być dowolny inny widok). Moja konfiguracja była następująca: Wyświetl kontroler z containerView i StackView

To klasyczny przypadek, w którym mam pojemnik, który należało umieścić w tle, z przyciskami z boku. Dla celów układu umieściłem przyciski w UIStackView, ale teraz środkowa (pusta) część przechwytywania stackView dotyka :-(

To, co zrobiłem, to utworzenie podklasy UIStackView z właściwością definiującą subView, który powinien być dotykalny. Teraz każde dotknięcie przycisków bocznych (zawartych w tablicy * viewsWithActiveTouch *) zostanie przypisane do przycisków, podczas gdy jakiekolwiek dotknięcie widoku stosu w dowolnym miejscu poza tymi widokami nie zostanie przechwycone, a zatem przekazane do tego, co znajduje się poniżej stosu widok.

/** 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
   }
}
Frédéric Adda
źródło
6

Jeśli widok, do którego chcesz przekazać dotknięcia, nie jest widokiem podrzędnym / nadzorem, możesz skonfigurować właściwość niestandardową w podklasie UIView, na przykład:

@interface SomeViewSubclass : UIView {

    id forwardableTouchee;

}
@property (retain) id forwardableTouchee;

Upewnij się, że zsyntetyzujesz go w swoim .m:

@synthesize forwardableTouchee;

Następnie uwzględnij następujące elementy w dowolnej z metod UIResponder, takich jak:

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

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

}

Gdziekolwiek tworzysz wystąpienie swojego UIView, ustaw właściwość forwardableTouchee na dowolny widok, do którego mają być przekazywane zdarzenia:

    SomeViewSubclass *view = [[[SomeViewSubclass alloc] initWithFrame:someRect] autorelease];
    view.forwardableTouchee = someOtherView;
Chris Ladd
źródło
4

W Swift 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
źródło
4

Musiałem przejść dotknięcia przez UIStackView. UIView wewnątrz był przezroczysty, ale UIStackView pochłaniał wszystkie dotknięcia. To zadziałało dla mnie:

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

Wszystkie zaaranżowane podglądy nadal są dotykane, ale dotknięcia samego UIStackView przeszły do ​​widoku poniżej (dla mnie mapView).

gorkem
źródło
2

Wygląda na to, że nawet ty masz tutaj sporo odpowiedzi, nie ma nikogo, kto by był czysty w szybkim tempie, którego potrzebowałem. Więc wziąłem odpowiedź z @fresidue tutaj i przekonwertowałem ją na szybką, ponieważ jest to coś, czego teraz chcą tutaj używać głównie programiści.

To rozwiązało mój problem, w którym mam przezroczysty pasek narzędzi z przyciskiem, ale chcę, aby pasek narzędzi był niewidoczny dla użytkownika, a zdarzenia dotykowe powinny przechodzić.

isUserInteractionEnabled = false, jak niektórzy stwierdzili, nie jest opcją opartą na moich testach.

 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
źródło
1

Miałem kilka etykiet w StackView i nie odniosłem większego sukcesu z powyższymi rozwiązaniami, zamiast tego rozwiązałem swój problem za pomocą poniższego kodu:

    let item = ResponsiveLabel()

    // Configure label

    stackView.addArrangedSubview(item)

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

}

Wtedy możesz zrobić coś takiego:

class ResponsiveLabel:UILabel{
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // Respond to touch
    }
}
Amir.n3t
źródło
0

Spróbuj czegoś takiego ...

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

Na przykład powyższy kod w metodzie touchesBegan przekazuje dotknięcia do wszystkich podwidoków widoku.

Jordania
źródło
6
Problem z tym podejściem AFAIK polega na tym, że próby przekazania zmian do klas frameworka UIKit najczęściej nie przyniosą efektu. Zgodnie z dokumentacją firmy Apple, klasy frameworka UIKit nie są przeznaczone do odbierania dotknięć, które mają właściwość widoku ustawioną na inny widok. Więc to podejście działa głównie dla niestandardowych podklas UIView.
maciejs
0

Sytuacja, którą próbowałem zrobić, polegała na zbudowaniu panelu sterowania przy użyciu kontrolek wewnątrz zagnieżdżonych UIStackView. Niektóre kontrolki miały UITextField, inne z UIButton. Były też etykiety identyfikujące kontrole. To, co chciałem zrobić, to umieścić duży „niewidoczny” przycisk za panelem sterowania, aby jeśli użytkownik dotknął obszaru poza przyciskiem lub polem tekstowym, mógłbym to złapać i podjąć działanie - przede wszystkim odrzucić każdą klawiaturę, jeśli tekst pole było aktywne (resignFirstResponder). Jednak dotknięcie etykiety lub innego pustego obszaru w panelu sterowania nie spowoduje przejścia. Powyższe dyskusje były pomocne w sformułowaniu mojej odpowiedzi poniżej.

Zasadniczo podklasowałem UIStackView i nadpisałem procedurę „point (inside: with)” w celu wyszukania typu elementów sterujących, które wymagały dotknięcia i „zignorowania” rzeczy, takich jak etykiety, które chciałem zignorować. Sprawdza również wewnętrzne UIStackView, aby rzeczy mogły powtarzać się w strukturze panelu sterowania.

Kod jest być może trochę bardziej rozwlekły niż powinien. Ale był pomocny w debugowaniu i, miejmy nadzieję, zapewnia większą jasność w tym, co robi rutyna. Po prostu upewnij się, że w programie Interface Builder zmienisz klasę UIStackView na 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
źródło
-1

Jak sugeruje @PixelCloudStv, jeśli chcesz rzucać dotknięte z jednego widoku do drugiego, ale z dodatkową kontrolą nad tym procesem - podklasa UIView

//nagłówek

@interface TouchView : UIView

@property (assign, nonatomic) CGRect activeRect;

@end

//realizacja

#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

Następnie w interfaceBuilder po prostu ustaw klasę View na TouchView i ustaw aktywny prostokąt swoim prostokątem. Możesz także zmienić i wdrożyć inną logikę.

gbk
źródło