Home iOS Expand Button Click Area
Post
Cancel

iOS Expand Button Click Area

iOS Expand Button Click Area

Rewrite pointInside to expand the touch area

In daily development, it is often encountered that after arranging the UI according to the design, the screen looks beautiful, but the actual operation shows that the button’s touch area is too small, making it difficult to click accurately; especially unfriendly to people with thick fingers.

Completed Example

Completed Example

Before…

Initially, I didn’t delve deeply into this issue and directly overlaid a larger transparent UIButton on the original button, using this transparent button to respond to events. This approach was very cumbersome and difficult to control when there were many components.

Later, I solved it by layout, setting the button to align 0 (or lower) on all sides during layout, and then controlling the imageEdgeInsets, titleEdgeInsets, and contentEdgeInsets parameters to push the Icon/button title to the correct position in the UI design. However, this method is more suitable for projects using Storyboard/xib because you can directly push the layout in Interface Builder. Additionally, the designed Icon should ideally have no spacing, otherwise, it will be difficult to align, sometimes stuck at that 0.5 distance, no matter how you adjust it, it won’t align.

After…

As the saying goes, “seeing more broadens the mind.” Recently, after encountering a new project, I learned a small trick; you can increase the event response range in UIButton’s pointInside. By default, it is UIButton’s Bounds, but we can extend the Bounds size inside to make the button’s clickable area larger!

Based on the above idea, we can:

1
2
3
4
5
6
7
8
9
10
11
12
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);
    }
}

Customize a UIButton, adding the touchEdgeInsets public property to store the range to be expanded, making it convenient for us to use; then override the pointInside method to implement the above idea.

Usage:

1
2
3
4
5
6
7
8
9
10
11
12
13
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 click area/Red is the expanded click area

Play Button/Blue is the original click area/Red is the expanded click area

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

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

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

⚠️⚠️⚠️⚠️⚠️

touchEdgeInsets extends outward from the center of (0,0) itself, so the distances for top, bottom, left, and right should be negative numbers.

Looks good… but:

Replacing every UIButton with a custom MyButton is quite cumbersome and increases the complexity of the program. It might even cause conflicts in large projects.

For functionalities that we believe all UIButtons should inherently have, if possible, we would prefer to directly extend the original UIButton:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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);
    }
}

Use it as described in the previous usage example.

Since Extensions cannot contain properties or it will cause a compilation error “Extensions must not contain stored properties”, 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 principles, please refer to Mao Da’s article )

What about UIImageView (UITapGestureRecognizer)?

For image clicks, we add a Tap gesture to the View; Similarly, we can achieve the same effect by overriding UIImageView’s pointInside.

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

References:

UIView Change Touch Range (Objective-C)

Postscript

Around the same time last year, I wanted to start a small category “ Small things make big things “ to record the trivial daily development tasks. These small tasks, when accumulated, can significantly improve the overall APP experience or the program itself. However, after a year, I only added one more article <( _ _ )>. Small tasks are really easy to forget to record!

If you have any questions or comments, feel free to contact me.

===

本文中文版本

===

This article was first published in Traditional Chinese on Medium ➡️ View Here


This post is licensed under CC BY 4.0 by the author.

Medium One-Year Review

First Experience with iOS Reverse Engineering