hitTest : withEvent를 사용하여 수퍼 뷰 프레임 외부의 하위 뷰에 대한 터치 캡처 :
내 문제 :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
}
I hope this helps anyone trying to use this solution for more complex use cases.
Ok, I did some digging and testing, here's how hitTest:withEvent
works - at least at a high level. Image this scenario:
- (E) is EditView, the parent of all views
- (M) is MenuView, a subview of EditView
- (B) is ButtonView, a subview of MenuView
Diagram:
+------------------------------+
|E |
| |
| |
| |
| |
|+-----+ |
||B | |
|+-----+ |
|+----------------------------+|
||M ||
|| ||
|+----------------------------+|
+------------------------------+
Because (B) is outside (M)'s frame, a tap in the (B) region will never be sent to (M) - in fact, (M) never analyzes the touch in this case, and the touch is sent to the next object in the hierarchy.
However, if you implement hitTest:withEvent:
in (M), taps anywhere in in the application will be sent to (M) (or it least it knows about them). You can write code to handle the touch in that case and return the object that should receive the touch.
More specifically: the goal of hitTest:withEvent:
is to return the object that should receive the hit. So, in (M) you might write code like this:
// 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];
}
I am still very new to this method and this problem, so if there are more efficient or correct ways to write the code, please comment.
I hope that helps anyone else who hits this question later. :)
In 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
}
What I would do is have both the ButtonView and MenuView exist at the same level in the view hierarchy by placing them both in a container whose frame completely fits both of them. This way the interactive region of the clipped item will not be ignored because of it's superview's boundaries.
If anyone needs it, here is the swift alternative
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
}
If you have many other subviews inside your parent view then probably most of other interactive views would not work if you use above solutions, in that case you can use something like this(In 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)
}
}
Place below lines of code into your view hierarchy:
- (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;
}
For the more clarification, it was explained in my blog: "goaheadwithiphonetech" regarding "Custom callout : Button is not clickable issue".
I hope that helps you...!!!
'program story' 카테고리의 다른 글
SQL Server 관리 스튜디오에서 DATETIME으로 삽입하려면 어떻게해야합니까? (0) | 2020.09.05 |
---|---|
Git에서 가장 많이 변경된 파일 찾기 (0) | 2020.09.05 |
Ruby가 Python보다 Rails에 더 적합한 이유는 무엇입니까? (0) | 2020.09.04 |
두 NSDates 사이의 신속한 일 (0) | 2020.09.04 |
뒤로 버튼의 텍스트를 변경하는 방법 (0) | 2020.09.04 |