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

我有一个关于UIButton和它的点击区域的问题。我正在使用Interface Builder中的Info Dark按钮,但我发现点击区域对于某些人的手指来说不够大。

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

101765 次浏览

好吧,你可以把你的UIButton放在一个透明的稍大的UIView中,然后在UIButton中捕捉UIView实例上的触摸事件。这样,你仍然会有你的按钮,但有一个更大的触摸区域。您必须手动处理所选的&;如果用户触摸视图而不是按钮,则突出显示按钮上的状态。

另一种可能是使用UIImage而不是UIButton.

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

  1. 选中自定义按钮的“突出显示触摸”选项。白色辉光将出现在信息按钮上,但在大多数情况下,用户的手指将覆盖这一点,他们将看到的只是周围的辉光。

  2. 为信息按钮设置一个IBOutlet,为自定义按钮设置两个IBActions,一个用于“触摸”,一个用于“内部触摸”。然后,在Xcode中,让Touchdown事件将Info按钮的Highlighted属性设置为Yes,让TouchupInside事件将Highlighted属性设置为No.

我已经能够以编程方式增加信息按钮的点击区域。“ 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 *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)];

不要设置图像的backgroundImage属性,而应设置imageView属性。此外,确保将imageView.contentMode设置为UIViewContentModeCenter

只需在Interface Builder中设置图像边缘插入值。

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

我对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];
}

由于我使用的是背景图像,这些解决方案都不适合我。下面是一个解决方案,它做了一些有趣的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")。

我使用了一种更通用的方法,将-[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}}

您还可以将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)
}

目标-C

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

我已经跟踪了Chase的响应,它工作得很好,一个问题是当您创建的Arrea太大,大于按钮被取消选择的区域(如果区域不大)时,它不会调用UIControleventTouchupInside事件的选择器。

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

不要更改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

从不重写类别中的方法。子分类按钮和覆盖- pointInside:withEvent:。例如,如果按钮的边小于44px(这是建议的最小可点击区域),请使用以下命令:

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

所提供的答案没有任何问题;然而,我想扩展Jlarjlar的回答,因为它拥有惊人的潜力,可以为其他控件(例如搜索栏)的相同问题增加价值。这是因为由于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

在Storyboard/xib文件中,选择有问题的控件,打开Identity Inspector并键入自定义类的名称。

mngbutton

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

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

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

除了覆盖PointInside方法以及CgrectinsetCGRectContainsPoint方法之外,应当花时间检查CGGeometry以扩展任何UIView子类的矩形触摸区域。您还可以在希普斯特中找到有关CGGeometry用例的一些很好的提示。

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

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

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

我玩这个游戏太晚了,但我想用一个简单的技巧来解决你的问题。下面是我的一个典型的编程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设置框架,并为Retina缩放50%。好吧,也许你同意或不同意上面的观点,但如果你想让打击范围更大,让自己免于头痛:

我所做的是,在Photoshop中打开图像,然后简单地将画布大小增加到120%并保存。实际上,您只是使用透明像素使图像变大。

只有一种方法。

下面是一个使用Swift扩展的优雅解决方案。根据苹果的人机界面指南(https://developer.apple.com/ios/human-interface-guidelines/visual-design/layout/),它为所有UIButton提供了至少44x44点的点击区域。

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

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

正是为了这个目的,我做了一个图书馆

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

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

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

就像其他解决方案一样,它使用- (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,而无需编写任何代码: enter image description here

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

这是Chase在Swift 3.0中的UIButton+扩展。


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)

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

相反,我通过使PNG透明区域更大来扩大Tap区域。

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

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

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

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

SWIFT:

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

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

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

这是我的斯威夫特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
}
}
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)
}
  • 而不在类别或扩展中覆盖
  • 根据指南,最小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
}
}

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

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


}

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

就这么简单:

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

这就是整件事。

我看到很多解决方案要么没有完全达到目标,要么需要指定一些固定的插入来添加。下面是一个简单的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)
}