UIButton: Making the hit area larger than the default hit area

Viewed 101518

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?

36 Answers

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

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

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

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

Other possibility involves using a UIImage instead of a UIButton.

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.

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

}

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

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

This ensures that a button will always have the recommended minimum touch size of 44 x 44 without needing any manual configuration, calculation, or image 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)
}
Related