iOS-보기를 통해 모든 터치 전달


141

다른 많은보기 위에 오버레이 된보기가 있습니다. 나는 화면에서 몇 가지 터치를 감지하기 위해 Overaly를 사용하고 있지만 그 외에는 뷰가 스크롤 뷰와 같은 다른 뷰의 동작을 멈추고 싶지 않습니다. 모든 터치를 전달하는 방법 이 오버레이 뷰? UIView의 일부입니다.


2
문제를 해결 했습니까? 그렇다면 다른 사람이 같은 문제에 직면했기 때문에 솔루션을 쉽게 찾을 수 있도록 답변을 수락하십시오.
Khushbu Desai

이 답변은
Lance Samaria

답변:


121

사용자 상호 작용을 비활성화하는 것만으로도 충분했습니다!

목표 -C :

myWebView.userInteractionEnabled = NO;

빠른:

myWebView.isUserInteractionEnabled = false

이것은 버튼의 하위 뷰가있는 상황에서 효과적이었습니다. 하위보기에서 상호 작용을 비활성화하고 버튼이 작동했습니다.
simple_code

2
스위프트 4에서myWebView.isUserInteractionEnabled = false
루이 'LYRO'듀폰

117

오버레이보기에서 아래보기로 터치를 전달하려면 UIView에서 다음 메소드를 구현하십시오.

목표 -C :

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

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

3
나는 같은 문제가 있지만 원하는 것은 제스처로 확대 / 축소 / 회전 / 이동하는 경우 예를 반환해야하며 나머지 이벤트의 경우 아니요를 반환해야합니다. 어떻게 할 수 있습니까?
Minakshi

@Minakshi 그것은 완전히 다른 질문입니다. 새로운 질문을하십시오.
Fattie

그러나이 간단한 접근 방식으로 인해 해당 레이어 위에 버튼 등이 없어야합니다!
Fattie

56

이것은 오래된 스레드이지만 검색시 나왔으므로 2c를 추가 할 것이라고 생각했습니다. 하위보기가있는 덮는 UIView가 있고 하위보기 중 하나에 닿는 터치 만 가로 채기를 원하므로 PixelCloudSt의 답변을 다음과 같이 수정했습니다.

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

1
사용자 정의 UIView 서브 클래스 (또는 UIImageView와 같은 다른 UIView 서브 클래스의 서브 클래스)를 작성하고이 함수를 대체해야합니다. 본질적으로 서브 클래스는 .h 파일에 완전히 비어있는 '@interface'를 가질 수 있으며 위의 함수는 '@implementation'의 전체 내용입니다.
Fresidue

감사합니다. 이것은 UIView의 빈 공간을 통해 터치를 전달하는 데 필요한 것입니다. 뒤의보기로 처리하십시오. +100
Danny

41

@fresidue 답변의 개선 된 버전. 이 UIView서브 클래스는 서브 뷰 외부로 터치를 전달하는 투명한 뷰로 사용할 수 있습니다 . 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 pointInside:[self convertPoint:point toView:view] withEvent:event]) {
            return YES;
        }
    }
    return NO;
}

@end

.

그리고 스위프트에서 :

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

팁:

그렇다면 테이블 뷰가있는 큰 "홀더"패널이 있다고 가정 해보십시오. "홀더"패널을 PassthroughView만듭니다. 이제 작동합니다. "홀더"를 통해 "테이블"을 스크롤 할 수 있습니다.

그러나!

  1. "보유자"패널 위에는 레이블이나 아이콘이 있습니다. 물론 사용자 상호 작용이 활성화 된 것으로 표시되어야합니다.

  2. "홀더"패널 위에는 몇 개의 버튼이 있습니다. 물론 사용자 상호 작용이 활성화 된 것으로 표시되어야합니다.

  3. 참고 사용하는보기 - 다소 혼동은 "홀더"자체 있음 PassthroughView에이 - 표시해야 사용자 상호 작용이 활성화 ON을 ! 그건 ON ! 그렇지 않으면 PassthroughView의 코드는 단순히 호출되지 않습니다.


1
Swift에 제공된 솔루션을 사용했습니다. iOS 11에서 작동하지만 iOS 10에서 매번 충돌이 발생합니다. Bad Access 표시. 어떤 생각?
Soumen

내 일을 저장했습니다. 아래 UIView에 터치를 전달하는 데 효과적이었습니다.
AaoIi

1
잊지 마세요$0.isUserInteractionEnabled
Sean Thielen

7

UIStackView와 비슷한 문제가 있었지만 다른보기 일 수 있습니다. 내 구성은 다음과 같습니다. containerView 및 StackView가있는 View 컨트롤러

측면에 버튼이있는 배경에 배치해야 할 컨테이너가있는 고전적인 경우입니다. 레이아웃 목적으로 UIStackView에 버튼을 포함 시켰지만 이제 stackView의 중간 (빈) 부분이 터치를 차단합니다.

내가 한 것은 터치 할 수있는 subView를 정의하는 속성으로 UIStackView의 서브 클래스를 만드는 것입니다. 이제 사이드 버튼 (* viewsWithActiveTouch * 배열에 포함)을 터치하면 버튼이 표시되고,이 뷰 이외의 다른 곳에서 스택 뷰를 터치하면 가로 채지 않으므로 스택 아래에있는 모든 것에 전달됩니다. 전망.

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

6

터치를 전달하려는 뷰가 서브 뷰 / 슈퍼 뷰가 아닌 경우 UIView 서브 클래스에서 다음과 같이 사용자 정의 속성을 설정할 수 있습니다.

@interface SomeViewSubclass : UIView {

    id forwardableTouchee;

}
@property (retain) id forwardableTouchee;

당신의 .m에서 그것을 합성하십시오 :

@synthesize forwardableTouchee;

그런 다음과 같은 UIResponder 메소드에 다음을 포함하십시오.

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

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

}

UIView를 인스턴스화 할 때마다 forwardableTouchee 속성을 이벤트를 전달할보기로 설정하십시오.

    SomeViewSubclass *view = [[[SomeViewSubclass alloc] initWithFrame:someRect] autorelease];
    view.forwardableTouchee = someOtherView;

4

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

4

UIStackView를 통해 터치를 전달해야했습니다. 내부의 UIView는 투명했지만 UIStackView는 모든 터치를 소비했습니다. 이것은 나를 위해 일했다 :

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

정렬 된 모든 서브 뷰는 여전히 터치를 받지만 UIStackView 자체를 터치하면 아래보기로 이동했습니다 (나에게 mapView).


2

여기에 많은 답변이있는 것처럼 보이지만, 내가 필요로하는 사람은 신속하지 않습니다. 그래서 나는 여기에서 @fresidue의 대답을 가져 와서 현재 대부분의 개발자가 여기에서 사용하고 싶어하기 때문에 신속하게 변환했습니다.

버튼이있는 투명한 툴바가있는 문제를 해결했지만 툴바가 사용자에게 보이지 않게하고 터치 이벤트가 진행되어야합니다.

isUserInteractionEnabled = 일부는 내 테스트를 기반으로 한 옵션이 아니기 때문에 false입니다.

 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
}

1

StackView 안에 몇 가지 레이블이 있었고 위의 솔루션으로는 큰 성공을 거두지 못했습니다. 대신 아래 코드를 사용하여 문제를 해결했습니다.

    let item = ResponsiveLabel()

    // Configure label

    stackView.addArrangedSubview(item)

서브 클래 싱 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
    }

}

그런 다음 다음과 같은 작업을 수행 할 수 있습니다.

class ResponsiveLabel:UILabel{
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // Respond to touch
    }
}

0

이런 식으로 시도하십시오 ...

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

예를 들어 touchesBegan 메서드에서 위의 코드는 터치를 모든 하위 뷰로 전달합니다.


6
이 접근법 AFAIK의 문제점은 UIKit 프레임 워크의 클래스로 터치를 전달하려고 시도해도 효과가 없다는 것입니다. Apple의 문서에 따르면 UIKit 프레임 워크 클래스는 view 속성이 다른보기로 설정된 터치를 수신하도록 설계되지 않았습니다. 따라서이 방법은 주로 UIView의 사용자 정의 서브 클래스에 적용됩니다.
maciejs

0

내가 하려던 상황은 중첩 된 UIStackView 내부의 컨트롤을 사용하여 제어판을 작성하는 것이 었습니다. 일부 컨트롤에는 UIButton이있는 UITextField가 있습니다. 또한 컨트롤을 식별하는 레이블이있었습니다. 내가하고 싶은 것은 제어판 뒤에 큰 "보이지 않는"버튼을 두어 사용자가 버튼이나 텍스트 필드 외부 영역을 두드리면 잡을 수 있고 조치를 취할 수 있도록했습니다. 텍스트가있는 경우 주로 키보드를 닫습니다. 필드가 활성화되었습니다 (resignFirstResponder). 그러나 제어판의 레이블 또는 다른 빈 영역을 두드리면 통과하지 않습니다. 위의 토론은 아래의 답변을 얻는 데 도움이되었습니다.

기본적으로 UIStackView를 서브 클래 싱하고 "point (inside : with) 루틴을 덮어 써서 터치가 필요한 컨트롤 유형을 찾고 무시하려는 레이블과 같은 것을"무시 "합니다. 또한 UIStackView 내부를 확인하여 제어판 구조로 재귀 할 수 있습니다.

코드는 아마도 좀 더 장황하다. 그러나 디버깅에 도움이되었으므로 루틴이 수행하는 작업에 더 많은 명확성을 제공하기를 바랍니다. Interface Builder에서 UIStackView의 클래스를 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
}

}


-1

@PixelCloudStv가 제안한 것처럼 한 뷰에서 다른 뷰로 터치하지만이 프로세스를 추가로 제어하려면 하위 클래스 UIView

//헤더

@interface TouchView : UIView

@property (assign, nonatomic) CGRect activeRect;

@end

//이행

#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

interfaceBuilder에서 View 클래스를 TouchView로 설정하고 rect로 활성 rect를 설정하십시오. 또한 다른 논리를 변경하고 구현할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.