一个UIButton(或其他任何控件)是否可能在它的frame超出其父视图的范围时接收触摸事件?因为当我尝试这样做时,我的UIButton似乎无法接收任何事件。如何解决这个问题?
一个UIButton(或其他任何控件)是否可能在它的frame超出其父视图的范围时接收触摸事件?因为当我尝试这样做时,我的UIButton似乎无法接收任何事件。如何解决这个问题?
是的。您可以重写hitTest:withEvent:
方法,以返回一个包含更多点的视图。请参见UIView Class Reference。
编辑:示例:
- (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的解释,应该这样做:
Note the code just there for overriding pointInside is not quite correct. As Summon explains below, do this:
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if ( CGRectContainsPoint(self.oversizeButton.frame, point) )
return YES;
return [super pointInside:point withEvent:event];
}
注意,在这个UIView子类中,你很可能会用self.oversizeButton
作为IBOutlet来完成它;然后你只需要将所涉及的“超大按钮”拖到特定的视图中。 (或者,如果出于某种原因您在项目中经常这样做,那么您可以有一个特殊的UIButton子类,并且您可以查看子视图列表以获得这些类。)希望能帮到你。
pointInside:withEvent:
方法来包含按钮的框架。我会在上面的答案中添加一些示例代码。 - jnichitTest:withEvent:
和pointInside:withEvent:
方法没有被调用。可能出了什么问题? - iosdude@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,所有子元素也是UIButtons,它们可以移动到超出父UIButton边界的位置。在我的情况下,我有一个包含UIButton
的UICollectionViewCell
子类。 我在单元格上禁用了clipsToBounds
,并且按钮可见超出了单元格的边界。 但是,该按钮没有接收到触摸事件。 我能够使用Jack的答案检测到按钮上的触摸事件:https://dev59.com/L2035IYBdhLWcg3wbPU5#30431157
这里是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)
}
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)
}
- (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];
}
在这种情况下,如果点落在按钮范围内,您会将调用转发到那里;否则,恢复到原始实现。为什么会发生这种情况?
原因在于当你的子视图超出父视图的边界时,实际上发生在该子视图上的触摸事件将无法传递给该子视图。但是,它会传递给它的父视图。
无论子视图是否在视觉上被剪裁,触摸事件始终尊重目标视图的父视图边界矩形。换句话说,在视图的一部分发生的触摸事件,如果该部分位于其父视图的边界矩形之外,则不会传递到该视图。 链接
你需要做什么?
当你的父视图接收到上述的触摸事件时,你需要显式地告诉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)
}
有趣的问题:你必须去到“最高的太小父视图”
你需要向上进入包含问题视图的最高层视图。
典型的例子:
假设你有一个名为S的屏幕,其中包含一个容器视图C。容器视图控制器的视图是V。(请注意V将位于C内并具有相同的大小。)V有一个子视图(可能是一个按钮)B。B是实际上在V之外的问题视图。
但请注意,B也在C之外。
在这个例子中,你需要将解决方案override hitTest
应用到C中,而不是应用到V中。如果应用到V中,则无效。
Swift:
其中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)
}
对于我和其他人而言,答案不是覆盖hitTest
方法,而是覆盖pointInside
方法。我只需要在两个地方进行修改。
超级视图
如果将clipsToBounds
设置为true,则此视图就可以解决整个问题。因此,以下是我使用Swift 3编写的简单UIView
代码:
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];
}
}
这个答案(以及同一问题的几个其他答案)可以帮助您澄清理解。
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)
}
我知道这是一个非常老的问题,但我们仍然需要这些解决方案作为更新版本,这就是为什么我会分享这个问题的新版本解决方案。
假设我的mainView
框架是CGRect(0.0, 0.0, 250, 250.0)
,我想在其中添加一个大小为50的按钮 subView
(位于左上角),它的框架看起来像这样CGRect(-50, -50, 50, 50)
在这种情况下,我们无法与此按钮进行交互,以下是如何解决此问题的示例
在您的MainView(按钮容器视图)中重写hitTest
函数,如其他答案中所述,但在此处反转子视图顺序并将点击点转换为此子视图点,然后再次使用hitTest
检查结果,否则检查您的视图是否包含该点返回您的mainView
,否则返回nil以避免影响其他视图
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard !clipsToBounds && !isHidden && alpha > 0 else { return self.superview }
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 self.bounds.contains(point) ? self : nil
}
以下是jnic's answer的Swift 5版本:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let radius: CGFloat = 100
let frame = CGRect(
x: -radius,
y: -radius,
width: frame.width + radius * 2,
height: frame.height + radius * 2
)
if frame.contains(point) {
return self
}
return nil
}