ZhgChg.Li

iOS Button Tap Area Expansion|Enhance User Interaction with Custom pointInside Override

Improve iOS app usability by expanding button tap areas using a custom pointInside method. Solve missed taps and boost user experience with precise touch target enlargement techniques.

iOS Button Tap Area Expansion|Enhance User Interaction with Custom pointInside Override
This article was AI-translated — please let me know if anything looks off.

iOS Expand Button Tap Area

Override pointInside to Expand the Touch Area

In daily development, it’s common to have a UI layout that looks perfect according to the design, but the actual button touch areas are too small, making accurate taps difficult—especially unfriendly for users with larger fingers.

Completed example image

Completed example image

Before…

Regarding this issue, I didn’t research it deeply at first. I simply overlaid a larger transparent UIButton on the original button and used this transparent button to handle events. This approach is very troublesome and hard to manage when there are many components.

Later, the issue was solved by layout adjustments. The button’s top, bottom, left, and right constraints were set to 0 (or less) during layout, then the imageEdgeInsets, titleEdgeInsets, and contentEdgeInsets were controlled to push the icon/button title to the correct UI design position. However, this method is more suitable for projects using Storyboard/xib, as you can adjust the layout directly in Interface Builder. Another point is that the designed icon should preferably have no extra spacing; otherwise, positioning becomes difficult, sometimes stuck at a 0.5 distance that can’t be aligned properly.

After…

As the saying goes, the more you see, the more you know. Recently, after starting a new project, I learned a neat trick: you can enlarge the event response area in UIButton’s pointInside method. By default, it uses UIButton’s bounds, but you can extend the bounds inside to make the button’s clickable area larger!

After the above ideas… we can:

class MyButton: UIButton {
    var touchEdgeInsets:UIEdgeInsets?
    override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        var frame = self.bounds
        
        if let touchEdgeInsets = self.touchEdgeInsets {
            frame = frame.inset(by: touchEdgeInsets)
        }
        
        return frame.contains(point);
    }
}

Create a custom UIButton and add a public property touchEdgeInsets to store the expanded touch area for easy use; then override the pointInside method to implement the above idea.

Usage:

import UIKit

class MusicViewController: UIViewController {

    @IBOutlet weak var playerButton: MyButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        playerButton.touchEdgeInsets = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
    }
    
}

Play button / Blue is the original clickable area / Red is the expanded clickable area

Play button / Blue is the original touch area / Red is the expanded touch area

When using, just remember to set the Button’s Class to our custom MyButton, then you can expand the clickable area for each Button by setting touchEdgeInsets!

️⚠️⚠️⚠️⚠️️️️⚠️️️️

When using Storyboard/xib, remember to set the Custom Class to MyButton

⚠️⚠️⚠️⚠️⚠️

touchEdgeInsets expands outward from (0,0) as the center, so the distances for top, bottom, left, and right must be negative to extend.

Looks good… but:

Replacing every UIButton with a custom MyButton is quite tedious and increases code complexity, which may cause conflicts in large projects.

For this feature that we believe every UIButton should have by default, if possible, we want to directly extend the original UIButton using an Extension:

private var buttonTouchEdgeInsets: UIEdgeInsets?

extension UIButton {
    var touchEdgeInsets:UIEdgeInsets? {
        get {
            return objc_getAssociatedObject(self, &buttonTouchEdgeInsets) as? UIEdgeInsets
        }

        set {
            objc_setAssociatedObject(self,
                &buttonTouchEdgeInsets, newValue,
                .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
    
    override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        var frame = self.bounds
        
        if let touchEdgeInsets = self.touchEdgeInsets {
            frame = frame.inset(by: touchEdgeInsets)
        }
        
        return frame.contains(point);
    }
}

Usage as shown in the previous example.

Since Extensions cannot contain stored properties or they will cause a compile error “Extensions must not contain stored properties,” here we refer to Using Property with Associated Object to associate the external variable buttonTouchEdgeInsets with our Extension, allowing it to be used like a regular property. (For detailed explanation, please see Mao’s article)

What about UIImageView (UITapGestureRecognizer)?

For image taps and tap gestures added to the View,
you can achieve the same effect by overriding UIImageView’s pointInside method.

Done! After continuous improvements, solving this issue has become much simpler and more convenient!

References:

UIView Expand Touch Area (Objective-C)

Postscript

Around the same time last year, I wanted to create a subcategory “Small Things Lead to Big Things” to record daily trivial development tasks. These small tasks quietly add up and improve both the app’s experience and code quality. However, after delaying for a year, I finally added another article <(_ _)>. Small things are really easy to forget to document!

Improve this page
Edit on GitHub
Originally published on Medium
Read the original
Share this essay
Copy link · share to socials
ZhgChgLi
Author

ZhgChgLi

An iOS, web, and automation developer from Taiwan 🇹🇼 who also loves sharing, traveling, and writing.

Comments