UIButton:使点击区域大于默认点击区域

Posted

技术标签:

【中文标题】UIButton:使点击区域大于默认点击区域【英文标题】:UIButton: Making the hit area larger than the default hit area 【发布时间】:2010-10-22 22:49:33 【问题描述】:

我有一个关于 UIButton 及其点击区域的问题。我正在使用界面生成器中的信息暗按钮,但我发现点击区域对于某些人的手指来说不够大。

有没有办法在不改变 InfoButton 图形大小的情况下以编程方式或在 Interface Builder 中增加按钮的点击区域?

【问题讨论】:

如果没有任何效果,只需添加一个没有图像的 UIButton,然后放置一个 ImageView 覆盖! 这一定是整个网站上最简单的问题,有几十个答案。我的意思是,我可以在这里粘贴整个答案:override func point(inside point: CGPoint, with event: UIEvent?) -> Bool return bounds.insetBy(dx: -20, dy: -20).contains(point) 【参考方案1】:

@antoine 为 Swift 4 格式化的答案

final class ExtendedHitButton: UIButton 
    
    override func point( inside point: CGPoint, with event: UIEvent? ) -> Bool 
        let relativeFrame = self.bounds
        let hitTestEdgeInsets = UIEdgeInsets(top: -44, left: -44, bottom: -44, right: -44) // Apple recommended hit target
        let hitFrame = relativeFrame.inset(by: hitTestEdgeInsets)
        return hitFrame.contains( point );
    

【讨论】:

【参考方案2】:

我看到很多解决方案要么没有达到目标,要么需要指定一些固定的插入来添加。这是一个简单的UIView 子类的解决方案,它将视图的命中矩形扩展到至少 44 x 44;如果任一维度已经大于该维度,则它不会人为地填充该维度。

这可确保按钮始终具有建议的最小触摸尺寸 44 x 44,而无需任何手动配置、计算或图像填充:

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool 
    let minimumTouchSize: CGFloat = 44.0
    let center: CGPoint = .init(x: self.bounds.midX, y: self.bounds.midY)

    let minimumHitRect: CGRect =
        .init(center: center, size: .zero)
        .insetBy(
            dx: -minimumTouchSize / 2.0,
            dy: -minimumTouchSize / 2.0
        )

    let fullHitRect = self.bounds.union(minimumHitRect)

    return fullHitRect.contains(point)

【讨论】:

Apple 构建了一个更好的解决方案,这似乎是最通用和最简单的答案,副作用最少。我认为通过子类强制选择加入更安全。我对大多数这些变通办法的唯一抱怨是它对初级开发人员来说并不明显,并且可能是调试混乱的根源,但我没有任何更好、更简单的解决方案可以提供!【参考方案3】:

就是这么简单:

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

就是这样。

【讨论】:

不错。优秀的解决方案。谢谢! 欢呼@YogeshPatel。是的,ios 示例代码有一个不幸的问题——它很快就会过时。您最终会遇到关于 SO 的问题,其中顶部/勾选的答案确实非常错误且已过时!【参考方案4】:

与 Zhanserik 类似,具有可变扩展名并针对 Swift 4.2 进行了更新:

class ButtonWithExtendedHitArea: UIButton 

    var extention: CGFloat

    required init(extendBy: CGFloat) 
        extention = extendBy

        super.init(frame: .zero)
    

    required init?(coder aDecoder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    

    override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool 
        let relativeFrame = self.bounds
        let hitTestEdgeInsets = UIEdgeInsets(top: -extention, left: -extention, bottom: -extention, right: -extention)
        let hitFrame = relativeFrame.inset(by: hitTestEdgeInsets)
        return hitFrame.contains(point)
    


【讨论】:

在所有提议的选项中,这个选项对我有效 100%【参考方案5】:

由于我使用的是背景图片,因此这些解决方案都不适合我。这是一个解决方案,它可以实现一些有趣的 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")。

【讨论】:

覆盖类别中的方法是个坏主意。如果另一个类别也试图覆盖 pointInside:withEvent: 怎么办?并且对 [super pointInside:withEvent] 的调用不是调用 UIButton 的调用版本(如果有的话),而是调用 UIButton 的父版本。 @honus 你是对的,Apple 不建议这样做。话虽如此,只要此代码不在共享库中并且只是您的应用程序的本地代码,您就可以了。 嗨,Chase,使用子类有什么缺点吗? 你做了太多不必要的工作,你可以做两行简单的代码:[[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"arrow.png"] forState:UIControlStateNormal]; 只需设置你需要的宽度和高度:` backButton.frame = CGRectMake(5, 28, 45, 30);` @Resty,他正在使用背景图片,这意味着您的方法行不通。【参考方案6】:

我在 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)
    

【讨论】:

【参考方案7】: 不覆盖类别或扩展 每条准则至少 44x44

我的看法:

open class MinimumTouchAreaButton: UIButton 
    open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? 
        guard !self.isHidden, self.isUserInteractionEnabled, self.alpha > 0 else  return nil 
        let expandedBounds = bounds.insetBy(dx: min(bounds.width - 44, 0), dy: min(bounds.height - 44, 0))
        return expandedBounds.contains(point) ? self : nil
    
 

【讨论】:

【参考方案8】:
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool 
    let inset = UIEdgeInsets(top: -adjustHitY * 0.5, left: -adjustHitX * 0.5, bottom: -adjustHitY * 0.5, right: -adjustHitX * 0.5)
    return UIEdgeInsetsInsetRect(bounds, inset).contains(point)

【讨论】:

【参考方案9】:

这是 Chase 在 Swift 3.0 中的 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)

【讨论】:

其实我们应该扩展UIControl,而不是UIButton 并且变量pTouchAreaEdgeInsets 必须是Int,而不是UIEdgeInsets。 (其实可以是任何类型,但Int是最通用最简单的类型,所以在这种构造中很常见)。 (因此我确实喜欢这种方法。谢谢你,dcRay!) 我尝试过 titleEdgeInsets、imageEdgeInsets、contentEdgeInset 都不适合我这个扩展运行良好。谢谢你张贴这个!干得好!!【参考方案10】:

您还可以继承 UIButton 或自定义 UIView 并使用以下内容覆盖 point(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)

Objective-C

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

【讨论】:

感谢非常酷的解决方案,没有任何额外的类别。我也简单地将它与我在 XIB 中的按钮集成在一起,效果很好。很好的答案 这是一个很好的解决方案,可以扩展到所有其他控件!例如,我发现子类化 UISearchBar 很有用。自 iOS 2.0 以来,每个 UIView 上都有 pointInside 方法。顺便说一句 - 斯威夫特:func pointInside(_点:CGPoint,withEvent事件:UIEvent!)->布尔。 谢谢!正如其他人所说,这对其他控件非常有用! 这太棒了!!谢谢,你为我节省了很多时间! :-)【参考方案11】:

这是一个在 Swift 中使用 Extensions 的优雅解决方案。根据 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
    

【讨论】:

您需要测试按钮当前是否隐藏。如果是,则返回 nil。 谢谢,这看起来很棒。有什么潜在的缺点需要注意吗? 我喜欢这个解决方案,不需要我为所有按钮设置额外的属性。谢谢 如果 Apple 指南推荐这些尺寸用于命中区域,为什么要让我们修复它们未能实施的问题?以后的 SDK 中是否解决了这个问题? 谢天谢地,我们有 Stack Overflow 及其贡献者。因为我们当然不能依赖 Apple 提供有用、及时、准确的信息来解决他们最常见的基本问题。【参考方案12】:

这是我的 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
    

【讨论】:

【参考方案13】:

基于上面 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
    

【讨论】:

【参考方案14】:

我刚刚在 swift 2.2 中完成了@Chase solution 的移植

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://***.com/a/13067285/1728552

【讨论】:

【参考方案15】:

斯威夫特:

override func viewWillAppear(animated: Bool) 
        self.sampleButton.frame = CGRectInset(self.sampleButton.frame, -10, -10);
    

【讨论】:

【参考方案16】:

此 Swift 版本允许您为所有 UIButton 定义最小点击大小。至关重要的是,它还可以处理 UIButtons 被隐藏的情况,许多答案都忽略了这一点。

extension UIButton 
    public override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? 
        // Ignore if button hidden
        if self.hidden 
            return nil
        

        // If here, button visible so expand hit area
        let hitSize = CGFloat(56.0)
        let buttonSize = self.frame.size
        let widthToAdd = (hitSize - buttonSize.width > 0) ? hitSize - buttonSize.width : 0
        let heightToAdd = (hitSize - buttonSize.height > 0) ? hitSize - buttonSize.height : 0
        let largerFrame = CGRect(x: 0-(widthToAdd/2), y: 0-(heightToAdd/2), width: buttonSize.width+widthToAdd, height: buttonSize.height+heightToAdd)
        return (CGRectContainsPoint(largerFrame, point)) ? self : nil
    

【讨论】:

【参考方案17】:

通过覆盖继承的 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)
    

【讨论】:

【参考方案18】:

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

【讨论】:

【参考方案19】:

我为此创建了library。

您可以选择使用UIView 类别,不需要子类化

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

或者您可以子类化您的 UIView 或 UIButton 并设置 minimumHitTestWidth 和/或 minimumHitTestHeight。然后,您的按钮命中测试区域将由这 2 个值表示。

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

https://github.com/kgaidis/KGHitTestingViews

@interface KGHitTestingButton : UIButton <KGHitTesting>

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

@end

您也可以直接继承并使用 Interface Builder 而无需编写任何代码:

【讨论】:

我最终安装它只是为了速度。添加 IBInspectable 是一个好主意,感谢您将这些放在一起。【参考方案20】:

没有一个答案对我来说是完美的,因为我在那个按钮上使用了背景图片和标题。此外,按钮会随着屏幕大小的变化而调整大小。

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

【讨论】:

【参考方案21】:

我通过调配-[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

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

【讨论】:

【参考方案22】:

我在 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)
    

【讨论】:

如何手动更改这个点里面?如果我创建了一个带有旧边距的 UIButton,但现在我想更改它?【参考方案23】:

@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 的类中。这很好用:)

【讨论】:

【参考方案24】:

我玩这个游戏太晚了,但想权衡一下可能解决您问题的简单技术。这是一个典型的编程 UIButton sn-p 对我来说:

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% 并保存。实际上,您刚刚使用透明像素使图像更大。

只有一种方法。

【讨论】:

【参考方案25】:

给出的答案没有错;但是我想扩展jlarjlar's answer,因为它具有惊人的潜力,可以为其他控件(例如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 文件中选择相关控件并打开身份检查器并输入您的自定义类的名称。

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

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

将您的故事板/xib 按钮链接到属性 IBOutlet,您的触摸区域将被扩展以适应子类中定义的区域。

除了覆盖pointInside method 以及CGRectInset 和CGRectContainsPoint 方法之外,还应该花时间检查CGGeometry 以扩展任何UIView 子类的矩形触摸区域。您还可以在 NSHipster 找到一些关于 CGGeometry 用例的不错的技巧。

例如可以使用上面提到的方法使触摸区域变得不规则,或者简单地选择使宽度触摸区域是水平触摸区域的两倍:

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

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

【讨论】:

【参考方案26】:

永远不要覆盖类别中的方法。子类按钮并覆盖- 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));

【讨论】:

【参考方案27】:

不要改变 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,如果负数更大,则不会调用此方法 - 就像其他人提到的那样。否则这行得通。 这对我不起作用。 hitFrame 似乎计算正确,但是当点在 self.bounds 之外时,永远不会调用 pointInside:。 if (CGRectContainsPoint(hitFrame, point) && !CGRectContainsPoint(self.bounds, point)) NSLog(@"Success!"); // 从未调用 【参考方案28】:

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

【讨论】:

更具体地说,将按钮的 contentMode 属性设置为 UIViewContentModeCenter。然后您可以使按钮的框架大于图像。为我工作! 仅供参考,UIButton 不尊重 UIViewContentMode:***.com/a/4503920/361247 @EnricoSusatyo 抱歉,我已经澄清了我在说什么 API。 imageView.contentMode 可以设置为UIContentModeCenter 这很好用,感谢您的建议! [[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"image.png"] forState:UIControlStateNormal]; @Resty 上面提到的评论对我不起作用:/ 图像正在调整为更大的设置帧大小。【参考方案29】:

我已经关注了 Chase 的回复,它工作得很好,当你创建的区域太大时,一个问题是大于按钮被取消选择的区域(如果区域不是更大),它不会调用选择器UIControlEventTouchUpInside 事件。

我认为大小超过 200 任何方向或类似的东西。

【讨论】:

【参考方案30】:

我已经能够以编程方式增加信息按钮的点击区域。 “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 的更高版本似乎增加了信息按钮的点击区域。

【讨论】:

以上是关于UIButton:使点击区域大于默认点击区域的主要内容,如果未能解决你的问题,请参考以下文章

如何使整个区域可点击?

UIButton点击区域太大

如何在 Swift 中扩展特定 UIButton 的点击区域?

UIButton具有自定义不对称可点击区域?

IOS自定义UIbutton,怎样让点击的有效区域集中在图标内?

iOS之扩大UIButton(UIView)的点击范围