简体   繁体   中英

how can I expand the hit area of a specific UIButton in Swift?

In my application I have a UIButton that is quite small, so I thought about increasing the hit area of it.

I found an extension for that:

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

but when I use it, every button in my app has a bigger hit area. I want to increase it to only one specialButton - how can I do it?

You can add a computed property to your extension which you can set in your controller if you want to enable the hit area like this:

fileprivate let minimumHitArea = CGSize(width: 100, height: 100)

extension UIButton {

  var isIncreasedHitAreaEnabled: Bool {
    get {
      // default value is false, so you can easily handle from outside when to enable the increased hit area of your specific button
      return false
    }
    set {

    }
  }

  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 }

    // only if it's true the hit area can be increased
    if isIncreasedHitAreaEnabled {
      // 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
    }
    return self
  }
}

And then in your view controller just set isIncreasedHitAreaEnabled to true if you want to increase it for a specific button (eg in your viewDidLoad ):

override func viewDidLoad() {
  super.viewDidLoad()
  specialButton.isIncreasedHitAreaEnabled = true
}

Update:

First of all the method which you use doesn't work like expected. And also my approach with the computed property did not work like I thought. So even if the method does not work like expected, I just want to update my approach with the computed property . So to set the property from outside, you can use Associated Objects . Also have look here . With this solution now it would be possible to handle the Bool property isIncreasedHitAreaEnabled from your controller:

fileprivate let minimumHitArea = CGSize(width: 100, height: 100)
private var xoAssociationKey: UInt8 = 0

extension UIButton {

  var isIncreasedHitAreaEnabled: Bool {
    get {
      return (objc_getAssociatedObject(self, &xoAssociationKey) != nil)
    }
    set(newValue) {
      objc_setAssociatedObject(self, &xoAssociationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
    }
  }

  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 }

    if isIncreasedHitAreaEnabled {
      // 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
    }
    return self
  }
}

Don't expand the hit area; shrink the drawing area. Make the button a subclass of UIButton, and in that subclass, implement rect methods, along these lines:

class MyButton : UIButton {
    override func contentRect(forBounds bounds: CGRect) -> CGRect {
        return bounds.insetBy(dx: 30, dy: 30)
    }
    override func backgroundRect(forBounds bounds: CGRect) -> CGRect {
        return bounds.insetBy(dx: 30, dy: 30)
    }
}

Now the button is tappable 30 points outside its visible background.

在此处输入图片说明

Create a Custom UIButton Class and override pointInside:(CGPoint)point method like below. Then set these properties value from viewController.

#import <UIKit/UIKit.h>
@interface CustomButton : UIButton
    @property (nonatomic) CGFloat leftArea;
    @property (nonatomic) CGFloat rightArea;
@property (nonatomic) CGFloat topArea;
@property (nonatomic) CGFloat bottomArea;
@end




#import "CustomButton.h
@implementation CustomButton

-(BOOL) pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    CGRect newArea = CGRectMake(self.bounds.origin.x - _leftArea, self.bounds.origin.y - _topArea, self.bounds.size.width + _leftArea + _rightArea, self.bounds.size.height + _bottomArea + _topArea);

    return CGRectContainsPoint(newArea, point);
}
@end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM