iOS 용 이벤트 처리-hitTest : withEvent : 및 pointInside : withEvent :는 어떤 관련이 있습니까?


145

대부분의 Apple 문서는 잘 작성되었지만 ' iOS 용 이벤트 처리 안내서 '는 예외입니다. 거기에 설명 된 내용을 명확하게 이해하기는 어렵습니다.

문서는 말합니다

적중 테스트에서 창 hitTest:withEvent:은보기 계층의 최상위보기를 호출 합니다. 이 메소드 pointInside:withEvent:는 YES를 리턴하는보기 계층 구조의 각보기를 재귀 적으로 호출 하여 터치가 발생한 범위 내에서 하위보기를 찾을 때까지 계층 구조를 진행합니다. 해당보기는 적중 테스트보기가됩니다.

따라서 hitTest:withEvent:최상위 뷰만 시스템에서 호출합니다 pointInside:withEvent:. 모든 하위 뷰를 호출하고 특정 하위 뷰에서 리턴이 YES 인 경우 pointInside:withEvent:해당 하위 뷰의 서브 클래스 를 호출 합니까?


3
저를 도와 아주 좋은 튜토리얼 링크
anneblue

답변:


173

꽤 기본적인 질문 인 것 같습니다. 그러나 나는 당신에게 문서가 다른 문서만큼 명확하지 않다는 것에 동의합니다. 그래서 여기에 내 대답이 있습니다.

hitTest:withEvent:UIResponder 의 구현은 다음을 수행합니다.

  • 이 호출 pointInside:withEvent:self
  • 반환 값이 NO이면를 hitTest:withEvent:반환합니다 nil. 이야기의 끝.
  • 리턴이 YES 인 경우 hitTest:withEvent:서브 뷰에 메시지를 보냅니다 . 최상위 하위 뷰에서 시작하여 하위 뷰가 nil객체 가 아닌 객체를 반환 하거나 모든 하위 뷰가 메시지를받을 때까지 다른 뷰로 계속 진행됩니다 .
  • 하위 뷰가 nil처음에 객체 가 아닌 객체를 hitTest:withEvent:반환 하면 첫 번째 객체는 해당 객체를 반환합니다. 이야기의 끝.
  • 하위 뷰가 nil객체 가 아닌 객체를 반환하지 않으면 첫 번째 뷰 hitTest:withEvent:self

이 프로세스는 재귀 적으로 반복되므로 일반적으로 뷰 계층의 리프 뷰가 결국 반환됩니다.

그러나 hitTest:withEvent다르게 수행하도록 재정의 할 수 있습니다 . 대부분의 경우 재정의 pointInside:withEvent:가 더 간단하고 응용 프로그램에서 이벤트 처리를 조정할 수있는 충분한 옵션을 제공합니다.


hitTest:withEvent:모든 하위 뷰가 결국 실행 된다는 의미 입니까?
realstuff02

2
예. hitTest:withEvent:뷰에서 재정의 하고 ( pointInside원하는 경우) 로그를 인쇄하고 호출 [super hitTest...하여 누가 hitTest:withEvent:어떤 순서로 호출되는지 확인하십시오.
MHC

withEvent : 리턴이 YES 인 경우 "언급 경우 3 단계 안, 그것은 그러나 hitTest는 전송 ... 그것은 pointInside 안 : withEvent 내가 모든 파단에 pointInside를 전송 생각?
prostock를

2 월에 처음으로 hitTest : withEvent :를 보냈는데, pointInside : withEvent :가 자신에게 전송되었습니다. 다음 SDK 버전으로이 동작을 다시 확인하지는 않았지만 hitTest : withEvent :를 보내는 것은 이벤트가 뷰에 속하는지 여부에 대한 높은 수준의 제어를 제공하기 때문에 더 의미가 있다고 생각합니다. pointInside : withEvent : 이벤트 위치가 뷰에 있는지 여부를 나타내며 이벤트가 뷰에 속하는지 여부를 알려줍니다. 예를 들어, 서브 뷰는 위치가 서브 뷰에 있더라도 이벤트를 처리하지 않을 수 있습니다.
MHC

1
WWDC2014 Session 235-Advanced Scrollviews and Touch Handling Techniques는이 문제에 대한 훌륭한 설명과 예를 제공합니다.
antonio081014

297

서브 클래스와 뷰 계층을 혼동하고 있다고 생각합니다. 의사가 말하는 것은 다음과 같습니다. 이 뷰 계층 구조가 있다고 가정하십시오. 계층 구조로 클래스 계층 구조에 대해 이야기하지 않고 다음과 같이 뷰 계층 구조 내의 뷰를 말합니다.

+----------------------------+
|A                           |
|+--------+   +------------+ |
||B       |   |C           | |
||        |   |+----------+| |
|+--------+   ||D         || |
|             |+----------+| |
|             +------------+ |
+----------------------------+

손가락을 안에 넣었다고 가정 해보십시오 D. 다음과 같은 일이 발생합니다.

  1. hitTest:withEvent:A뷰 계층 구조의 최상위 뷰 에서에 호출됩니다 .
  2. pointInside:withEvent: 각 뷰에서 재귀 적으로 호출됩니다.
    1. pointInside:withEvent:에 호출되어 다음 A을 반환합니다.YES
    2. pointInside:withEvent:에 호출되어 다음 B을 반환합니다.NO
    3. pointInside:withEvent:에 호출되어 다음 C을 반환합니다.YES
    4. pointInside:withEvent:에 호출되어 다음 D을 반환합니다.YES
  3. 반환 된보기 YES에서는 계층 구조를 내려다보고 터치가 발생한 하위보기를 봅니다. 이 경우 from A, CD이됩니다 D.
  4. D 적중 테스트보기입니다

답변 감사합니다. 당신이 묘사 한 것은 또한 내 마음 속에있는 것이지만 @MHC는 hitTest:withEvent:B, C 및 D에 대해서도 말합니다 . D가 A가 아닌 C의 하위 뷰인 경우 어떻게됩니까? 내가 혼란스러워 생각합니다 ...
realstuff02

2
내 그림에서 D는 C의 하위 뷰입니다.
pgb

1
하지 않을까요 A반환 YES뿐만 아니라, 마찬가지로 CD합니까?
Martin Wickman 2016 년

2
.hidden 또는 0.1 미만의 불투명도로 보이지 않거나 사용자 상호 작용이 해제 된 뷰는 hitTest에 절대 응답하지 않는다는 것을 잊지 마십시오. 나는이 객체에서 hitTest가 처음부터 호출되지 않는다고 생각합니다.
Jonny

계층 구조에 따라 모든보기에서 hitTest : withEvent :가 호출 될 수 있음을 추가하고 싶었습니다.
Adithya

47

iOS 에서이 적중 테스트 가 매우 유용하다는 것을 알았습니다.

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

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
        return nil;
    }
    if ([self pointInside:point withEvent:event]) {
        for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
            CGPoint convertedPoint = [subview convertPoint:point fromView:self];
            UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
            if (hitTestView) {
                return hitTestView;
            }
        }
        return self;
    }
    return nil;
}

스위프트 4 편집 :

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    if self.point(inside: point, with: event) {
        return super.hitTest(point, with: event)
    }
    guard isUserInteractionEnabled, !isHidden, alpha > 0 else {
        return nil
    }

    for subview in subviews.reversed() {
        let convertedPoint = subview.convert(point, from: self)
        if let hitView = subview.hitTest(convertedPoint, with: event) {
            return hitView
        }
    }
    return nil
}

따라서 이것을 UIView의 서브 클래스에 추가하고 계층의 모든 뷰가 상속해야합니까?
Guig

21

답변 주셔서 감사합니다, 그들은 "오버레이"보기로 상황을 해결하는 데 도움이되었습니다.

+----------------------------+
|A +--------+                |
|  |B  +------------------+  |
|  |   |C            X    |  |
|  |   +------------------+  |
|  |        |                |
|  +--------+                | 
|                            |
+----------------------------+

가정 X-사용자의 터치. pointInside:withEvent:on B은 리턴 NO하므로 hitTest:withEvent:리턴합니다 A. UIView가장 잘 보이는 화면에서 터치를 받아야 할 때 문제를 처리하기 위해 카테고리를 작성했습니다 .

- (UIView *)overlapHitTest:(CGPoint)point withEvent:(UIEvent *)event {
    // 1
    if (!self.userInteractionEnabled || [self isHidden] || self.alpha == 0)
        return nil;

    // 2
    UIView *hitView = self;
    if (![self pointInside:point withEvent:event]) {
        if (self.clipsToBounds) return nil;
        else hitView = nil;
    }

    // 3
    for (UIView *subview in [self.subviewsreverseObjectEnumerator]) {
        CGPoint insideSubview = [self convertPoint:point toView:subview];
        UIView *sview = [subview overlapHitTest:insideSubview withEvent:event];
        if (sview) return sview;
    }

    // 4
    return hitView;
}
  1. 숨겨 지거나 투명한보기 또는로 userInteractionEnabled설정된 보기의 경우 터치 이벤트를 보내면 안됩니다 NO.
  2. 터치 내부의 경우 self, self잠재적 인 결과로 간주됩니다.
  3. 적중이 있는지 모든 서브 뷰를 재귀 적으로 점검하십시오. 있는 경우 반환하십시오.
  4. 그렇지 않으면 2 단계의 결과에 따라 self 또는 nil을 리턴하십시오.

참고, [self.subviewsreverseObjectEnumerator]아래로 가장 상단에서 뷰 계층 구조를 따라 할 필요가 있었다. clipsToBounds마스크 된 서브 뷰를 테스트하지 않는지 확인하십시오 .

용법:

  1. 하위 클래스보기에서 카테고리를 가져옵니다.
  2. 대체 hitTest:withEvent:이 함께
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    return [self overlapHitTest:point withEvent:event];
}

공식 Apple 's Guide 도 좋은 그림을 제공합니다.

이것이 누군가를 돕기를 바랍니다.


놀랄 만한! 명확한 논리와 훌륭한 코드 스 니펫에 감사드립니다.
톰슨

@ 라이온, 좋은 답변. 또한 첫 번째 단계에서 색상이 선명하도록 평등을 확인할 수 있습니다.
aquarium_moose

3

이 스 니펫과 같이 표시됩니다!

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    if (self.hidden || !self.userInteractionEnabled || self.alpha < 0.01)
    {
        return nil;
    }

    if (![self pointInside:point withEvent:event])
    {
        return nil;
    }

    __block UIView *hitView = self;

    [self.subViews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {   

        CGPoint thePoint = [self convertPoint:point toView:obj];

        UIView *theSubHitView = [obj hitTest:thePoint withEvent:event];

        if (theSubHitView != nil)
        {
            hitView = theSubHitView;

            *stop = YES;
        }

    }];

    return hitView;
}

나는 이것이 가장 이해하기 쉬운 대답을 찾았으며 실제 행동에 대한 나의 관찰과 매우 밀접하게 일치합니다. 유일한 차이점은 하위 뷰가 역순으로 나열되므로 전면에 더 가까운 하위 뷰는 그 뒤에있는 형제보다 우선 터치를 수신한다는 것입니다.
Douglas Hill

귀하의 정정에 감사드립니다. 안부
하마

1

@lion의 스 니펫은 매력처럼 작동합니다. 나는 그것을 스위프트 2.1로 포팅하고 UIView의 확장으로 사용했다. 누군가가 필요로하는 경우를 대비하여 여기에 게시하고 있습니다.

extension UIView {
    func overlapHitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        // 1
        if !self.userInteractionEnabled || self.hidden || self.alpha == 0 {
            return nil
        }
        //2
        var hitView: UIView? = self
        if !self.pointInside(point, withEvent: event) {
            if self.clipsToBounds {
                return nil
            } else {
                hitView = nil
            }
        }
        //3
        for subview in self.subviews.reverse() {
            let insideSubview = self.convertPoint(point, toView: subview)
            if let sview = subview.overlapHitTest(insideSubview, withEvent: event) {
                return sview
            }
        }
        return hitView
    }
}

그것을 사용하려면 다음과 같이 uiview에서 hitTest : point : withEvent를 재정의하십시오.

override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
    let uiview = super.hitTest(point, withEvent: event)
    print("hittest",uiview)
    return overlapHitTest(point, withEvent: event)
}

0

클래스 다이어그램

적중 테스트

을 찾다 First Responder

First Responder이 경우 가장 깊은 UIView point()방법은 true를 반환 한 것입니다.

func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
func point(inside point: CGPoint, with event: UIEvent?) -> Bool

내부적 hitTest()처럼 보인다

hitTest() {

    if (isUserInteractionEnabled == false || isHidden == true || alpha == 0 || point() == false) { return nil }

    for subview in subviews {
        if subview.hitTest() != nil {
            return subview
        }
    }

    return nil

}

에 터치 이벤트 보내기 First Responder

//UIApplication.shared.sendEvent()

//UIApplication, UIWindow
func sendEvent(_ event: UIEvent)

//UIResponder
func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?)

예를 보자

응답자 체인

//UIApplication.shared.sendAction()
func sendAction(_ action: Selector, to target: Any?, from sender: Any?, for event: UIEvent?) -> Bool

예를 살펴보십시오

class AppDelegate: UIResponder, UIApplicationDelegate {
    @objc
    func foo() {
        //this method is called using Responder Chain
        print("foo") //foo
    }
}

class ViewController: UIViewController {
    func send() {
        UIApplication.shared.sendAction(#selector(AppDelegate.foo), to: nil, from: view1, for: nil)
    }
}

[Android onTouch]

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