IT story

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

hot-time 2020. 6. 17. 07:49
반응형

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


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

문서는 말합니다

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

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


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

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:가 더 간단하고 여전히 응용 프로그램에서 이벤트 처리를 조정할 수있는 충분한 옵션을 제공합니다.


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

+----------------------------+
|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 적중 테스트보기입니다

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
}

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

+----------------------------+
|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. We should not send touch events for hidden or transparent views, or views with userInteractionEnabled set to NO;
  2. If touch is inside self, self will be considered as potential result.
  3. Check recursively all subviews for hit. If any, return it.
  4. Else return self or nil depending on result from step 2.

Note, [self.subviewsreverseObjectEnumerator] needed to follow view hierarchy from top most to bottom. And check for clipsToBounds to ensure not to test masked subviews.

Usage:

  1. Import category in your subclassed view.
  2. Replace hitTest:withEvent: with this
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    return [self overlapHitTest:point withEvent:event];
}

Official Apple's Guide provides some good illustrations too.

Hope this helps somebody.


It shows like this snippet!

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

The snippet of @lion works like a charm. I ported it to swift 2.1 and used it as an extension to UIView. I'm posting it here in case somebody needs it.

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

참고 URL : https://stackoverflow.com/questions/4961386/event-handling-for-ios-how-hittestwithevent-and-pointinsidewithevent-are-r

반응형