Enlarge your ... Hit Area!

January 11th, 2015 3 min read

While I was recently working on a small iOS Swift side project, I stumbled on the issue of poor usability due to a small hitarea of some tiny buttons. Although the icons in the buttons were clear, I noticed tapping the small buttons wasn’t very easy. To solve this, I created my own buttons by subclassing the UIButton class.

Cool, but how does it work?

When a user taps your view, it will check if it hits one of the subviews by recursively calling the following method:

func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView?

This method will make use of an other function to check if the point is in the receiver’s coordinate system:

func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool

By subclassing a UIButton and overwriting the method above, we are able to change the hitarea. I used an additional property to store the desired insets of the hitarea:

class MTButton: UIButton {

    // Create a property to store the hit insets:
    var hitInsets:UIEdgeInsets = UIEdgeInsetsZero

    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {

        // Generate the new hit area by adding the hitInsets:
        let newRect = CGRect(x: 0 + hitInsets.left,
                             y: 0 + hitInsets.top,
                         width: self.frame.size.width - hitInsets.left - hitInsets.right,
                        height: self.frame.size.height - hitInsets.top - hitInsets.bottom)

        // Check if the point is within the new hit area:
        return CGRectContainsPoint(newRect, point)

    }
}
image

Awesome, what does it look like?

Since the default of hitInsets is UIEdgeInsetsZero, the hit area will be the default hitarea:

When we alter hitInsets by setting it to UIEdgeInsets(top: -20, left: -20, bottom: -20, right: -20) the hit area will expand with 20 points in all directions:

In above image, the dark gray box is the frame size of the button. The light gray box represents the new hit area. Of course, this hit area is only visible in this demo.
In above image, the dark gray box is the frame size of the button. The light gray box represents the new hit area. Of course, this hit area is only visible in this demo.

Note that we’ve used negative value’s to expand the hit area. This is because UIEdgeInsetsusually represent Insets, not Outsets. By using positive values, we can make the hit area smaller than the original button. We can even mix positive and negative values:

Okay, any pitfalls?

When I used my subclassed button in a UITableViewCell in a Notification Center (Today) extension, I noticed it didn’t work. Somehow, the UITableViewCell did not call the hitTest(...)method on it’s subviews. This was solved by adding an empty drawRect(rect: CGRect)override to the UITableViewCell:

In above image, the dark gray box is the frame size of the button. The light gray box represents the new hit area. Of course, this hit area is only visible in this demo.

override public func drawRect(rect: CGRect) {
    // This needs to be here to allow interaction outside button bounds.
}

Of course, this is only necessary if your UITableViewCell does not already contains a drawRect(rect: CGRect) override.

I’m not sure why this does work, so if you have a background story on this, please leave a message down below.

If you’re looking for the full source code of this example project, check out GitHub.

Loading comments …
©2021 - MichaelTeeuw.nl