如何增加UIButton的触摸区域?

101

我在使用自动布局时,使用了UIButton。当图片尺寸较小时,点击区域也很小。我可以想象出几种修复方法:

  1. 增加图像大小,即在图像周围放置透明区域。这不好,因为当您定位图像时,必须记住额外的透明边框。
  2. 使用CGRectInset并增加大小。这与自动布局不兼容,因为使用自动布局将返回原始图像大小。

除了上述两种方法,是否有更好的解决方案来增加UIButton的点击区域?

14个回答

75

您可以通过调整按钮的内容插入来获得所需的大小。在代码中,它看起来像这样:

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

在界面构建器中,它会看起来像这样:

interface builder


3
应该给予负面价值还是请解释? - Shiva
12
这种方法不可行。AutoLayout会考虑insets,改变按钮的位置,这不是我们想要的。相反,我们只想影响点击检测,而不是布局。 - Womble
31
我很惊讶这个回答被接受了。它并不管用,图像仍然会变形而框架保持不变。 - Ethan Zhao
1
我昨天创建了一个示例项目来测试它是否仍然有效,结果是有效的。如果图像失真,那是因为图像视图的contentMode没有正确设置。 - Travis
3
这根本不是一个好的答案。它可能会起作用 - 我甚至都不确定 - 但它会改变按钮的其他属性,这些属性并不是实现此结果所必需的。 - HepaKKes
显示剩余4条评论

65

非常简单。创建一个自定义的UIButton类,然后重写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个触摸区域。

而且Swift 5版本:

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

1
同样的问题是:如果按钮放在小视图内,而您需要扩展可触摸区域以覆盖此视图呢? - Vyachaslav Gerchicov
@VyachaslavGerchicov,使用一个大的透明视图作为按钮的父视图,以便获得简单的解决方案。 - Syed Sadrul Ullah Sahad
6
请始终使用有意义的名称,例如LargeTapAreaButton。 - Denis Rybkin
@VyachaslavGerchicov - 点击事件从父视图向下传递,因此如果您的父视图比扩展的可触摸区域小,则还必须在父视图中处理pointInsidehitTest。当您意识到这对于某些情况来说并不是一种优雅的解决方案时,我认为。 - anders

16

我确认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")
    }
}

3
这应该是被采纳的答案。非常好用,只是处理了按钮周围更大的区域并允许其接收事件。 - Miki
我发现这在我的情况下没有任何区别,而且我认为这是因为按钮的UIImageView正在获取触摸。我该如何防止这种情况发生?这让我完全疯了... - jbm
@mrwheet button.imageView?.isUserInteractionEnabled = false @mrwheet button.imageView?.isUserInteractionEnabled = false - David
这不是有些不对称吗?height 应该是 +10.0 或者 y 应该是 -10.0,不是吗? - clearlight

14

您可以在Storyboard或通过代码设置按钮EdgeInsets。按钮的大小应该比设置给按钮的图像更高和更宽。

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

UIEdgeInseton UIButton

另外,您还可以在图像视图上使用轻拍手势进行操作。请确保在Storyboard上为图像视图选中“用户交互启用”以便手势正常工作。 将图像视图放大到大于要设置的图像,并在其上设置图像。 现在,在Storyboard/界面生成器中将图像视图图像的模式设置为中心。

Using image view with tap action and image set on it as center mode 您可以点击图像执行操作。

希望这会有所帮助。


10

这应该可以正常工作

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)
    }

}

谢谢。看起来这是正确的答案,没有任何副作用。 - mathema

7

基于Syed的回答,Swift 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)
}

如果按钮被插入到另一个视图中,而该视图的大小小于可触摸区域怎么办? - Vyachaslav Gerchicov

6

关于边缘插图答案的一些背景信息。

当使用自动布局与内容边缘插图相结合时,您可能需要更改约束条件。

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

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

  2. 使用内容边缘插图来缩小可用于图像的空间,使其匹配正确的大小。在此示例中,它将是10 10 10 10。这样,图像就有了一个10x10的空间来绘制自己。

  3. 成功。


2
非常有帮助。感谢您“修复”上面的解决方案。对我来说,这是一个更优秀的解决方案,因为它不涉及自定义类。通过示例重新强调第一点:如果您之前将此按钮固定在左上角距离32像素处,则还应从顶部和左侧约束中减去10,将这两个常量都减少到22。 - John

5
我处理这个问题的方法是给按钮周围一个小图像添加一些额外空间,使用 contentEdgeInsets (它们像按钮内容外的边距),同时还要使用相同的插入重写 alignmentRect 属性,将自动布局使用的矩形带回到图像中。这确保了自动布局使用更小的图像计算其约束,而不是按钮的完整可点击范围。
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),以及较小的图像 - 其前沿根据图标而不是整个按钮与绿色视图的前沿对齐。

基于较小图标与绿色视图对齐的粉色可点击按钮


5

这里介绍的两种解决方案确实可以在适当的情况下解决问题。但是,您可能会遇到一些问题。

  • 轻触操作必须在按钮区域内进行,轻触按钮边界不起作用。如果一个按钮非常小,你的手指大部分可能在按钮外面,那么轻触操作将无法生效。

以下内容特定于上述解决方案:

解决方案1 @Travis:

使用contentEdgeInsets来增加按钮的大小而不增加图标/文本的大小,类似于添加填充。

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

这是很明显的,增加按钮的大小会增加点击区域。

  • 如果您设置了高度/宽度框架或约束,那么显然不会有太大作用,并且只会扭曲或移动图标/文本。
  • 按钮大小将变大。在布局其他视图时必须考虑这一点(根据需要偏移其他视图)。

解决方法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中放置一个按钮是行不通的。
  • 如果按钮是嵌套的,所有视图向上的视图层次结构都需要提供足够的"空间"或者也需要覆盖点中。 例如:

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

3

继承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)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接