UIView의 경계를 넘어서는 상호 작용


94

UIButton의 프레임이 부모의 프레임 밖에있을 때 UIButton (또는 그 문제에 대한 다른 컨트롤)이 터치 이벤트를받을 수 있습니까? 이 작업을 시도하면 UIButton이 이벤트를 수신 할 수없는 것 같습니다. 이 문제를 어떻게 해결합니까?


1
이것은 나를 위해 완벽하게 작동합니다. developer.apple.com/library/ios/qa/qa2013/qa1812.html Best
Frank Li

2
이것은 또한 좋은 관련 (혹은 속는 사람)입니다 : stackoverflow.com/a/31420820/8047
댄 Rosenstark는

답변:


94

예. hitTest:withEvent:메서드를 재정 의하여 해당 뷰에 포함 된 것보다 더 큰 점 집합에 대한 뷰를 반환 할 수 있습니다. 참고 항목 에 UIView 클래스 참조 .

편집 : 예 :

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat radius = 100.0;
    CGRect frame = CGRectMake(-radius, -radius,
                              self.frame.size.width + radius,
                              self.frame.size.height + radius);

    if (CGRectContainsPoint(frame, point)) {
        return self;
    }
    return nil;
}

편집 2 : (설명 후 :) 버튼이 부모의 경계 내에있는 것으로 처리되도록 pointInside:withEvent:하려면 버튼의 프레임을 포함하도록 부모에서 재정의해야합니다 .

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    if (CGRectContainsPoint(self.view.bounds, point) ||
        CGRectContainsPoint(button.view.frame, point))
    {
        return YES;
    }
    return NO;
}

참고 pointInside을 무시하는 것은 매우 정확하지 않습니다에 대한 그냥 거기에 코드를. 아래에 Summon이 설명하는대로 다음과 같이하십시오.

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
    {
    if ( CGRectContainsPoint(self.oversizeButton.frame, point) )
        return YES;

    return [super pointInside:point withEvent:event];
    }

self.oversizeButton이 UIView 하위 클래스에서 IBOutlet으로 수행 할 가능성이 매우 높습니다 . 그런 다음 문제의 "크기 초과 버튼"을 문제의 특수보기로 드래그 할 수 있습니다. (또는 어떤 이유로 프로젝트에서이 작업을 많이 수행했다면 특별한 UIButton 하위 클래스가 있고 해당 클래스에 대한 하위 뷰 목록을 살펴볼 수 있습니다.) 도움이되기를 바랍니다.


샘플 코드를 사용하여이를 올바르게 구현하는 데 도움을 줄 수 있습니까? 또한 참조를 읽었으며 다음 줄 때문에 혼란스러워했습니다. "수신자의 경계 밖에있는 포인트는 실제로 수신자의 하위보기 중 하나에 있더라도 적중으로보고되지 않습니다. 하위보기는 시각적으로 경계를 넘어 확장 될 수 있습니다. 부모보기의 clipsToBounds 속성이 NO로 설정되어 있으면 부모보기의 경계를 벗어난 지점을 항상 무시합니다. "
ThomasM

특히이 부분이 내가 필요한 솔루션이 아닌 것처럼 보이게 만드는 부분입니다. "하지만 적중 테스트는 항상 상위 뷰의 범위를 벗어난 지점을 무시합니다.".하지만 당연히 잘못된 것일 수 있습니다. 애플 참조가 때때로 정말 혼란스러워집니다. ..
ThomasM 2011 년

아, 이제 이해합니다. pointInside:withEvent:버튼의 프레임을 포함하려면 부모를 재정의해야 합니다. 위의 답변에 샘플 코드를 추가하겠습니다.
jnic 2011 년

UIView의 메서드를 재정의하지 않고이를 수행하는 방법이 있습니까? 예를 들어 뷰가 UIKit에 대해 완전히 일반화되고 Nib을 통해 초기화 된 경우 UIViewController 내에서 이러한 메서드를 재정의 할 수 있습니까?
RLH

1
hitTest:withEvent:pointInside:withEvent:지역 내가 버튼을 누를 때 나를 위해라는 부모의 경계의 거짓말의 외부 그.하지 무엇이 잘못 될 수 있습니까?
iosdude

24

@jnic, 저는 iOS SDK 5.0에서 작업 중이며 코드가 올바르게 작동하도록하려면 다음을 수행해야합니다.

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
if (CGRectContainsPoint(button.frame, point)) {
    return YES;
}
return [super pointInside:point withEvent:event]; }

필자의 경우 컨테이너 뷰는 UIButton이고 모든 자식 요소도 부모 UIButton의 경계 밖으로 이동할 수있는 UIButton입니다.

베스트


20

상위보기에서 적중 테스트 방법을 재정의 할 수 있습니다.

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    CGPoint translatedPoint = [_myButton convertPoint:point fromView:self];

    if (CGRectContainsPoint(_myButton.bounds, translatedPoint)) {
        return [_myButton hitTest:translatedPoint withEvent:event];
    }
    return [super hitTest:point withEvent:event];

}

이 경우 포인트가 버튼 범위 내에 있으면 해당 지점에서 통화를 착신 전환합니다. 그렇지 않은 경우 원래 구현으로 되돌립니다.


18

제 경우 UICollectionViewCell에는 UIButton. 나는 clipsToBounds셀을 비활성화 했고 버튼은 셀 경계 밖에서 보였습니다. 그러나 버튼이 터치 이벤트를받지 못했습니다. Jack의 답변 ( https://stackoverflow.com/a/30431157/3344977) 을 사용하여 버튼의 터치 이벤트를 감지 할 수있었습니다.

다음은 Swift 버전입니다.

override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {

    let translatedPoint = button.convertPoint(point, fromView: self)

    if (CGRectContainsPoint(button.bounds, translatedPoint)) {
        print("Your button was pressed")
        return button.hitTest(translatedPoint, withEvent: event)
    }
    return super.hitTest(point, withEvent: event)
}

스위프트 4 :

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

    let translatedPoint = button.convert(point, from: self)

    if (button.bounds.contains(translatedPoint)) {
        print("Your button was pressed")
        return button.hitTest(translatedPoint, with: event)
    }
    return super.hitTest(point, with: event)
}

그것은 정확히 나의 시나리오였습니다. 다른 점은이 메서드를 CollectionViewCell Class 안에 넣어야한다는 것입니다.
Hamid Reza Ansari

그러나 재정의 된 메서드에 버튼을 삽입하는 이유는 무엇입니까 ???
rommex

10

왜 이런 일이 발생합니까?

이는 서브 뷰가 수퍼 뷰의 경계 밖에있을 때 해당 서브 뷰에서 실제로 발생하는 터치 이벤트가 해당 서브 뷰로 전달되지 않기 때문입니다. 그러나, 그것은 것입니다 그 슈퍼 뷰에 전달 될 수있다.

하위 뷰가 시각적으로 잘리는 지 여부에 관계없이 터치 이벤트는 항상 대상 뷰 수퍼 뷰의 경계 사각형을 따릅니다. 즉, 수퍼 뷰의 경계 사각형 밖에있는 뷰의 일부에서 발생하는 터치 이벤트는 해당 뷰로 전달되지 않습니다. 링크

무엇을해야합니까?

수퍼 뷰가 위에서 언급 한 터치 이벤트를 수신하면 UIKit에 내 서브 뷰가이 터치 이벤트를 수신 할 수 있어야한다고 명시 적으로 알려야합니다.

코드는 어떻습니까?

수퍼 뷰에서 func hitTest(_ point: CGPoint, with event: UIEvent?)

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        if isHidden || alpha == 0 || clipsToBounds { return super.hitTest(point, with: event) }
        // convert the point into subview's coordinate system
        let subviewPoint = self.convert(point, to: subview)
        // if the converted point lies in subview's bound, tell UIKit that subview should be the one that receives this event
        if !subview.isHidden && subview.bounds.contains(subviewPoint) { return subview }
        return super.hitTest(point, with: event)
    }

매혹적인 고치 야 : "가장 높은 너무 작은 슈퍼 뷰"로 가야합니다

문제보기가 외부에있는 "가장 높은"보기로 "위로"이동해야합니다.

전형적인 예 :

컨테이너 뷰 C가있는 화면 S가 있다고 가정 해 보겠습니다. 컨테이너 뷰 컨트롤러의 뷰는 V입니다. (리콜 V는 C 내부에 있으며 동일한 크기입니다.) V에는 하위 뷰가 있습니다 (아마도 버튼 일 수 있음) B. B가 문제입니다. 실제로 V 외부에있는보기입니다.

그러나 B도 C 외부에 있습니다.

이 예에서는 override hitTest실제로 솔루션 을 V가 아닌 C 에 적용해야합니다 . V에 적용하면 아무것도하지 않습니다.


4
감사합니다! 나는 이것으로 인한 문제에 몇 시간을 보냈다. 시간을내어 게시 해 주셔서 감사합니다. :)
ClayJ

@ScottZhu : 나는 당신의 대답에 중요한 gotchya를 추가했습니다. 물론 내 추가 항목을 삭제하거나 변경해도됩니다. 다시 한 번 감사드립니다!
Fattie

9

빠른:

여기서 targetView는 이벤트를 수신하려는 뷰입니다 (전체 또는 부분적으로 상위 뷰의 프레임 외부에 위치 할 수 있음).

override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        let pointForTargetView: CGPoint = targetView.convertPoint(point, fromView: self)
        if CGRectContainsPoint(targetView.bounds, pointForTargetView) {
            return closeButton
        }
        return super.hitTest(point, withEvent: event)
}

5

나를 위해 (다른 사람)을, 대답이었다 하지 재정의하는 hitTest방법, 오히려 pointInside방법을. 나는 단지 두 곳에서만 이것을해야했다.

The Superview 이것이 clipsToBounds참으로 설정하면이 모든 문제가 사라질 것이라는 견해입니다 . 그래서 여기 UIViewSwift 3의 간단한 것이 있습니다 .

class PromiscuousView : UIView {
    override func point(inside point: CGPoint,
               with event: UIEvent?) -> Bool {
        return true
    }
} 

서브 뷰 마일리지는 다를 수 있지만, 제 경우 pointInside에는 터치가 범위를 벗어났다고 결정한 서브 뷰에 대한 방법 도 덮어 써야 했습니다. 광산은 Objective-C에 있으므로 :

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    WEPopoverController *controller = (id) self.delegate;
    if (self.takeHitsOutside) {
        return YES;
    } else {
        return [super pointInside:point withEvent:event];
    }
}

이 답변 (및 같은 질문에 대한 몇 가지 다른 답변)은 이해를 명확히하는 데 도움이 될 수 있습니다.


2

swift 4.1 업데이트

동일한 UIView에서 터치 이벤트를 얻으려면 다음과 같은 재정의 메서드를 추가하기 만하면됩니다. UIView에서 탭된 특정 뷰를 식별합니다.

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