내 문제 :EditView
기본적으로 전체 애플리케이션 프레임을 차지하는 수퍼 뷰 와 MenuView
하단 ~ 20 % 만 차지하는 MenuView
하위 뷰가 ButtonView
있으며 실제로 MenuView
의 경계 밖에있는 자체 하위 뷰 를 포함합니다 (예 🙂 ButtonView.frame.origin.y = -100
.
(참고 : 의 뷰 계층 구조의 EditView
일부가 MenuView
아니지만 답변에 영향을 미칠 수있는 다른 하위 뷰가 있습니다 .)
이미이 문제를 알고있을 것입니다 ButtonView
. 범위 내에 있을 때 MenuView
(또는 더 구체적으로 내 터치가 MenuView
의 범위 내에있을 때 ) ButtonView
는 터치 이벤트에 응답합니다. 내 터치가 MenuView
의 범위를 벗어 났지만 여전히 ButtonView
의 범위 내에 있으면에서 터치 이벤트를받지 않습니다 ButtonView
.
예:
- (E)는
EditView
모든보기의 상위입니다. - (M)은
MenuView
EditView의 하위보기입니다. - (B)는
ButtonView
MenuView의 하위보기입니다.
도표:
+------------------------------+
|E |
| |
| |
| |
| |
|+-----+ |
||B | |
|+-----+ |
|+----------------------------+|
||M ||
|| ||
|+----------------------------+|
+------------------------------+
(B)가 (M)의 프레임 밖에 있기 때문에 (B) 영역의 탭은 (M)으로 전송되지 않습니다. 사실, (M)은이 경우 터치를 분석하지 않으며 터치는 계층 구조의 다음 개체.
목표 : 재정의 hitTest:withEvent:
가이 문제를 해결할 수 있다고 생각 하지만 정확히 어떻게해야하는지 모르겠습니다. 제 경우에는 (내 ‘마스터’수퍼 뷰) hitTest:withEvent:
에서 재정의 해야 EditView
합니까? 아니면 MenuView
터치를받지 않는 버튼의 직접 수퍼 뷰 인 에서 재정의해야 합니까? 아니면 이것에 대해 잘못 생각하고 있습니까?
긴 설명이 필요하다면 좋은 온라인 리소스가 도움이 될 것입니다. Apple의 UIView 문서를 제외하고는 분명하지 않습니다.
감사!
답변
나는 수락 된 답변의 코드를보다 일반적으로 수정했습니다.보기가 하위보기를 경계로 자르고 숨길 수 있으며 더 중요한 경우를 처리합니다. 하위보기가 복잡한보기 계층 인 경우 올바른 하위보기가 반환됩니다.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (self.clipsToBounds) {
return nil;
}
if (self.hidden) {
return nil;
}
if (self.alpha == 0) {
return nil;
}
for (UIView *subview in self.subviews.reverseObjectEnumerator) {
CGPoint subPoint = [subview convertPoint:point fromView:self];
UIView *result = [subview hitTest:subPoint withEvent:event];
if (result) {
return result;
}
}
return nil;
}
SWIFT 3
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if clipsToBounds || isHidden || alpha == 0 {
return nil
}
for subview in subviews.reversed() {
let subPoint = subview.convert(point, from: self)
if let result = subview.hitTest(subPoint, with: event) {
return result
}
}
return nil
}
더 복잡한 사용 사례에이 솔루션을 사용하려는 모든 사람에게 도움이되기를 바랍니다.
답변
좋아, 나는 약간의 파고와 테스트를 hitTest:withEvent
했다. 적어도 높은 수준에서 작동 하는 방법 은 다음과 같다. 이 시나리오 이미지 :
- (E)는 모든 뷰의 부모 인 EditView입니다 .
- (M)은 EditView의 하위보기 인 MenuView 입니다.
- (B)는 MenuView의 하위보기 인 ButtonView 입니다.
도표:
+------------------------------+
|E |
| |
| |
| |
| |
|+-----+ |
||B | |
|+-----+ |
|+----------------------------+|
||M ||
|| ||
|+----------------------------+|
+------------------------------+
(B)가 (M)의 프레임 밖에 있기 때문에 (B) 영역의 탭은 (M)으로 전송되지 않습니다. 사실, (M)은이 경우 터치를 분석하지 않으며 터치는 계층 구조의 다음 개체.
그러나 hitTest:withEvent:
(M)에서 구현 하면 응용 프로그램의 아무 곳이나 탭이 (M)로 전송됩니다 (또는 최소한 알고 있음). 이 경우 터치를 처리하고 터치를 받아야하는 객체를 반환하는 코드를 작성할 수 있습니다.
더 구체적으로 말하면 목표 hitTest:withEvent:
는 히트를 받아야하는 객체를 반환하는 것입니다. 따라서 (M)에서 다음과 같은 코드를 작성할 수 있습니다.
// need this to capture button taps since they are outside of self.frame
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
for (UIView *subview in self.subviews) {
if (CGRectContainsPoint(subview.frame, point)) {
return subview;
}
}
// use this to pass the 'touch' onward in case no subviews trigger the touch
return [super hitTest:point withEvent:event];
}
나는 여전히이 방법 과이 문제에 대해 매우 생소하므로 코드를 작성하는 더 효율적이거나 올바른 방법이 있으면 의견을 말하십시오.
나중에이 질문에 답하는 다른 사람에게 도움이되기를 바랍니다. 🙂
답변
Swift 5에서
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard !clipsToBounds && !isHidden && alpha > 0 else { return nil }
for member in subviews.reversed() {
let subPoint = member.convert(point, from: self)
guard let result = member.hitTest(subPoint, with: event) else { continue }
return result
}
return nil
}
답변
내가 할 일은 ButtonView와 MenuView를 모두 프레임에 완전히 맞는 컨테이너에 배치하여 뷰 계층 구조에서 동일한 수준에 존재하도록하는 것입니다. 이렇게하면 잘린 항목의 대화 형 영역이 수퍼 뷰의 경계로 인해 무시되지 않습니다.
답변
상위 뷰 내에 다른 하위 뷰가 많이있는 경우 위의 솔루션을 사용하면 대부분의 다른 대화 형 뷰가 작동하지 않을 것입니다.이 경우 다음과 같은 것을 사용할 수 있습니다 (Swift 3.2에서).
class BoundingSubviewsViewExtension: UIView {
@IBOutlet var targetView: UIView!
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// Convert the point to the target view's coordinate system.
// The target view isn't necessarily the immediate subview
let pointForTargetView: CGPoint? = targetView?.convert(point, from: self)
if (targetView?.bounds.contains(pointForTargetView!))! {
// The target view may have its view hierarchy,
// so call its hitTest method to return the right hit-test view
return targetView?.hitTest(pointForTargetView ?? CGPoint.zero, with: event)
}
return super.hitTest(point, with: event)
}
}
답변
누구든지 그것을 필요로한다면 여기에 신속한 대안이 있습니다.
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
if !self.clipsToBounds && !self.hidden && self.alpha > 0 {
for subview in self.subviews.reverse() {
let subPoint = subview.convertPoint(point, fromView:self);
if let result = subview.hitTest(subPoint, withEvent:event) {
return result;
}
}
}
return nil
}
답변
보기 계층 구조에 코드 줄 아래에 배치하십시오.
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
UIView* hitView = [super hitTest:point withEvent:event];
if (hitView != nil)
{
[self.superview bringSubviewToFront:self];
}
return hitView;
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event
{
CGRect rect = self.bounds;
BOOL isInside = CGRectContainsPoint(rect, point);
if(!isInside)
{
for (UIView *view in self.subviews)
{
isInside = CGRectContainsPoint(view.frame, point);
if(isInside)
break;
}
}
return isInside;
}
더 자세한 설명을 위해 내 블로그 “goaheadwithiphonetech”에서 “사용자 정의 콜 아웃 : 버튼을 클릭 할 수없는 문제”에 대해 설명했습니다.
도움이 되었기를 바랍니다 … !!!