투명한 UIView 뒤의 버튼을 어떻게 클릭합니까?


177

하나의 하위보기가있는보기 컨트롤러가 있다고 가정 해 봅시다. 서브 뷰는 모든면에서 100 픽셀의 여백으로 화면 중앙을 차지합니다. 그런 다음 하위 뷰 내부를 클릭하기 위해 작은 것들을 추가합니다. 우리는 새로운 프레임을 이용하기 위해 서브 뷰만을 사용하고 있습니다 (서브 뷰 내에서 x = 0, y = 0은 실제로는 부모 뷰에서 100,100입니다).

그런 다음 메뉴와 같이 하위 뷰 뒤에 무언가가 있다고 상상해보십시오. 사용자가 하위보기에서 "작은 물건"을 선택할 수 있기를 원하지만 아무 것도 없으면 터치가 뒤에 배경으로 전달됩니다 (배경이 분명하기 때문에).

어떻게해야합니까? 터치가 시작된 것처럼 보이지만 버튼이 작동하지 않습니다.


1
투명 (알파 0) UIView가 터치 이벤트에 응답하지 않아야한다고 생각 했습니까?
Evadne Wu

1
나는 그것을 위해 작은 수업을 썼습니다. (답변에 예를 추가했습니다). UIButton반투명 아래에있는 버튼을 클릭 할 수는 있지만 투명 UIView하지 않은 부분 UIView은 여전히 ​​터치 이벤트에 응답하기 때문에 허용 된 답변보다 다소 나은 해결책이 있습니다.
Segev

답변:


315

컨테이너에 대한 사용자 지정보기를 만들고 pointInside : 메시지를 재정 의하여 포인트가 다음과 같이 적격 한 자식보기 내에 있지 않은 경우 false를 반환합니다.

빠른:

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

이 뷰를 컨테이너로 사용하면 모든 자식이 터치를받을 수 있지만 뷰 자체는 이벤트에 투명합니다.


4
흥미 롭군 응답자 체인으로 더 뛰어들 필요가 있습니다.
Sean Clark Hess

1
뷰가 표시되는지 확인한 다음 테스트하기 전에 하위 뷰가 표시되는지 확인해야합니다.
게으른 코더

2
불행히도이 트릭은보기를 변경할 수없는 tabBarController에서 작동하지 않습니다. 아무도이 견해를 이벤트에 투명하게 만들 생각이 있습니까?
cyrilchampier

1
뷰의 알파도 확인해야합니다. 종종 알파를 0으로 설정하여 뷰를 숨길 것입니다. 알파가 0 인 뷰는 숨겨진 뷰처럼 작동해야합니다.
James Andrews

2
나는 빠른 버전을했다 : 어쩌면 당신은 가시성에 대한 답변에 그것을 포함 할 수 있습니다 gist.github.com/eyeballz/17945454447c7ae766cb
eyeballz

107

나는 또한 사용

myView.userInteractionEnabled = NO;

서브 클래스 할 필요가 없습니다. 잘 작동합니다.


76
이것은 또한 모든 서브 뷰에 대한 사용자 상호 작용을 비활성화합니다
pixelfreak

@pixelfreak이 언급했듯이 모든 경우에 이상적인 솔루션은 아닙니다. 해당 뷰의 하위 뷰에서 상호 작용이 여전히 필요한 경우 해당 플래그를 활성화 상태로 유지해야합니다.
Augusto Carmo

20

Apple에서 :

이벤트 전달은 일부 응용 프로그램에서 사용하는 기술입니다. 다른 응답자 오브젝트의 이벤트 처리 메소드를 호출하여 터치 이벤트를 전달합니다. 이것이 효과적인 기술 일 수 있지만주의해서 사용해야합니다. UIKit 프레임 워크의 클래스는 바인딩되지 않은 터치를 수신하도록 설계되지 않았습니다 .... 애플리케이션에서 다른 응답자에게 조건부로 터치를 전달하려면 이러한 응답자는 모두 고유 한 UIView 서브 클래스의 인스턴스 여야합니다.

사과 모범 사례 :

응답자 체인에 이벤트를 명시 적으로 보내지 마십시오 (NextResponder를 통해). 대신, 수퍼 클래스 구현을 호출하고 UIKit이 응답자 체인 탐색을 처리하게하십시오.

대신 다음을 무시할 수 있습니다.

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

터치를 응답자 체인으로 보내려면 (아니면 아무것도없는 뷰 뒤의 뷰에) 원하는 경우 UIView 하위 클래스에서 NO를 반환하십시오.


1
인용문과 함께 인용 한 문서에 대한 링크를 갖는 것이 좋습니다.
morgancodes

1
나는 당신을 위해 문서의 관련 장소에 대한 링크를 추가했다
jackslash

1
~~ 재정의가 어떻게 생겼는지 보여줄 수 있습니까? ~~ 이것이 어떻게 생겼는지에 대한 또 다른 질문에서 답을 찾았습니다 . 말 그대로 그냥 돌아온다 NO;
kontur

이것은 아마도 대답이 될 것입니다. 가장 간단한 방법과 권장되는 방법입니다.
에콰도르

8

훨씬 간단한 방법은 인터페이스 빌더에서 사용자 상호 작용을 "체크 해제"하는 것입니다. "스토리 보드를 사용하는 경우"

여기에 이미지 설명을 입력하십시오


6
다른 사람들이 비슷한 대답으로 지적 했듯이이 경우 기본보기에서 터치 이벤트를 비활성화하기 때문에이 경우에는 도움이되지 않습니다. 즉,이 설정을 사용하거나 사용하지 않더라도 해당보기 아래의 단추를 누를 수 없습니다.
auco

6

John이 게시 한 내용을 기반으로 다음은 터치 이벤트가 버튼을 제외한 뷰의 모든 하위 뷰를 통과 할 수있는 예입니다.

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

보기가 표시되는지, 하위보기가 표시되는지 확인해야합니다.
게으른 코더

완벽한 솔루션! 더 간단한 방법은 터치 이벤트를 뷰 아래로 전달하려는 경우 재정의 하는 UIView하위 클래스 를 만드는 것 입니다. PassThroughView-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { return YES; }
auco

6

최근에 나는 그것을 도와 줄 수업을 썼습니다. UIButton또는 사용자 정의 클래스로 사용 하거나 UIView투명 픽셀에서 실행 된 터치 이벤트를 전달합니다.

이 솔루션은 UIButton반투명 아래에있는 버튼을 클릭 할 수 있지만 투명 UIView하지 않은 부분은 UIView터치 이벤트에 계속 반응 하므로 허용 된 답변보다 다소 낫습니다 .

GIF

GIF에서 볼 수 있듯이 기린 버튼은 단순한 사각형이지만 투명한 영역의 터치 이벤트는 UIButton아래 의 노란색으로 전달됩니다 .

수업 링크


1
이 솔루션은 효과가있을 수 있지만 솔루션의 관련 부분을 답변 내에 배치하는 것이 좋습니다.
코엔.

4

최고 투표 솔루션이 저에게 완전히 효과가 없었습니다 .TabBarController를 계층 구조에 넣었 기 때문에 (댓글 중 하나가 지적한 것처럼) 실제로 UI의 일부 부분에 대한 터치를 전달했지만 내 엉망이었습니다. 터치 이벤트를 가로 채는 tableView의 기능, 마지막으로 터치 테스트 를 무시하고 있었지만 터치를 무시하고 해당 뷰의 하위 뷰가 처리하도록했습니다.

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

스위프트 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

'iPhone 응용 프로그래밍 안내서'에 따르면 :

터치 이벤트 전달을 해제합니다. 기본적으로보기는 터치 이벤트를 수신하지만 userInteractionEnabled 특성을 NO로 설정하여 이벤트 전달을 해제 할 수 있습니다. 숨겨 지거나 투명한 경우에도 이벤트가 수신되지 않습니다 .

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

업데이트 : 예 제거-질문을 다시 읽습니다 ...

버튼을 가져 오기 전에 탭을 처리하는 뷰에서 제스처 처리가 있습니까? 투명하게 보이지 않을 때 버튼이 작동합니까?

작동하지 않는 코드의 코드 샘플은 무엇입니까?


예, 나는 정상적인 터치가 뷰 아래에서 작동하지만 UIButton 및 기타 요소는 작동하지 않는다고 내 질문에 썼습니다.
Sean Clark Hess

1

내가 아는 한 hitTest : 메서드를 재정 의하여이 작업을 수행 할 수 있어야합니다 . 나는 그것을 시도했지만 제대로 작동하지 못했습니다.

결국 나는 터치 가능한 객체 주위에 투명한 뷰를 만들어서 그것을 덮지 않았습니다. 내 문제에 대한 해킹이 조금 발생했습니다.


1

다른 답변에서 팁을 얻고 Apple 문서를 읽으면서 문제를 해결하기 위해 다음과 같은 간단한 라이브러리를 만들었습니다.
https://github.com/natrosoft/NATouchThroughView
인터페이스 빌더에서 터치를 통과 해야하는 뷰를 쉽게 그릴 수 있습니다. 근본적인 견해.

필자는 애플의 기본 구현을 직접 엉망으로 만들고 의도하지 않은 결과를 초래할 수있는 응용 프로그램 전체를 변경하기 때문에 메소드 스위 즐링이 프로덕션 코드에서 과도하고 위험하다고 생각합니다.

데모 프로젝트가 있으며 README가해야 할 일을 잘 설명해주기를 바랍니다. OP를 해결하기 위해 인터페이스 빌더에서 NATouchThroughView 클래스로 단추가 포함 된 명확한 UIView를 변경합니다. 그런 다음 탭 할 수있는 메뉴를 오버레이하는 명확한 UIView를 찾으십시오. 인터페이스 빌더에서 해당 UIView를 클래스 NARootTouchThroughView로 변경하십시오. 터치가 기본보기 컨트롤러로 전달되도록하려는 경우보기 컨트롤러의 루트 UIView 일 수도 있습니다. 데모 프로젝트를 확인하여 작동 방식을 확인하십시오. 정말 간단하고 안전하며 비 침습적입니다.


1

이를 위해 카테고리를 만들었습니다.

약간의 방법으로 화려하고보기가 황금합니다.

헤더

//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을 추가해야합니다.

여기 에 위치

그런 다음 {Project} -Prefix.pch 파일에서 UIView + PassthroughParent.h를 가져 오면 모든 뷰에이 기능이 있습니다.

모든 뷰는 포인트를 갖지만 빈 공간은 없습니다.

또한 명확한 배경을 사용하는 것이 좋습니다.

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

passthroughPointInside 메서드는 passthroughparent 또는 swizz 항목을 사용하지 않고도 passthroughPointInside를 pointInside로 이름을 바꾸어도 잘 작동합니다.
Benjamin Piette

propertyValueForKey 란 무엇입니까?
Skotch

1
흠. 아무도 그것을 지적하지 않았습니다. 클래스 확장에 속성을 보유하는 사용자 정의 포인터 사전을 작성했습니다. 여기에 포함시킬 수 있는지 확인해 보겠습니다.
게으른 코더

스위블 링 방법은 드문 경우와 개발자의 즐거움을위한 정교한 해킹 솔루션으로 알려져 있습니다. 다음 OS 업데이트로 앱이 중단되거나 중단 될 가능성이 높기 때문에 App Store 안전하지 않습니다. 그냥 무시하지 pointInside: withEvent:않겠습니까?
auco

0

카테고리 또는 서브 클래스 UIView를 사용할 수 없다면 버튼을 앞으로 가져 와서 투명 뷰 앞에 놓을 수도 있습니다. 응용 프로그램에 따라 항상 가능하지는 않지만 나에게 효과적이었습니다. 언제든지 버튼을 다시 가져 오거나 숨길 수 있습니다.


2
안녕하세요, 스택 오버플로에 오신 것을 환영합니다. 새로운 사용자로서 당신을위한 힌트 일뿐입니다. "귀찮게 할 수 없다면"과 같은 문구를 피하고 싶습니다. 그것은 condescending로 보일 수 있습니다
nomistic

헤드 업 주셔서 감사합니다. 그것은 condescending보다 게으른 관점에서 더 많았지 만, 요점을했다.
Shaked Sayag 12

0

이 시도

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

한 세트입니다보십시오 backgroundColor당신의 transparentViewUIColor(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

메소드 포인트를 재정의하는 대신 이것을 사용합니다 (내부 : CGPoint, UIEvent)

  override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        guard self.point(inside: point, with: event) else { return nil }
        return self
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.