By Kevin Bomberry


2009-04-30 18:54:52 8 Comments

I have a question dealing with UIButton and its hit area. I am using the Info Dark button in interface builder, but I am finding that the hit area is not large enough for some people's fingers.

Is there a way to increase the hit area of a button either programmatically or in Interface Builder without changing the size of the InfoButton graphic?

30 comments

@spfursich 2019-03-18 22:06:53

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

@MT1asli8ghwoi 2019-02-22 02:41:03

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

}

@Chase 2012-10-25 11:08:58

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

First, add a category to UIButton that overrides the hit test and also adds a property 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 class is added, all you need to do is set the edge insets of your button. Note that I chose to add the insets so if you want to make the hit area larger, you must use negative numbers.

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

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

@honus 2013-07-19 02:41:42

Overriding a method in a category is a bad idea. What if another category also tries to override pointInside:withEvent:? And the call to [super pointInside:withEvent] isn't calling UIButton's version of the call (if it has one)but UIButton's parent's version.

@Chase 2013-07-19 03:09:06

@honus You are right and Apple does not recommend doing this. That being said, as long as this code is not in a shared library and just local to your application, you should be fine.

@Sid 2014-01-13 16:49:51

Hi Chase, is there any disadvantage of using a subclass instead?

@Chase 2014-01-14 01:30:51

@Sid, subclassing UIButton is tricky because the initializer is a static method. Adding methods with a category means you don't have to explicitly use your custom class which can be convenient.

@Resty 2014-03-28 04:15:20

You're doing too much unnecessary work, you can do two simple lines of code: [[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"arrow.png"] forState:UIControlStateNormal]; And just set width & height that you need:` backButton.frame = CGRectMake(5, 28, 45, 30);`

@Wim Fikkert 2014-06-17 08:51:32

I like this approach, works like a charm for me. Thanks

@mattsson 2014-07-01 08:24:58

@Resty, he's using the background image, meaning that your approach won't work.

@uliwitness 2016-04-30 15:19:38

@Chase While your claim of the code not being in a shared library is theoretically true, if Apple one day decide to implement their code in a category, there is still no guarantee which one of you wins. Using categories to override stuff is a) bad style and b) not reliable. And this is exactly what subclasses are for. Please change this to use a subclass.

@Chase 2016-05-02 09:20:01

@uliwitness I don't disagree. This sort of monkey patching is not ideal. I don't have a subclass solution unfortunately. Feel free to provide one.

@Phillip Martin 2016-06-16 18:17:07

I didn't realize how cool of a solution this was until I dove into the code more. Then, since there was this talk of subclassing, I tried my hand at a subclass implementation - stackoverflow.com/a/37866101/4403600.

@Tim Friedland 2017-05-09 12:33:33

You saved my day! So elegant! Thank you!

@Zhanserik 2017-08-01 11:36:17

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

@Jacob Jennings 2017-07-19 04:02:45

  • Without overriding in categories or extensions
  • 44x44 minimum per guidelines

My take:

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

@Azon 2017-05-24 16:50:45

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

@dcRay 2015-08-14 04:12:27

Here's Chase's UIButton+Extensions in Swift 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)

@Unome 2015-12-14 23:02:17

Nice work. I found this very helpful.

@Valentin Shergin 2016-08-10 22:42:39

Actually, we should extend UIControl, not UIButton.

@Valentin Shergin 2016-08-10 22:44:04

And variable pTouchAreaEdgeInsets must be Int, not UIEdgeInsets. (Actually, it can be any type, but Int is the most generic and simple type, so it is common in such construction). (Therefore I do love this approach in general. Thank you, dcRay!)

@jlajlar 2012-12-20 18:02:57

You could also subclass UIButton or a custom UIView and 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);
}

@Matrosov Alexander 2013-04-08 21:48:24

thanks really cool solution without any additional categories. Also I simple integrate it with my buttons that I have in XIB and it works good. great answer

@Tommie C. 2014-09-08 15:19:03

This is a great solution that can be extended to all other controls! I found it useful to subclass a UISearchBar for example. The pointInside method is on every UIView since iOS 2.0. BTW - Swift: func pointInside(_ point: CGPoint, withEvent event: UIEvent!) -> Bool.

@Yanchi 2015-02-17 16:14:34

Thanks for this! As other said, this can be really useful with other controls!

@Vojta 2016-10-28 12:11:59

This is great!! Thanks, you saved me a lot of time! :-)

@giaset 2014-12-29 04:58:43

Here's an elegant solution using Extensions in Swift. It gives all UIButtons a hit area of at least 44x44 points, as per Apple's 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
    }
}

@Josh Gafni 2015-09-10 01:44:34

You need to test if the button is currently hidden. If so, return nil.

@Crashalot 2016-04-06 17:38:44

Thanks, this looks great. Any potential downsides to be aware of?

@xtrinch 2016-04-29 14:24:16

I like this solution, doesn't require me to set additional properties on all buttons. Thanks

@Crashalot 2016-06-21 01:40:29

The downside is if the UIButton is hidden, this will still return true! Must see if the button is hidden or not.

@NoodleOfDeath 2016-09-06 14:48:06

If Apple guidelines recommend these dimensions for the hit area, why make us have to fix their failure to implement? Is this addressed in later SDK's?

@Womble 2016-09-08 03:02:33

Thank goodness we have Stack Overflow, and its contributors. Because we certainly can't rely on Apple for useful, timely, accurate information on how to fix their most common, basic problems.

@Dmitry 2016-11-16 09:45:13

Really nice solution!

@Valerio Mazzeo 2017-02-21 09:13:12

I am not sure overriding functions in extension is a good idea...

@miss Gbot 2017-08-15 10:21:43

I'm just adding this to subclass of UIButton and use it. thank you!

@user2104363 2017-01-20 15:00:19

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

@Nhon Nguyen 2016-09-16 14:58:22

Base on giaset's answer above (which I found the most elegant solution), here is the 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
    }
}

@Serluca 2016-08-03 17:07:06

I just did the port of the @Chase solution in swift 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 reference see https://stackoverflow.com/a/13067285/1728552

@A.G 2016-07-20 09:53:51

Swift:

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

@Crashalot 2016-06-21 01:58:29

This Swift version lets you define a minimum hit size for all UIButtons. Crucially, it also handles the case when UIButtons are 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
    }
}

@dimpiax 2016-06-17 22:22:43

Implementation through override inherited 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)
    }
}

@Phillip Martin 2016-06-16 17:48:29

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

It seems to work both for init and buttonWithType: constructors. For my needs it's perfect, but since subclassing UIButton can be hairy, I'd be interested to know 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

@kgaidis 2015-05-17 00:50:41

I made a library for this very purpose.

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

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

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

Just like other solutions, it uses the - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event method. The method is called when iOS performs hit-testing. This blog post has a good description on how iOS hit-testing 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 the Interface Builder without writing any code: enter image description here

@user3344977 2016-01-18 03:06:22

I ended up installing this just for the sake of speed. Adding IBInspectable was a great idea thanks for putting this together.

@Jakehao 2016-04-13 08:10:18

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

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

@Ortwin Gentz 2012-11-20 14:11:34

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

Oftentimes, a button is placed inside a container view that also limits the hit testing. For instance, when a button is at the top of a container view and you want to extend the touch target upwards, you also have to extend the touch 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 about this approach is you can use this even in Storyboards by adding a User Defined Runtime Attribute. Sadly, UIEdgeInsets is not directly available as a type there but since CGRect also consists of a struct with four CGFloat it works flawlessly by choosing "Rect" and filling in the values like this: {{top, left}, {bottom, right}}.

@Antoine 2015-06-23 12:51:04

I'm using the following class in Swift, to also enable an Interface Builder property 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)
    }
}

@TomSawyer 2015-10-12 19:49:12

how to manually change this pointInside? If i've created an UIButton with old margin, but now i want to change it?

@Has AlTaiar 2015-03-20 01:07:36

@jlajlar 's answer above seemed good and straightforward but does not match Xamarin.iOS, so I converted it into Xamarin. If you looking for a solution on a Xamarin iOS, there here 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 where you are overriding UIView or UIImageView. This worked nicely :)

@chrisallick 2014-11-06 18:29:38

I am so late to this game, but wanted to weigh in on a simple technique that might solve your problems. Here is a typical programmatic 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 png Image for my button and setting the background image. I'm setting the frame based on the UIImage and scaling by 50% for retina. OK, maybe you agree with the above or not, BUT if you want to make the hit area BIGGER and save yourself a headache:

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

Just one approach.

@Tommie C. 2014-09-08 16:40:58

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

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

#import <UIKit/UIKit.h>

@interface MNGButton : UIButton

@end

Next override the pointInside method in your 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 file select the control in question and open the identity inspector and type in the name of your custom class.

mngbutton

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

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

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

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

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

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

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

@Rich86man 2014-12-10 00:20:19

This is really cool. I had no idea you could do that. Great solution!!!

@SpaceDog 2016-11-08 04:19:10

brilliant!!!!!!!

@user500 2014-06-06 16:50:09

Never override method in category. Subclass button and override - pointInside:withEvent:. For example if your button's side is smaller than 44 px (which is recommended as minimum tappable area) use 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));
}

@user3109079 2013-12-16 22:29:25

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

@James O'Brien 2014-05-13 03:01:34

If your button is 40x40, this method won't be called if the negative numbers are greater - like someone else mentioned. Otherwise this works.

@arsenius 2014-10-28 04:26:55

This doesn't work for me. hitFrame appears calculated properly, but pointInside: is never called when the point is outside of self.bounds. if (CGRectContainsPoint(hitFrame, point) && !CGRectContainsPoint(self.bounds, point)){ NSLog(@"Success!"); // Never called }

@Maurizio 2010-11-06 04:45:30

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

@arlomedia 2012-09-07 10:49:28

More specifically, set the button's contentMode property to UIViewContentModeCenter. Then you can make the button's frame larger than the image. Works for me!

@Enrico Susatyo 2013-04-19 00:47:07

FYI, UIButton does not respect UIViewContentMode: stackoverflow.com/a/4503920/361247

@Maurizio 2013-04-19 13:46:02

@EnricoSusatyo sorry, I've clarified what APIs I was talking about. The imageView.contentMode can be set as UIContentModeCenter.

@Resty 2014-03-27 12:46:24

This works perfectly, thanks for advice! [[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"image.png"] forState:UIControlStateNormal];

@Anil 2014-05-17 15:18:20

@Resty The above mentioned comment doesnt work for me :/ The image is resizing to the larger set frame size.

@Resty 2014-05-19 04:45:11

@Anil ok, I will try to give more details, it works when you set more Width & Height in CGRectMake, so make your button a bit larger, I recommend you to do it with custom image, here is my full working example: ` UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom]; backButton.frame = CGRectMake(100, 100, 45, 35); [[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"arrow.png"] forState:UIControlStateNormal]; // "arror.png" size is 19x34 [self.view addSubview:backButton]; //hit area is 45x35`

@Anil 2014-05-19 05:27:17

I tried doing the same but couldn't get it to work. Finally overrode UIButton and changed it under hitTest. Its working fine now.

@Unreal Dragon 2013-04-12 15:07:28

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

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

@otto 2009-10-13 23:55:17

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

The size of the info button seems to be fixed to 18x19[*] in Interface Builder. By connecting it to an IBOutlet, I was able to change 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 versions of iOS appear to have increased the hit area of the info button.

@abuharsky 2012-08-08 15:07:31

I'm use this trick for button inside tableviewcell.accessoryView 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];
}

@William Jockusch 2012-08-03 02:09:28

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

Related Questions

Sponsored Content

3 Answered Questions

[SOLVED] How to increase the UIButton hittest area?

7 Answered Questions

[SOLVED] Emulating aspect-fit behaviour using AutoLayout constraints in Xcode 6

3 Answered Questions

[SOLVED] Make UIButton bigger than its image

  • 2017-03-06 06:02:49
  • theAlse
  • 1173 View
  • 2 Score
  • 3 Answer
  • Tags:   ios swift uibutton

2 Answered Questions

3 Answered Questions

[SOLVED] xcode iOS increase UIButton hit area

  • 2014-10-06 21:50:44
  • Jim Bak
  • 5910 View
  • 6 Score
  • 3 Answer
  • Tags:   ios uibutton xcode6

0 Answered Questions

Swift: How do i change existing UIButton larger hit area?

1 Answered Questions

[SOLVED] Subclassing UIButton and make it IBDesignable

1 Answered Questions

3 Answered Questions

[SOLVED] UIButton: Need a circle hit area

3 Answered Questions

[SOLVED] UIButton hit area is too big

Sponsored Content