IT story

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

hot-time 2020. 9. 18. 19:26
반응형

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


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


예. 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 하위 클래스가 있고 해당 클래스에 대한 하위 뷰 목록을 살펴볼 수 있습니다.) 도움이되기를 바랍니다.


@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입니다.

베스트


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

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

}

In this case, if the point falls within the bounds of your button, you forward the call there; if not, revert to the original implementation.


In my case, I had a UICollectionViewCell subclass that contained a UIButton. I disabled clipsToBounds on the cell and the button was visible outside of the cell's bounds. However, the button was not receiving touch events. I was able to detect the touch events on the button by using Jack's answer: https://stackoverflow.com/a/30431157/3344977

Here's a Swift version:

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

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

Swift:

Where targetView is the view you wish to receive the event (which may be entirely or partially located outside of the frame of its parent view).

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

Why this is happening?

This is because when your subview lies outside of your superview's bounds, touch events that actually happens on that subview will not be delivered to that subview. However, it WILL be delivered to its superview.

Regardless of whether or not subviews are clipped visually, touch events always respect the bounds rectangle of the target view’s superview. In other words, touch events occurring in a part of a view that lies outside of its superview’s bounds rectangle are not delivered to that view. Link

What you need to do?

When your superview receives the touch event mentioned above, you'll need to tell UIKit explicitly that my subview should be the one to receive this touch event.

What about the code?

In your superview, implement 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)
    }

Fascinating gotchya: you must go to the "highest too-small superview"

You have to go "up" to the "highest" view which the problem view is outside.

Typical example:

Say you have a screen S, with a container view C. The container viewcontroller's view is V. (Recall V will sit inside of C and be the identical size.) V has a subview (maybe a button) B. B is the problem view which is actually outside of V.

But note that B is also outside of C.

In this example you have to apply the solution override hitTest in fact to C, not to V. If you apply it to V - it does nothing.


For me (as for others), the answer was not to override the hitTest method, but rather the pointInside method. I had to do this in only two places.

The Superview This is the view that if it had clipsToBounds set to true, it would make this whole problem disappear. So here's my simple UIView in Swift 3:

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

The Subview Your mileage may vary, but in my case I also had to overwrite the pointInside method for the subview that was had decided the touch was out of bounds. Mine is in Objective-C, so:

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

This answer (and a few others on the same question) can help clear up your understanding.


swift 4.1 updated

To get touch events in same UIView you just need to add following override method, it will identify specific view tapped in UIView which is placed out of bound

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

참고URL : https://stackoverflow.com/questions/5432995/interaction-beyond-bounds-of-uiview

반응형