programing

iOS에 대한 이벤트 처리 - 테스트:withEvent:와 pointInside:withEvent:는 관련이 있습니까?

magicmemo 2023. 6. 15. 21:45
반응형

iOS에 대한 이벤트 처리 - 테스트:withEvent:와 pointInside:withEvent:는 관련이 있습니까?

대부분의 애플 문서는 매우 잘 작성되어 있지만, 'iOS용 이벤트 처리 가이드'는 예외라고 생각합니다.저는 거기에 설명된 내용을 명확하게 이해하기가 어렵습니다.

그 문서에는 다음과 같이 쓰여 있습니다.

히트 테스트에서 창이 호출합니다.hitTest:withEvent:보기 계층 구조의 맨 위 보기에서, 이 메서드는 재귀적으로 호출합니다.pointInside:withEvent:YES를 반환하는 보기 계층의 각 보기에서 터치가 발생한 범위 내에서 하위 보기를 찾을 때까지 계층 아래로 이동합니다.그 뷰는 히트 테스트 뷰가 됩니다.

그래서 그것만 그런가요?hitTest:withEvent:시스템에서 최상위 보기를 호출하고 이를 호출합니다.pointInside:withEvent:모든 하위 뷰 중에서 특정 하위 뷰에서 반환된 값이 YES인 경우 호출합니다.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터치가 발생한 하위 보기를 보기 위해 계층을 내려다봅니다.이 경우, 에서A,C그리고.D,그럴 것이다.D.
  4. D히트 테스트 뷰가 될 것입니다.

그것은 꽤 기본적인 질문인 것 같습니다.하지만 저는 그 서류가 다른 서류들보다 명확하지 않다는 것에 동의합니다, 그래서 제 대답은 이렇습니다.

의 구현hitTest:withEvent:UIResponder에서 다음 작업을 수행합니다.

  • 이트 콜pointInside:withEvent:self
  • 반환이 NO이면,hitTest:withEvent:돌아온다nil이야기의 끝
  • 반환이 YES이면 다음을 전송합니다.hitTest:withEvent:메시지를 하위 뷰에 표시합니다.최상위 하위 뷰에서 시작하여 하위 뷰가 다음을 반환할 때까지 다른 뷰로 계속 이동합니다.nil개체 또는 모든 하위 보기가 메시지를 수신합니다.
  • 하위 뷰에서 다음을 반환하는 경우nil처음, 처음에 객체hitTest:withEvent:해당 개체를 반환합니다.이야기의 끝
  • 하지 않는 는 non-between 우nil 첫 객째, 첫번hitTest:withEvent:아온다를 합니다.self

이 프로세스는 반복적으로 반복되므로 일반적으로 보기 계층의 리프 보기가 반환됩니다.

그러나 다음을 무시할 수도 있습니다.hitTest:withEvent뭔가를 다르게 하는 것.의 경우 대분의경우, 재정지부를 합니다.pointInside:withEvent:더 단순하고 여전히 응용 프로그램에서 이벤트 처리를 조정할 수 있는 충분한 옵션을 제공합니다.

iOS에서 이 히트 테스트가 매우 도움이 된다고 생각합니다.

enter image description here

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

Swift 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: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단계의 결과에 따라 자체 또는 0을 반환합니다.

메모,[self.subviewsreverseObjectEnumerator]맨 위에서 맨 아래로 보기 계층 구조를 따라야 합니다.다음 항목을 확인합니다.clipsToBounds마스크된 하위 뷰를 테스트하지 않도록 합니다.

용도:

  1. 하위 분류 보기에서 범주를 가져옵니다.
  2. 를 바꿉니다.hitTest:withEvent:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    return [self overlapHitTest:point withEvent:event];
}

공식 애플 가이드는 몇 가지 좋은 예증도 제공합니다.

이것이 누군가에게 도움이 되기를 바랍니다.

이 스니펫처럼 보여요!

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

iOS 터치

1. User touch
2. event is created
3. hit testing by coordinates - find first responder - UIView and successors (UIWindow) 
 3.1 hit testing - recursive find the most deep view
  3.1.1 point inside - check coordinates
4. Send Touch Event to the First Responder

클래스 다이어그램

3 히트 테스트

찾기First Responder

First Responder 이경가가깊습다니장우▁the▁in다입니다.UIView point()(hitTest()사용하다point()내부적으로) 방법이 참으로 반환되었습니다.그것은 항상 지나갑니다.UIApplication -> UIWindow -> First Responder

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

으로 내적로으부▁internhitTest()처럼 .

func hitTest() -> View? {
    if (isUserInteractionEnabled == false || isHidden == true || alpha == 0 || point() == false) { return nil }

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

4 터치 이벤트를 에 전송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?)

예를 들어 보겠습니다.

응답기 체인

은 일종의 종의입니다.chain of responsibility양식.구성되어 있습니다.UIResponser을 다룰 줄 아는 사람UIEvent이 경우 첫 번째 응답자부터 시작합니다. 첫 번째 응답자는 다음을 무시합니다.touch....super.touch...를 합니다.

Responder chain는 에도사다니됩용에서도 됩니다.addTarget또는sendAction이벤트

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

*isExclusiveTouch를 처리할 때 됩니다.

[Android on Touch]

@lion의 토막글은 매력적으로 작동합니다.나는 그것을 swift 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
    }
}

이를 사용하려면 다음과 같이 uview에서 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

반응형