[ACCEPTED]-UIButton: Making the hit area larger than the default hit area-uikit

Accepted answer
Score: 137

Since I am using a background image, none 12 of these solutions worked well for me. Here 11 is a solution that does some fun objective-c 10 magic and offers a drop in solution with 9 minimal code.

First, add a category to UIButton that 8 overrides the hit test and also adds a property 7 for expanding the hit test frame.

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

Once this 6 class is added, all you need to do is set 5 the edge insets of your button. Note that 4 I chose to add the insets so if you want 3 to make the hit area larger, you must use 2 negative numbers.

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

Note: Remember to import 1 the category (#import "UIButton+Extensions.h") in your classes.

Score: 78

Just set the image edge inset values in 1 interface builder.

Score: 66

Here's an elegant solution using Extensions 3 in Swift. It gives all UIButtons a hit area 2 of at least 44x44 points, as per Apple's 1 Human Interface Guidelines (https://developer.apple.com/ios/human-interface-guidelines/visual-design/layout/)

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

Swift 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
    }
}
Score: 51

You could also subclass UIButton or a custom UIView and 1 override point(inside:with:) with something like:

Swift 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);
}
Score: 35

Here's Chase's UIButton+Extensions in Swift 1 3.0.


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

To use it, you can:

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

I recommend placing a UIButton with type 16 Custom centered over your info button. Resize 15 the custom button to the size you want the 14 hit area to be. From there you have two 13 options:

  1. Check the 'Show touch on highlight' option 12 of the custom button. The white glow will 11 appear over the info button, but in most 10 cases the users finger will cover this and 9 all they will see is the glow around the 8 outside.

  2. Set up an IBOutlet for the info 7 button and two IBActions for the custom 6 button one for 'Touch Down' and one for 5 the 'Touch Up Inside'. Then in Xcode make 4 the touchdown event set the highlighted 3 property of the info button to YES and the 2 touchupinside event set the highlighted 1 property to NO.

Score: 19

Don't set the backgroundImage property with your image, set 2 the imageView property. Also, make sure you have 1 imageView.contentMode set at UIViewContentModeCenter.

Score: 14

My solution on 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)
    }
}

0

Score: 12

There is nothing wrong with the answers 33 presented; however I wanted to extend jlarjlar's answer as 32 it holds amazing potential that can add 31 value to the same problem with other controls 30 (e.g. SearchBar). This is because since 29 pointInside is attached to a UIView, one 28 is able to subclass any control to improve the touch 27 area. This answer also shows a full sample 26 of how to implement the complete solution.

Create 25 a new subclass for your button (or any control)

#import <UIKit/UIKit.h>

@interface MNGButton : UIButton

@end

Next 24 override the pointInside method in your 23 subclass implementation

@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

On your storyboard/xib 22 file select the control in question and 21 open the identity inspector and type in 20 the name of your custom class.

mngbutton

In your UIViewController 19 class for scene containing the button, change 18 the class type for the button to the name 17 of your subclass.

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

Link your storyboard/xib 16 button to the property IBOutlet and your 15 touch area will be expanded to fit the area 14 defined in the subclass.

In addition to overriding 13 the pointInside method together with the CGRectInset and CGRectContainsPoint methods, one 12 should take time to examine the CGGeometry for extending 11 the rectangular touch area of any UIView 10 subclass. You may also find some nice tips 9 on CGGeometry use-cases at NSHipster.

For example 8 one could make the touch area irregular 7 using the methods mentioned above or simply 6 choose to make the width touch area twice 5 as large as the horizontal touch area:

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

NB: Substituting 4 any UI Class control should produce similar 3 results on extending the touch area for 2 different controls (or any UIView subclass, like 1 UIImageView, etc).

Score: 10

This works for me:

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)];

0

Score: 7

I'm using a more generic approach by swizzling 15 -[UIView pointInside:withEvent:]. This allows me to modify hit testing behavior 14 on any UIView, not just UIButton.

Oftentimes, a button 13 is placed inside a container view that also 12 limits the hit testing. For instance, when 11 a button is at the top of a container view 10 and you want to extend the touch target 9 upwards, you also have to extend the touch 8 target of the container view.

@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

The nice thing 7 about this approach is you can use this 6 even in Storyboards by adding a User Defined 5 Runtime Attribute. Sadly, UIEdgeInsets is not directly 4 available as a type there but since CGRect also 3 consists of a struct with four CGFloat it works 2 flawlessly by choosing "Rect" and filling 1 in the values like this: {{top, left}, {bottom, right}}.

Score: 7

Don't change the behavior of 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

0

Score: 6

I'm using the following class in Swift, to 2 also enable an Interface Builder property 1 to adjust the margin:

@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)
    }
}
Score: 5

I've been able to increase the hit area 9 of the info button programmatically. The 8 "i" graphic doesn't change scale and remains 7 centered in the new button frame.

The size 6 of the info button seems to be fixed to 5 18x19[*] in Interface Builder. By connecting 4 it to an IBOutlet, I was able to change 3 its frame size in code without any issues.

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

[*]: Later 2 versions of iOS appear to have increased 1 the hit area of the info button.

Score: 5

Well, you can place your UIButton inside 9 a transparent and slightly bigger UIView, and 8 then catch the touch events on the UIView 7 instance as in the UIButton. That way, you 6 will still have your button, but with a 5 bigger touch area. You will manually have 4 to deal with selected & highlighted 3 states con the button if the user touches 2 the view instead of the button.

Other possibility 1 involves using a UIImage instead of a UIButton.

Score: 5

This is my Swift 3 Solution(based on this blogpost: 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
    }
}

0

Score: 3

Chase's custom hit test implemented as a 5 subclass of UIButton. Written in Objective-C.

It 4 seems to work both for init and buttonWithType: constructors. For 3 my needs it's perfect, but since subclassing 2 UIButton can be hairy, I'd be interested to know 1 if anyone has a fault with it.

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
Score: 3

Implementation through override inherited 1 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)
    }
}
Score: 3

Base on giaset's answer above (which I found 2 the most elegant solution), here is the 1 swift 3 version:

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
    }
}
Score: 3

It's just this simple:

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

That's the whole thing.

0

Score: 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

0

Score: 2

Never override method in category. Subclass 4 button and override - pointInside:withEvent:. For example if your 3 button's side is smaller than 44 px (which 2 is recommended as minimum tappable area) use 1 this:

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

I made a library for this very purpose.

You can 10 choose to use a UIView category, no subclassing required:

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

Or you can subclass 9 your UIView or UIButton and set the minimumHitTestWidth and/or 8 minimumHitTestHeight. Your button hit-test area will then be 7 represented by these 2 values.

Just like 6 other solutions, it uses the - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event method. The 5 method is called when iOS performs hit-testing. This blog 4 post has a good description on how iOS hit-testing 3 works.

https://github.com/kgaidis/KGHitTestingViews

@interface KGHitTestingButton : UIButton <KGHitTesting>

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

@end

You can also just subclass and use 2 the Interface Builder without writing any 1 code: enter image description here

Score: 1

I'm use this trick for button inside tableviewcell.accessoryView 1 to enlarge its touch area

#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];
}
Score: 1

I just did the port of the @Chase solution in swift 2 2.2

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

an you can use like this

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

For any other 1 reference see https://stackoverflow.com/a/13067285/1728552

Score: 1

Similar to Zhanserik's, with variable extension 1 and updated for 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)
    }

}
Score: 1

@antoine's answer formatted for 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

Score: 1

I'm seeing a lot of solutions that either 11 don't quite hit the mark or require specifying 10 some fixed insets to add. Here's a solution 9 for a simple UIView subclass that will extend 8 the hit rect of the view to at least 44 x 44; if 7 either of the dimensions is already greater 6 than that, then it doesn't artificially 5 pad that dimension.

This ensures that a button 4 will always have the recommended minimum 3 touch size of 44 x 44 without needing any 2 manual configuration, calculation, or image 1 padding:

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)
}
Score: 0

I am so late to this game, but wanted to 13 weigh in on a simple technique that might 12 solve your problems. Here is a typical programmatic 11 UIButton snippet for me:

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];

I'm loading a transparent 10 png Image for my button and setting the 9 background image. I'm setting the frame 8 based on the UIImage and scaling by 50% for 7 retina. OK, maybe you agree with the above 6 or not, BUT if you want to make the hit 5 area BIGGER and save yourself a headache:

What 4 I do, open the image in photoshop and simply 3 increase the canvas size to 120% and save. Effectively 2 you've just made the image bigger with transparent 1 pixels.

Just one approach.

Score: 0

None of the answers works perfect for me, because 5 I use background image and a title on that 4 button. Moreover, the button will resize 3 as screen size changes.

Instead, I enlarge 2 the tap area by making the png transparent 1 area larger.

Score: 0

This Swift version lets you define a minimum 3 hit size for all UIButtons. Crucially, it 2 also handles the case when UIButtons are 1 hidden, which many answers neglect.

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
    }
}
Score: 0

I have followed Chase's response and it 7 works great, one single problem when you 6 create the arrea too big, bigger than the 5 zone where the button gets deselected (if 4 the zone wasn't bigger) it doesn't call 3 the selector for the UIControlEventTouchUpInside 2 event.

I think the size is over 200 any any 1 direction or something like that.

Score: 0

@jlajlar 's answer above seemed good and 6 straightforward but does not match Xamarin.iOS, so 5 I converted it into Xamarin. If you looking 4 for a solution on a Xamarin iOS, there here 3 it goes:

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

You can add this method to the class 2 where you are overriding UIView or UIImageView. This 1 worked nicely :)

More Related questions