如何增加 UIButton 的点击区域?

How can I increase the Tap Area for UIButton?

我使用 UIButton 自动布局。当图像较小时,点击区域也较小。我可以想象出几种解决此问题的方法:

  1. 增大图像尺寸,即在图像周围放置一个透明区域。这不好,因为当您定位图像时,您必须牢记额外的透明边框。
  2. 使用 CGRectInset 并增加大小。这不适用于自动布局,因为使用自动布局它会回退到原始图像大小。

除了以上两种方法,有没有更好的方法来增加UIButton的点击面积?

您可以简单地调整按钮的内容插入以获得您想要的大小。在代码中,它将如下所示:

button.contentEdgeInsets = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
//Or if you specifically want to adjust around the image, instead use button.imageEdgeInsets

在界面生成器中,它将如下所示:

您可以在情节提要中或通过代码设置按钮 EdgeInsets。按钮的尺寸在高度和宽度上应大于设置为按钮的图像。

注意:在Xcode8之后,尺寸检查器中可以设置内容插入

或者您也可以使用带有点击手势的图像视图,以便在点击图像视图时执行操作。确保在 Storyboard 上为 imageview 勾选 User Interaction Enabled 以使手势起作用。 使图像视图大于图像以在其上设置并在其上设置图像。 现在将图像视图图像的模式设置为以 storyboard/interface 构建器为中心。

您可以点击图片进行操作。

希望对您有所帮助。

很简单。创建自定义 UIButton class。然后覆盖 pointInside... 方法并根据需要更改值。

#import "CustomButton.h"

@implementation CustomButton

-(BOOL) pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    CGRect newArea = CGRectMake(self.bounds.origin.x - 10, self.bounds.origin.y - 10, self.bounds.size.width + 20, self.bounds.size.height + 20);
    
    return CGRectContainsPoint(newArea, point);
}
@end

每边需要多10点接触面积

和Swift5版本:

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

Swift 4 • Xcode 9

您可以 select 以编程方式作为 -

图片 -

button.imageEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)

标题-

button.titleEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)

我确认即使使用自动布局,Syed 的解决方案也能正常工作。这是 Swift 4.x 版本:

import UIKit

class BeepSmallButton: UIButton {

    // MARK: - Functions

    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        let newArea = CGRect(
            x: self.bounds.origin.x - 5.0,
            y: self.bounds.origin.y - 5.0,
            width: self.bounds.size.width + 10.0,
            height: self.bounds.size.height + 20.0
        )
        return newArea.contains(point)
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

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

子类 UIButton 并添加此函数

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    let verticalInset = CGFloat(10)
    let horizontalInset = CGFloat(10)

    let largerArea = CGRect(
        x: self.bounds.origin.x - horizontalInset,
        y: self.bounds.origin.y - verticalInset,
        width: self.bounds.size.width + horizontalInset*2,
        height: self.bounds.size.height + verticalInset*2
    )

    return largerArea.contains(point)
}

这应该有效

import UIKit

@IBDesignable
class GRCustomButton: UIButton {

    @IBInspectable var margin:CGFloat = 20.0
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        //increase touch area for control in all directions by 20

        let area = self.bounds.insetBy(dx: -margin, dy: -margin)
        return area.contains(point)
    }

}

关于边缘插入答案的一些上下文。

使用自动布局结合内容边缘插入时,您可能需要更改约束。

假设您有一张 10x10 的图像,您希望将其设为 30x30 以获得更大的点击区域:

  1. 将您的自动布局约束设置为所需的更大区域。如果你 立即构建这会拉伸图像。

  2. 使用内容边缘插入来缩小 space 可用于 图像,使其匹配正确的尺寸。在此示例中,将 10 10 10 10. 留下 10x10 space 的图像来绘制自己。

  3. 赢了。

Swift 基于 Syed 答案的 5 版本(较大区域的负值):

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

或者:

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    return bounds.inset(by: UIEdgeInsets(top: -5, left: -5, bottom: -5, right: -5)).contains(point)
}

我采用的方法是使用 contentEdgeInsets 为按钮在小图像周围留出一些额外空间(这就像按钮内容之外的边距),但也会覆盖 alignmentRect 属性 具有相同的插图,将自动布局使用的矩形带回图像 in。这确保自动布局使用较小的图像计算其约束,而不是按钮的完整可点击范围。

class HIGTargetButton: UIButton {
  override init(frame: CGRect) {
    super.init(frame: frame)
  }
  
  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
  
  override func setImage(_ image: UIImage?, for state: UIControl.State) {
    super.setImage(image, for: state)
    guard let image = image else { return }
    let verticalMarginToAdd = max(0, (targetSize.height - image.size.height) / 2)
    let horizontalMarginToAdd = max(0, (targetSize.width - image.size.width) / 2)
    let insets = UIEdgeInsets(top: verticalMarginToAdd,
                                     left: horizontalMarginToAdd,
                                     bottom: verticalMarginToAdd,
                                     right: horizontalMarginToAdd)
    contentEdgeInsets = insets
  }
  
  override var alignmentRectInsets: UIEdgeInsets {
    contentEdgeInsets
  }
  
  private let targetSize = CGSize(width: 44.0, height: 44.0)
}

粉红色按钮有一个更大的可点击目标(此处显示为粉红色,但可能是 .clear)和一个更小的图像 - 它的前缘根据图标而不是与绿色视图的前缘对齐整个按钮。

子类化的替代方法是扩展 UIControl,向其添加 touchAreaInsets 属性 - 通过利用 objC 运行时 - 并混合 pointInside:withEvent.

#import <objc/runtime.h>
#import <UIKit/UIKit.h>

#import "NSObject+Swizzling.h" // This is where the magic happens :)


@implementation UIControl (Extensions)

@dynamic touchAreaInsets;
static void * CHFLExtendedTouchAreaControlKey;

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self swizzleSelector:@selector(pointInside:withEvent:) withSelector:@selector(chfl_pointInside:event:) classMethod:NO];
    });
}

- (BOOL)chfl_pointInside:(CGPoint)point event:(UIEvent *)event
{
    if(UIEdgeInsetsEqualToEdgeInsets(self.touchAreaInsets, UIEdgeInsetsZero)) {
        return [self chfl_pointInside:point event:event];
    }
    
    CGRect relativeFrame = self.bounds;
    CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.touchAreaInsets);
    
    return CGRectContainsPoint(hitFrame, point);
}

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

- (void)setTouchAreaInsets:(UIEdgeInsets)touchAreaInsets
{
    NSValue *value = [NSValue value:&touchAreaInsets withObjCType:@encode(UIEdgeInsets)];
    objc_setAssociatedObject(self, &CHFLExtendedTouchAreaControlKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

这里是NSObject+Swizzling.h https://gist.github.com/epacces/fb9b8e996115b3bfa735707810f41ec8

这是一个非常通用的界面,允许您 reduce/increase UIControl 的触摸区域。

#import <UIKit/UIKit.h>

/**
 *  Extends or reduce the touch area of any UIControls
 *
 *  Example (extends the button's touch area by 20 pt):
 *
 *  UIButton *button = [[UIButton alloc] initWithFrame:CGRectFrame(0, 0, 20, 20)]
 *  button.touchAreaInsets = UIEdgeInsetsMake(-10.0f, -10.0f, -10.0f, -10.0f);
 */

@interface UIControl (Extensions)
@property (nonatomic, assign) UIEdgeInsets touchAreaInsets;
@end

此处介绍的两种解决方案都有效……在适当的情况下。但这里有一些您可能 运行 陷入的陷阱。 首先是不完全明显的东西:

  • 点击必须在按钮内,轻触按钮边界不起作用。如果按钮非常小,您的大部分手指很可能会超出按钮,点击将无法使用。

具体以上解决方案:

解决方案 1 @Travis:

使用 contentEdgeInsets 增加按钮尺寸而不增加 icon/text 尺寸,类似于添加 padding

button.contentEdgeInsets = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)

这个很简单,增加按钮尺寸会增加点击区域。

  • 如果您设置了 height/width 框架或约束,显然这不会有太大作用,只会扭曲或移动您的 icon/text。
  • 按钮尺寸会更大。在布置其他视图时必须考虑到这一点。 (根据需要抵消其他观点)

解决方案 2 @Syed Sadrul Ullah Sahad:

子类化 UIButton 并覆盖 point(inside point: CGPoint, with event: UIEvent?) -> Bool

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

这个解决方案很棒,因为它允许您在不更改布局的情况下将点击区域扩展到视图边界之外,但这里有一些问题:

  • 父视图需要有背景,将按钮放入没有背景的空 ViewController 中是行不通的。
  • 如果按钮是嵌套的,则视图层次结构中的所有视图都需要提供足够的“space”或覆盖点。 例如

---------
|       |
|oooo   |
|oXXo   |
|oXXo   |
|oooo   | Button-X nested in View-o will NOT extend beyond View-o
--------- 

如果您正在为按钮使用 Material 的 iOS 库,您可以只使用 hitAreaInsets 来增加按钮的触摸目标大小。

示例代码来自 https://material.io/components/buttons/ios#using-buttons

let buttonVerticalInset =
  min(0, -(kMinimumAccessibleButtonSize.height - button.bounds.height) / 2);
let buttonHorizontalInset =
  min(0, -(kMinimumAccessibleButtonSize.width - button.bounds.width) / 2);
button.hitAreaInsets =
  UIEdgeInsetsMake(buttonVerticalInset, buttonHorizontalInset,
buttonVerticalInset, buttonHorizontalInset);