UIButton:使匹配区域大于默认匹配区域


188

我有一个关于UIButton及其命中区域的问题。我在界面构建器中使用“信息暗”按钮,但是发现命中区域不足以容纳某些人的手指。

有没有一种方法可以通过编程方式或在Interface Builder中增加按钮的点击区域,而无需更改InfoButton图形的大小?


如果没有任何效果,只需添加一个没有图像的UIButton,然后放置一个ImageView覆盖物即可!
Varun

这将是整个站点上最令人惊讶的最简单问题,它包含数十个答案。我的意思是,我可以在此处粘贴整个答案: override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { return bounds.insetBy(dx: -20, dy: -20).contains(point) }
Fattie

Answers:


137

由于我使用的是背景图片,因此这些解决方案都不适合我。这是一个执行一些有趣的Objective-C魔术的解决方案,并以最少的代码提供了一个解决方案。

首先,向其中添加一个UIButton覆盖命中测试的类别,还添加一个用于扩展命中测试框架的属性。

UIButton + Extensions.h

@interface UIButton (Extensions)

@property(nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;

@end

UIButton + Extensions.m

#import "UIButton+Extensions.h"
#import <objc/runtime.h>

@implementation UIButton (Extensions)

@dynamic hitTestEdgeInsets;

static const NSString *KEY_HIT_TEST_EDGE_INSETS = @"HitTestEdgeInsets";

-(void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets {
    NSValue *value = [NSValue value:&hitTestEdgeInsets withObjCType:@encode(UIEdgeInsets)];
    objc_setAssociatedObject(self, &KEY_HIT_TEST_EDGE_INSETS, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(UIEdgeInsets)hitTestEdgeInsets {
    NSValue *value = objc_getAssociatedObject(self, &KEY_HIT_TEST_EDGE_INSETS);
    if(value) {
        UIEdgeInsets edgeInsets; [value getValue:&edgeInsets]; return edgeInsets;
    }else {
        return UIEdgeInsetsZero;
    }
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    if(UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero) || !self.enabled || self.hidden) {
        return [super pointInside:point withEvent:event];
    }

    CGRect relativeFrame = self.bounds;
    CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.hitTestEdgeInsets);

    return CGRectContainsPoint(hitFrame, point);
}

@end

添加此类后,您所需要做的就是设置按钮的边缘插图。请注意,我选择添加插图,因此如果要增大点击区域,则必须使用负数。

[button setHitTestEdgeInsets:UIEdgeInsetsMake(-10, -10, -10, -10)];

注意:请记住在类别中导入类别(#import "UIButton+Extensions.h")。


26
覆盖类别中的方法是一个坏主意。如果另一个类别也尝试覆盖pointInside:withEvent:怎么办?并且对[super pointInside:withEvent]的调用不是在调用UIButton的版本(如果有),而是在调用UIButton的父版本。
honus

@honus您是正确的,Apple不建议您这样做。话虽如此,只要此代码不在共享库中,而仅在您的应用程序本地,就可以了。
Chase

嗨,Chase,改用子类有什么缺点吗?
Sid 2014年

3
您正在做太多不必要的工作,您可以执行两行简单的代码:[[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"arrow.png"] forState:UIControlStateNormal];只需设置所需的宽度和高度:`backButton.frame = CGRectMake(5,28,45,30);`
Resty

7
@Resty,他正在使用背景图片,这意味着您的方法无效。
mattsson

77

只需在界面生成器中设置图像边缘插入值即可。


1
我认为这对我不起作用,因为背景图像对插入没有响应,但是我更改为设置图像属性,然后为标题设置了负的左边缘插入,即图像的宽度,它又回到了顶部我的形象。
Dave Ross

12
这也可以用代码完成。例如,button.imageEdgeInsets = UIEdgeInsetsMake(-10, -10, -10, -10);
bengoesboom

戴夫·罗斯的答案很好。对于那些无法显示图片的人,-image会将您的标题移到其本身的右边(很明显),因此您可以单击IB中的小点击器将其移回(或如上所述的代码中)。我看到的唯一负面影响是我无法使用可拉伸图像。小的价格支付IMO
保罗·布诺

7
如何在不拉伸图像的情况下做到这一点?
zonabi

将内容模式设置为“中心”之类,而不是“缩放比例”。
塞缪尔·古德温

63

这是在Swift中使用扩展的优雅解决方案。根据Apple的人机界面指南(https://developer.apple.com/ios/human-interface-guidelines/visual-design/layout/),它使所有UIButton的命中面积至少为44x44点

斯威夫特2:

private let minimumHitArea = CGSizeMake(44, 44)

extension UIButton {
    public override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        // if the button is hidden/disabled/transparent it can't be hit
        if self.hidden || !self.userInteractionEnabled || self.alpha < 0.01 { return nil }

        // increase the hit frame to be at least as big as `minimumHitArea`
        let buttonSize = self.bounds.size
        let widthToAdd = max(minimumHitArea.width - buttonSize.width, 0)
        let heightToAdd = max(minimumHitArea.height - buttonSize.height, 0)
        let largerFrame = CGRectInset(self.bounds, -widthToAdd / 2, -heightToAdd / 2)

        // perform hit test on larger frame
        return (CGRectContainsPoint(largerFrame, point)) ? self : nil
    }
}

斯威夫特3:

fileprivate let minimumHitArea = CGSize(width: 100, height: 100)

extension UIButton {
    open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        // if the button is hidden/disabled/transparent it can't be hit
        if self.isHidden || !self.isUserInteractionEnabled || self.alpha < 0.01 { return nil }

        // increase the hit frame to be at least as big as `minimumHitArea`
        let buttonSize = self.bounds.size
        let widthToAdd = max(minimumHitArea.width - buttonSize.width, 0)
        let heightToAdd = max(minimumHitArea.height - buttonSize.height, 0)
        let largerFrame = self.bounds.insetBy(dx: -widthToAdd / 2, dy: -heightToAdd / 2)

        // perform hit test on larger frame
        return (largerFrame.contains(point)) ? self : nil
    }
}

3
您需要测试按钮当前是否隐藏。如果是这样,则返回nil。
乔什·加夫尼

谢谢,这看起来很棒。有潜在的不利之处要注意吗?
Crashalot '16

我喜欢这种解决方案,不需要我在所有按钮上设置其他属性。谢谢
xtrinch '16

1
如果Apple准则针对受灾地区建议了这些尺寸,为什么要让我们不得不解决其实施失败的问题?在更高版本的SDK中解决了吗?
NoodleOfDeath

2
谢天谢地,我们拥有Stack Overflow及其贡献者。因为我们当然不能依靠Apple获得有关如何解决其最常见的基本问题的有用,及时,准确的信息。
Womble

49

您还可以继承子类UIButton或自定义,UIViewpoint(inside:with:)使用类似以下内容的值进行覆盖:

迅捷3

override func point(inside point: CGPoint, with _: UIEvent?) -> Bool {
    let margin: CGFloat = 5
    let area = self.bounds.insetBy(dx: -margin, dy: -margin)
    return area.contains(point)
}

物镜

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGFloat margin = 5.0;
    CGRect area = CGRectInset(self.bounds, -margin, -margin);
    return CGRectContainsPoint(area, point);
}

1
感谢非常酷的解决方案,没有任何其他类别。我也将其与XIB中的按钮简单地集成在一起,并且效果很好。很好的答案
Matrosov Alexander

这是一个很好的解决方案,可以扩展到所有其他控件!我发现子类化UISearchBar很有用。自iOS 2.0以来,每个UIView上都使用pointInside方法。顺便说一句-斯威夫特:func pointInside(_ point:CGPoint,withEvent event:UIEvent!)->布尔。
Tommie C. 2014年

谢谢你!就像其他人说的那样,这对于其他控件非常有用!
Yanchi 2015年

这很棒!!谢谢,您节省了我很多时间!:-)
福伊塔塔(Vojta)

35

这是Swift 3.0中Chase的UIButton + Extensions。


import UIKit

private var pTouchAreaEdgeInsets: UIEdgeInsets = .zero

extension UIButton {

    var touchAreaEdgeInsets: UIEdgeInsets {
        get {
            if let value = objc_getAssociatedObject(self, &pTouchAreaEdgeInsets) as? NSValue {
                var edgeInsets: UIEdgeInsets = .zero
                value.getValue(&edgeInsets)
                return edgeInsets
            }
            else {
                return .zero
            }
        }
        set(newValue) {
            var newValueCopy = newValue
            let objCType = NSValue(uiEdgeInsets: .zero).objCType
            let value = NSValue(&newValueCopy, withObjCType: objCType)
            objc_setAssociatedObject(self, &pTouchAreaEdgeInsets, value, .OBJC_ASSOCIATION_RETAIN)
        }
    }

    open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        if UIEdgeInsetsEqualToEdgeInsets(self.touchAreaEdgeInsets, .zero) || !self.isEnabled || self.isHidden {
            return super.point(inside: point, with: event)
        }

        let relativeFrame = self.bounds
        let hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.touchAreaEdgeInsets)

        return hitFrame.contains(point)
    }
}

要使用它,您可以:

button.touchAreaEdgeInsets = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)

1
实际上,我们应该扩展UIControl而不是UIButton
Valentin Shergin '16

2
并且变量pTouchAreaEdgeInsets必须是Int,不是UIEdgeInsets。(实际上,它可以是任何类型,但是Int是最通用和最简单的类型,因此在这种构造中很常见)。(因此,我一般都喜欢这种方法。谢谢dcRay!)
Valentin Shergin '16

1
我尝试了titleEdgeInsets,imageEdgeInsets,contentEdgeInset对我都不起作用,此扩展程序运行良好。谢谢你发布这个!很好 !!
Yogesh Patel

19

我建议将UIButton的类型为Custom放置在信息按钮的中心。将自定义按钮的大小调整为所需的点击区域大小。从那里有两个选择:

  1. 选中自定义按钮的“突出显示高亮显示”选项。白色发光会出现在“信息”按钮上方,但是在大多数情况下,用户的手指会遮住该发光,并且他们所看到的只是周围的发光。

  2. 为信息按钮设置一个IBOutlet,为自定义按钮设置两个IBAction,一个用于“ Touch Down”,一个用于“ Touch Up Inside”。然后在Xcode中,使触地事件将信息按钮的突出显示属性设置为“是”,而触库事件将其高亮显示属性设置为“否”。


19

不要backgroundImage用您的图像设置属性,而是设置imageView属性。另外,请确保已将imageView.contentMode设置为UIViewContentModeCenter


1
更具体地说,将按钮的contentMode属性设置为UIViewContentModeCenter。然后,可以使按钮的边框大于图像。为我工作!
arlomedia 2012年

仅供参考,UIButton不尊重UIViewContentMode:stackoverflow.com/a/4503920/361247
Enrico Susatyo 2013年

@EnricoSusatyo对不起,我已经澄清了我在说什么API。将imageView.contentMode可以设置为UIContentModeCenter
Maurizio

这非常完美,谢谢您的建议![[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"image.png"] forState:UIControlStateNormal];
Resty 2014年

@Resty上面提到的注释对我不起作用:/图像正在调整为较大的设置帧大小。
阿尼尔2014年

14

我对Swift 3的解决方案:

class MyButton: UIButton {

    override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        let relativeFrame = self.bounds
        let hitTestEdgeInsets = UIEdgeInsetsMake(-25, -25, -25, -25)
        let hitFrame = UIEdgeInsetsInsetRect(relativeFrame, hitTestEdgeInsets)
        return hitFrame.contains(point)
    }
}

12

给出的答案没有错。但是我想扩展jlarjlar的答案,因为它具有惊人的潜力,可以为其他控件(例如SearchBar)的相同问题增加价值。这是因为因为pointInside附加到UIView,所以可以继承任何控件来改善触摸区域。此答案还显示了有关如何实施完整解决方案的完整示例。

为您的按钮(或任何控件)创建一个新的子类

#import <UIKit/UIKit.h>

@interface MNGButton : UIButton

@end

接下来覆盖子类实现中的pointInside方法

@implementation MNGButton


-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    //increase touch area for control in all directions by 20
    CGFloat margin = 20.0;
    CGRect area = CGRectInset(self.bounds, -margin, -margin);
    return CGRectContainsPoint(area, point);
}


@end

在情节提要/ xib文件中,选择有问题的控件,然后打开身份检查器,然后输入自定义类的名称。

mngbutton

在包含按钮的场景的UIViewController类中,将按钮的类类型更改为子类的名称。

@property (weak, nonatomic) IBOutlet MNGButton *helpButton;

将您的情节提要/ xib按钮链接到属性IBOutlet,您的触摸区域将被扩展以适合子类中定义的区域。

除了重写pointInside方法与一起CGRectInsetCGRectContainsPoint方法,应该需要时间来检查CGGeometry用于延长任何UIView子类的矩形触摸区域。您也可以找到关于CGGeometry使用情况在一些不错的提示NSHipster

例如,可以使用上述方法使触摸区域不规则,或者简单地选择将宽度触摸区域设置为水平触摸区域的两倍:

CGRect area = CGRectInset(self.bounds, -(2*margin), -margin);

注意:替换任何UI类控件应该在扩展不同控件的触摸区域(或任何UIView子类,如UIImageView等)上产生相似的结果。


10

这对我有用:

UIButton *button = [UIButton buttonWithType: UIButtonTypeCustom];
// set the image (here with a size of 32 x 32)
[button setImage: [UIImage imageNamed: @"myimage.png"] forState: UIControlStateNormal];
// just set the frame of the button (here 64 x 64)
[button setFrame: CGRectMake(xPositionOfMyButton, yPositionOfMyButton, 64, 64)];

3
这可行,但是如果需要同时为按钮设置图像和标题,请小心。在这种情况下,您将需要使用setBackgoundImage,它将把图像缩放到按钮的新框架。
米海达米安

7

不要更改UIButton的行为。

@interface ExtendedHitButton: UIButton

+ (instancetype) extendedHitButton;

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

@end

@implementation ExtendedHitButton

+ (instancetype) extendedHitButton {
    return (ExtendedHitButton *) [ExtendedHitButton buttonWithType:UIButtonTypeCustom];
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGRect relativeFrame = self.bounds;
    UIEdgeInsets hitTestEdgeInsets = UIEdgeInsetsMake(-44, -44, -44, -44);
    CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, hitTestEdgeInsets);
    return CGRectContainsPoint(hitFrame, point);
}

@end

如果您的按钮为40x40,则负数较大时将不会调用此方法-就像其他人提到的那样。否则,这有效。
James O'Brien

这对我不起作用。计算出的hitFrame正确计算,但是当点在self.bounds之外时,永远不会调用pointInside:。如果(CGRectContainsPoint(hitFrame,point)&&!CGRectContainsPoint(self.bounds,point)){NSLog(@“ Success!”); //从未召唤}
arsenius

7

我正在使用一种更通用的方法-[UIView pointInside:withEvent:]。这使我可以修改任何UIView(而不只是)点击测试行为UIButton

通常,在容器视图内放置一个按钮,这也限制了命中测试。例如,当按钮位于容器视图的顶部并且您想要向上扩展触摸目标时,还必须扩展容器视图的触摸目标。

@interface UIView(Additions)
@property(nonatomic) UIEdgeInsets hitTestEdgeInsets;
@end

@implementation UIView(Additions)

+ (void)load {
    Swizzle(self, @selector(pointInside:withEvent:), @selector(myPointInside:withEvent:));
}

- (BOOL)myPointInside:(CGPoint)point withEvent:(UIEvent *)event {

    if(UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero) || self.hidden ||
       ([self isKindOfClass:UIControl.class] && !((UIControl*)self).enabled))
    {
        return [self myPointInside:point withEvent:event]; // original implementation
    }
    CGRect hitFrame = UIEdgeInsetsInsetRect(self.bounds, self.hitTestEdgeInsets);
    hitFrame.size.width = MAX(hitFrame.size.width, 0); // don't allow negative sizes
    hitFrame.size.height = MAX(hitFrame.size.height, 0);
    return CGRectContainsPoint(hitFrame, point);
}

static char hitTestEdgeInsetsKey;
- (void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets {
    objc_setAssociatedObject(self, &hitTestEdgeInsetsKey, [NSValue valueWithUIEdgeInsets:hitTestEdgeInsets], OBJC_ASSOCIATION_RETAIN);
}

- (UIEdgeInsets)hitTestEdgeInsets {
    return [objc_getAssociatedObject(self, &hitTestEdgeInsetsKey) UIEdgeInsetsValue];
}

void Swizzle(Class c, SEL orig, SEL new) {

    Method origMethod = class_getInstanceMethod(c, orig);
    Method newMethod = class_getInstanceMethod(c, new);

    if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
        class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    else
        method_exchangeImplementations(origMethod, newMethod);
}
@end

这种方法的好处是,即使在情节提要中,您也可以通过添加用户定义的运行时属性来使用此方法。遗憾的是,UIEdgeInsets它不能在那里直接用作类型,但由于CGRect它还包含一个带有四个结构的结构,CGFloat因此可以通过选择“ Rect”并填写如下值来完美地工作:{{top, left}, {bottom, right}}


6

我在Swift中使用以下类,还启用了Interface Builder属性来调整边距:

@IBDesignable
class ALExtendedButton: UIButton {

    @IBInspectable var touchMargin:CGFloat = 20.0

    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        var extendedArea = CGRectInset(self.bounds, -touchMargin, -touchMargin)
        return CGRectContainsPoint(extendedArea, point)
    }
}

如何手动更改此pointInside?如果我创建了具有旧边距的UIButton,但是现在我想更改它?
TomSawyer

5

好了,您可以将UIButton放置在一个透明且稍大的UIView中,然后像在UIButton中一样在UIView实例上捕获触摸事件。这样,您仍然可以使用按钮,但是触摸区域更大。如果用户触摸视图而不是按钮,则必须手动处理按钮上的选定状态和突出显示状态。

其他可能性包括使用UIImage代替UIButton。


5

我已经能够以编程方式增加信息按钮的点击区域。“ i”图形不会改变比例,而是保持在新按钮框架的中心。

在Interface Builder中,信息按钮的大小似乎固定为18x19 [*]。通过将其连接到IBOutlet,我可以在代码中更改其框架大小而没有任何问题。

static void _resizeButton( UIButton *button )
{
    const CGRect oldFrame = infoButton.frame;
    const CGFloat desiredWidth = 44.f;
    const CGFloat margin = 
        ( desiredWidth - CGRectGetWidth( oldFrame ) ) / 2.f;
    infoButton.frame = CGRectInset( oldFrame, -margin, -margin );
}

[*]:iOS的更高版本似乎增加了信息按钮的点击范围。


5

这是我的Swift 3解决方案(基于此博客文章:http : //bdunagan.com/2010/03/01/iphone-tip-larger-hit-area-for-uibutton/

class ExtendedHitAreaButton: UIButton {

    @IBInspectable var hitAreaExtensionSize: CGSize = CGSize(width: -10, height: -10)

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

        let extendedFrame: CGRect = bounds.insetBy(dx: hitAreaExtensionSize.width, dy: hitAreaExtensionSize.height)

        return extendedFrame.contains(point) ? self : nil
    }
}

3

Chase的自定义命中测试实现为UIButton的子类。用Objective-C编写。

它似乎对initbuttonWithType:构造器都有效。对于我的需求而言,它是完美的,但是由于子类化UIButton可能比较繁琐,因此我想知道是否有人对此有毛病。

CustomeHitAreaButton.h

#import <UIKit/UIKit.h>

@interface CustomHitAreaButton : UIButton

- (void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets;

@end

CustomHitAreaButton.m

#import "CustomHitAreaButton.h"

@interface CustomHitAreaButton()

@property (nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;

@end

@implementation CustomHitAreaButton

- (instancetype)initWithFrame:(CGRect)frame {
    if(self = [super initWithFrame:frame]) {
        self.hitTestEdgeInsets = UIEdgeInsetsZero;
    }
    return self;
}

-(void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets {
    self->_hitTestEdgeInsets = hitTestEdgeInsets;
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    if(UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero) || !self.enabled || self.hidden) {
        return [super pointInside:point withEvent:event];
    }
    CGRect relativeFrame = self.bounds;
    CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.hitTestEdgeInsets);
    return CGRectContainsPoint(hitFrame, point);
}

@end

3

通过重写继承的UIButton实现。

Swift 2.2

// don't forget that negative values are for outset
_button.hitOffset = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
...
class UICustomButton: UIButton {
    var hitOffset = UIEdgeInsets()

    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        guard hitOffset != UIEdgeInsetsZero && enabled && !hidden else {
            return super.pointInside(point, withEvent: event)
        }
        return UIEdgeInsetsInsetRect(bounds, hitOffset).contains(point)
    }
}

2

WJBackgroundInsetButton.h

#import <UIKit/UIKit.h>

@interface WJBackgroundInsetButton : UIButton {
    UIEdgeInsets backgroundEdgeInsets_;
}

@property (nonatomic) UIEdgeInsets backgroundEdgeInsets;

@end

WJBackgroundInsetButton.m

#import "WJBackgroundInsetButton.h"

@implementation WJBackgroundInsetButton

@synthesize backgroundEdgeInsets = backgroundEdgeInsets_;

-(CGRect) backgroundRectForBounds:(CGRect)bounds {
    CGRect sup = [super backgroundRectForBounds:bounds];
    UIEdgeInsets insets = self.backgroundEdgeInsets;
    CGRect r = UIEdgeInsetsInsetRect(sup, insets);
    return r;
}

@end

2

切勿覆盖类别中的方法。子类按钮并覆盖- pointInside:withEvent:。例如,如果按钮的侧面小于44像素(建议将其作为最小可触碰区域),请使用以下方法:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    return (ABS(point.x - CGRectGetMidX(self.bounds)) <= MAX(CGRectGetMidX(self.bounds), 22)) && (ABS(point.y - CGRectGetMidY(self.bounds)) <= MAX(CGRectGetMidY(self.bounds), 22));
}

2

我为此专门制作了一个

您可以选择使用UIView类别,而无需子类别:

@interface UIView (KGHitTesting)
- (void)setMinimumHitTestWidth:(CGFloat)width height:(CGFloat)height;
@end

或者,您可以继承UIView或UIButton并设置minimumHitTestWidthand / orminimumHitTestHeight。然后,您的按钮点击测试区域将由这两个值表示。

就像其他解决方案一样,它使用该- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event方法。当iOS执行命中测试时,将调用此方法。这篇博客文章很好地描述了iOS点击测试的工作原理。

https://github.com/kgaidis/KGHitTestingViews

@interface KGHitTestingButton : UIButton <KGHitTesting>

@property (nonatomic) CGFloat minimumHitTestHeight; 
@property (nonatomic) CGFloat minimumHitTestWidth;

@end

您也可以只继承类并使用Interface Builder,而无需编写任何代码: 在此处输入图片说明


1
我最终只是为了速度而安装了它。感谢您将IBInspectable整合在一起。
user3344977

2

基于上面的giaset的答案(我找到了最优雅的解决方案),这是swift 3版本:

import UIKit

fileprivate let minimumHitArea = CGSize(width: 44, height: 44)

extension UIButton {
    open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        // if the button is hidden/disabled/transparent it can't be hit
        if isHidden || !isUserInteractionEnabled || alpha < 0.01 { return nil }

        // increase the hit frame to be at least as big as `minimumHitArea`
        let buttonSize = bounds.size
        let widthToAdd = max(minimumHitArea.width - buttonSize.width, 0)
        let heightToAdd = max(minimumHitArea.height - buttonSize.height, 0)
        let largerFrame = bounds.insetBy(dx: -widthToAdd / 2, dy: -heightToAdd / 2)

        // perform hit test on larger frame
        return (largerFrame.contains(point)) ? self : nil
    }
}

2

就这么简单:

class ChubbyButton: UIButton {
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        return bounds.insetBy(dx: -20, dy: -20).contains(point)
    }
}

这就是全部。


1

我在tableviewcell.accessoryView内部的按钮上使用此技巧来扩大其触摸区域

#pragma mark - Touches

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch                  = [touches anyObject];
    CGPoint location                = [touch locationInView:self];
    CGRect  accessoryViewTouchRect  = CGRectInset(self.accessoryView.frame, -15, -15);

    if(!CGRectContainsPoint(accessoryViewTouchRect, location))
        [super touchesBegan:touches withEvent:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch                  = [touches anyObject];
    CGPoint location                = [touch locationInView:self];
    CGRect  accessoryViewTouchRect  = CGRectInset(self.accessoryView.frame, -15, -15);

    if(CGRectContainsPoint(accessoryViewTouchRect, location) && [self.accessoryView isKindOfClass:[UIButton class]])
    {
        [(UIButton *)self.accessoryView sendActionsForControlEvents:UIControlEventTouchUpInside];
    }
    else
        [super touchesEnded:touches withEvent:event];
}

1

我只是在Swift 2.2 中完成了@Chase 解决方案的移植

import Foundation
import ObjectiveC

private var hitTestEdgeInsetsKey: UIEdgeInsets = UIEdgeInsetsZero

extension UIButton {
    var hitTestEdgeInsets:UIEdgeInsets {
        get {
            let inset = objc_getAssociatedObject(self, &hitTestEdgeInsetsKey) as? NSValue ?? NSValue(UIEdgeInsets: UIEdgeInsetsZero)
            return inset.UIEdgeInsetsValue()
        }
        set {
            let inset = NSValue(UIEdgeInsets: newValue)
            objc_setAssociatedObject(self, &hitTestEdgeInsetsKey, inset, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    public override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        guard !UIEdgeInsetsEqualToEdgeInsets(hitTestEdgeInsets, UIEdgeInsetsZero) && self.enabled == true && self.hidden == false else {
            return super.pointInside(point, withEvent: event)
        }
        let relativeFrame = self.bounds
        let hitFrame = UIEdgeInsetsInsetRect(relativeFrame, hitTestEdgeInsets)
        return CGRectContainsPoint(hitFrame, point)
    }
}

你可以这样使用

button.hitTestEdgeInsets = UIEdgeInsetsMake(-10, -10, -10, -10)

有关其他任何参考,请参见https://stackoverflow.com/a/13067285/1728552


1

@antoine的答案格式为Swift 4

class ExtendedHitButton: UIButton
{
    override func point( inside point: CGPoint, with event: UIEvent? ) -> Bool
    {
        let relativeFrame = self.bounds
        let hitTestEdgeInsets = UIEdgeInsetsMake( -44, -44, -44, -44 ) // Apple recommended hit target
        let hitFrame = UIEdgeInsetsInsetRect( relativeFrame, hitTestEdgeInsets )
        return hitFrame.contains( point );
    }
}

0

我遵循了Chase的回答,并且效果很好,当您创建的Arrea太大,大于取消选择按钮的区域(如果区域不大)时,它不会调用UIControlEventTouchUpInside事件的选择器,这是一个问题。

我认为任何方向或类似尺寸的尺寸都超过200。


0

我对这款游戏太晚了,但是想尝试一种可以解决您问题的简单技术。这是我的典型程序化UIButton代码段:

UIImage *arrowImage = [UIImage imageNamed:@"leftarrow"];
arrowButton = [[UIButton alloc] initWithFrame:CGRectMake(15.0, self.frame.size.height-35.0, arrowImage.size.width/2, arrowImage.size.height/2)];
[arrowButton setBackgroundImage:arrowImage forState:UIControlStateNormal];
[arrowButton addTarget:self action:@selector(onTouchUp:) forControlEvents:UIControlEventTouchUpOutside];
[arrowButton addTarget:self action:@selector(onTouchDown:) forControlEvents:UIControlEventTouchDown];
[arrowButton addTarget:self action:@selector(onTap:) forControlEvents:UIControlEventTouchUpInside];
[arrowButton addTarget:self action:@selector(onTouchUp:) forControlEvents:UIControlEventTouchDragExit];
[arrowButton setUserInteractionEnabled:TRUE];
[arrowButton setAdjustsImageWhenHighlighted:NO];
[arrowButton setTag:1];
[self addSubview:arrowButton];

我正在为按钮加载透明的png图像并设置背景图像。我正在基于UIImage设置框架,并为视网膜缩放50%。好的,也许您同意以上所述,但是,如果您想使点击区域更大,并且让您省去头痛的话:

我要做的是,在photoshop中打开图像,然后简单地将画布大小增加到120%并保存。实际上,您已经使用透明像素将图像放大了。

只是一种方法。


0

上面的@jlajlar的回答似乎很好并且很直接,但是与Xamarin.iOS不匹配,因此我将其转换为Xamarin。如果您在Xamarin iOS上寻找解决方案,请按以下步骤进行:

public override bool PointInside (CoreGraphics.CGPoint point, UIEvent uievent)
{
    var margin = -10f;
    var area = this.Bounds;
    var expandedArea = area.Inset(margin, margin);
    return expandedArea.Contains(point);
}

您可以将此方法添加到覆盖UIView或UIImageView的类中。这很好用:)


0

没有一个答案适合我,因为我在该按钮上使用了背景图片和标题。此外,按钮将随着屏幕尺寸的改变而调整大小。

相反,我通过增大png透明区域来扩大点击区域。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.