web-dev-qa-db-fra.com

UIButton: Rendre la zone de résultats plus grande que la zone de résultats par défaut

J'ai une question concernant UIButton et sa région critique. J'utilise le bouton Info sombre dans le générateur d'interface, mais je constate que la zone touchée n'est pas assez grande pour les doigts de certaines personnes.

Existe-t-il un moyen d'augmenter la zone active d'un bouton, soit par programme, soit dans Interface Builder sans modifier la taille du graphique InfoButton?

184
Kevin Bomberry

Étant donné que j'utilise une image d'arrière-plan, aucune de ces solutions n'a bien fonctionné pour moi. Voici une solution qui fait quelque chose d’amusant avec magic-objective et qui propose une solution d’ajout direct avec un code minimal.

Tout d’abord, ajoutez à UIButton une catégorie qui remplace le test d’attraction et ajoute une propriété pour développer le cadre de test.

UIButton + Extensions.h

@interface UIButton (Extensions)

@property(nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;

@end

UIButton + Extensions.m

#import "UIButton+Extensions.h"
#import <objc/runtime.h>

@implementation UIButton (Extensions)

@dynamic hitTestEdgeInsets;

static const NSString *KEY_HIT_TEST_Edge_INSETS = @"HitTestEdgeInsets";

-(void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets {
    NSValue *value = [NSValue value:&hitTestEdgeInsets withObjCType:@encode(UIEdgeInsets)];
    objc_setAssociatedObject(self, &KEY_HIT_TEST_Edge_INSETS, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(UIEdgeInsets)hitTestEdgeInsets {
    NSValue *value = objc_getAssociatedObject(self, &KEY_HIT_TEST_Edge_INSETS);
    if(value) {
        UIEdgeInsets edgeInsets; [value getValue:&edgeInsets]; return edgeInsets;
    }else {
        return UIEdgeInsetsZero;
    }
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    if(UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero) || !self.enabled || self.hidden) {
        return [super pointInside:point withEvent:event];
    }

    CGRect relativeFrame = self.bounds;
    CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.hitTestEdgeInsets);

    return CGRectContainsPoint(hitFrame, point);
}

@end

Une fois cette classe ajoutée, il vous suffit de définir les incrustations Edge de votre bouton. Notez que j'ai choisi d'ajouter les incrustations, donc si vous voulez agrandir la zone touchée, vous devez utiliser des nombres négatifs.

[button setHitTestEdgeInsets:UIEdgeInsetsMake(-10, -10, -10, -10)];

Remarque: n'oubliez pas d'importer la catégorie (#import "UIButton+Extensions.h") dans vos cours.

138
Chase

Il suffit de définir les valeurs d'incrustation de l'image dans le générateur d'interface.

75
Samuel Goodwin

Voici une solution élégante utilisant des extensions dans Swift. Il donne à tous les boutons UIB une zone d’accès d’au moins 44x44 points, conformément aux consignes relatives aux interfaces utilisateur d’Apple ( https://developer.Apple.com/ios/human-interface-guidelines/visual-design/layout/ =)

Swift 2:

private let minimumHitArea = CGSizeMake(44, 44)

extension UIButton {
    public override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        // if the button is hidden/disabled/transparent it can't be hit
        if self.hidden || !self.userInteractionEnabled || 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 = CGRectInset(self.bounds, -widthToAdd / 2, -heightToAdd / 2)

        // perform hit test on larger frame
        return (CGRectContainsPoint(largerFrame, point)) ? self : nil
    }
}

Swift 3:

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

Vous pouvez également sous-classer UIButton ou un UIView personnalisé et remplacer point(inside:with:) par quelque chose comme:

Swift

override func point(inside point: CGPoint, with _: UIEvent?) -> Bool {
    let margin: CGFloat = 5
    let area = self.bounds.insetBy(dx: -margin, dy: -margin)
    return area.contains(point)
}

Objective-C

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGFloat margin = 5.0;
    CGRect area = CGRectInset(self.bounds, -margin, -margin);
    return CGRectContainsPoint(area, point);
}
46
jlajlar

Voici les extensions UIButton + de Chase dans Swift 3.0.


import UIKit

private var pTouchAreaEdgeInsets: UIEdgeInsets = .zero

extension UIButton {

    var touchAreaEdgeInsets: UIEdgeInsets {
        get {
            if let value = objc_getAssociatedObject(self, &pTouchAreaEdgeInsets) as? NSValue {
                var edgeInsets: UIEdgeInsets = .zero
                value.getValue(&edgeInsets)
                return edgeInsets
            }
            else {
                return .zero
            }
        }
        set(newValue) {
            var newValueCopy = newValue
            let objCType = NSValue(uiEdgeInsets: .zero).objCType
            let value = NSValue(&newValueCopy, withObjCType: objCType)
            objc_setAssociatedObject(self, &pTouchAreaEdgeInsets, value, .OBJC_ASSOCIATION_RETAIN)
        }
    }

    open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        if UIEdgeInsetsEqualToEdgeInsets(self.touchAreaEdgeInsets, .zero) || !self.isEnabled || self.isHidden {
            return super.point(inside: point, with: event)
        }

        let relativeFrame = self.bounds
        let hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.touchAreaEdgeInsets)

        return hitFrame.contains(point)
    }
}

Pour l'utiliser, vous pouvez:

button.touchAreaEdgeInsets = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
33
dcRay

Ne définissez pas la propriété backgroundImage avec votre image, définissez la propriété imageView. Aussi, assurez-vous que vous avez imageView.contentMode réglé sur UIViewContentModeCenter.

19
Maurizio

Je recommande de placer un UIButton avec le type Custom centré sur votre bouton d'information. Redimensionnez le bouton personnalisé à la taille souhaitée pour la zone touchée. De là, vous avez deux options:

  1. Cochez l'option "Afficher les mises en surbrillance" du bouton personnalisé. La lueur blanche apparaîtra au-dessus du bouton d’information, mais dans la plupart des cas, le doigt de l’utilisateur couvrira ce problème et tout ce qu’ils verront, c’est la lueur autour de l’extérieur.

  2. Configurez un IBOutlet pour le bouton d’information et deux IBActions pour le bouton personnalisé, l’un pour "Touch Down" et l’autre pour le "Touch Up Inside". Dans Xcode, définissez ensuite l’effet touchdown sur la propriété en surbrillance du bouton d’information et sur l’événement touchupinside, définissez la propriété en surbrillance sur NO.

18
Kyle

Ma solution sur Swift 3:

class MyButton: UIButton {

    override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        let relativeFrame = self.bounds
        let hitTestEdgeInsets = UIEdgeInsetsMake(-25, -25, -25, -25)
        let hitFrame = UIEdgeInsetsInsetRect(relativeFrame, hitTestEdgeInsets)
        return hitFrame.contains(point)
    }
}
13
Zhanserik

Il n'y a rien de mal avec les réponses présentées; Cependant, je voulais étendre réponse de jlarjlar car il présente un potentiel incroyable qui peut ajouter de la valeur au même problème avec d'autres contrôles (par exemple, SearchBar). En effet, étant donné que pointInside est attaché à UIView, il est possible de sous-classer n’importe quel contrôle pour améliorer la zone tactile. Cette réponse montre également un exemple complet de la mise en œuvre de la solution complète.

Créez une nouvelle sous-classe pour votre bouton (ou n'importe quel contrôle)

#import <UIKit/UIKit.h>

@interface MNGButton : UIButton

@end

Ensuite, remplacez la méthode pointInside dans l'implémentation de votre sous-classe.

@implementation MNGButton


-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    //increase touch area for control in all directions by 20
    CGFloat margin = 20.0;
    CGRect area = CGRectInset(self.bounds, -margin, -margin);
    return CGRectContainsPoint(area, point);
}


@end

Sur votre fichier storyboard/xib, sélectionnez le contrôle en question, ouvrez l'inspecteur d'identité et entrez le nom de votre classe personnalisée.

mngbutton

Dans votre classe UIViewController pour la scène contenant le bouton, remplacez le type de classe du bouton par le nom de votre sous-classe.

@property (weak, nonatomic) IBOutlet MNGButton *helpButton;

Liez votre bouton storyboard/xib à la propriété IBOutlet et votre zone tactile sera étendue pour correspondre à la zone définie dans la sous-classe.

En plus de remplacer les méthodes pointInside avec les méthodes CGRectInset et CGRectContainsPoint , il faut prendre le temps d'examiner les CGGeometry = pour l'extension de la zone tactile rectangulaire de toute sous-classe UIView. Vous pouvez également trouver quelques astuces intéressantes sur les cas d'utilisation de CGGeometry à NSHipster .

Par exemple, vous pouvez rendre la zone tactile irrégulière en utilisant les méthodes mentionnées ci-dessus ou simplement choisir de rendre la zone tactile en largeur deux fois plus grande que la zone tactile horizontale:

CGRect area = CGRectInset(self.bounds, -(2*margin), -margin);

NB: La substitution de tout contrôle de classe d’UI devrait produire des résultats similaires sur l’extension de la zone tactile de différents contrôles (ou de toute sous-classe UIView, comme UIImageView, etc.).

12
Tommie C.

Cela fonctionne pour moi:

UIButton *button = [UIButton buttonWithType: UIButtonTypeCustom];
// set the image (here with a size of 32 x 32)
[button setImage: [UIImage imageNamed: @"myimage.png"] forState: UIControlStateNormal];
// just set the frame of the button (here 64 x 64)
[button setFrame: CGRectMake(xPositionOfMyButton, yPositionOfMyButton, 64, 64)];
10
Olof

Ne changez pas le comportement de UIButton.

@interface ExtendedHitButton: UIButton

+ (instancetype) extendedHitButton;

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

@end

@implementation ExtendedHitButton

+ (instancetype) extendedHitButton {
    return (ExtendedHitButton *) [ExtendedHitButton buttonWithType:UIButtonTypeCustom];
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGRect relativeFrame = self.bounds;
    UIEdgeInsets hitTestEdgeInsets = UIEdgeInsetsMake(-44, -44, -44, -44);
    CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, hitTestEdgeInsets);
    return CGRectContainsPoint(hitFrame, point);
}

@end
7
user3109079

J'utilise une approche plus générique en balançant -[UIView pointInside:withEvent:]. Cela me permet de modifier le comportement de test de hit sur n'importe quel UIView, pas seulement UIButton.

Souvent, un bouton est placé dans une vue de conteneur qui limite également le test d'accès. Par exemple, lorsqu'un bouton se trouve en haut d'une vue conteneur et que vous souhaitez étendre la cible tactile vers le haut, vous devez également étendre la cible tactile de la vue conteneur.

@interface UIView(Additions)
@property(nonatomic) UIEdgeInsets hitTestEdgeInsets;
@end

@implementation UIView(Additions)

+ (void)load {
    Swizzle(self, @selector(pointInside:withEvent:), @selector(myPointInside:withEvent:));
}

- (BOOL)myPointInside:(CGPoint)point withEvent:(UIEvent *)event {

    if(UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero) || self.hidden ||
       ([self isKindOfClass:UIControl.class] && !((UIControl*)self).enabled))
    {
        return [self myPointInside:point withEvent:event]; // original implementation
    }
    CGRect hitFrame = UIEdgeInsetsInsetRect(self.bounds, self.hitTestEdgeInsets);
    hitFrame.size.width = MAX(hitFrame.size.width, 0); // don't allow negative sizes
    hitFrame.size.height = MAX(hitFrame.size.height, 0);
    return CGRectContainsPoint(hitFrame, point);
}

static char hitTestEdgeInsetsKey;
- (void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets {
    objc_setAssociatedObject(self, &hitTestEdgeInsetsKey, [NSValue valueWithUIEdgeInsets:hitTestEdgeInsets], OBJC_ASSOCIATION_RETAIN);
}

- (UIEdgeInsets)hitTestEdgeInsets {
    return [objc_getAssociatedObject(self, &hitTestEdgeInsetsKey) UIEdgeInsetsValue];
}

void Swizzle(Class c, SEL orig, SEL new) {

    Method origMethod = class_getInstanceMethod(c, orig);
    Method newMethod = class_getInstanceMethod(c, new);

    if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
        class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    else
        method_exchangeImplementations(origMethod, newMethod);
}
@end

La bonne chose à propos de cette approche est que vous pouvez l'utiliser même dans les Storyboards en ajoutant un attribut d'exécution défini par l'utilisateur. Malheureusement, UIEdgeInsets n’est pas directement disponible en tant que type, mais comme CGRect consiste également en une structure avec quatre CGFloat, il fonctionne parfaitement en choisissant "Rect" et en remplissant les valeurs. comme ça: {{top, left}, {bottom, right}}.

7
Ortwin Gentz

Eh bien, vous pouvez placer votre UIButton dans un UIView transparent et légèrement plus grand, puis capturer les événements tactiles sur l’instance UIView comme dans le UIButton. De cette façon, vous aurez toujours votre bouton, mais avec une zone tactile plus grande. Vous devrez manuellement traiter les états sélectionnés et mis en surbrillance avec le bouton si l'utilisateur touche la vue au lieu du bouton.

Une autre possibilité consiste à utiliser un UIImage au lieu d'un UIButton.

5
Pablo Santa Cruz

Voici ma Swift 3 Solution (basée sur cet article de blog: http://bdunagan.com/2010/03/01/iphone- tip-large-hit-area-for-uibutton / )

class ExtendedHitAreaButton: UIButton {

    @IBInspectable var hitAreaExtensionSize: CGSize = CGSize(width: -10, height: -10)

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {

        let extendedFrame: CGRect = bounds.insetBy(dx: hitAreaExtensionSize.width, dy: hitAreaExtensionSize.height)

        return extendedFrame.contains(point) ? self : nil
    }
}
5
user2104363

J'utilise la classe suivante dans Swift pour permettre également à une propriété d'Interface Builder d'ajuster la marge:

@IBDesignable
class ALExtendedButton: UIButton {

    @IBInspectable var touchMargin:CGFloat = 20.0

    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        var extendedArea = CGRectInset(self.bounds, -touchMargin, -touchMargin)
        return CGRectContainsPoint(extendedArea, point)
    }
}
5
Antoine

J'ai pu augmenter la zone active du bouton d'information par programmation. Le graphique "i" ne change pas d’échelle et reste centré dans le nouveau cadre du bouton.

La taille du bouton d'information semble être fixée à 18x19 [*] dans Interface Builder. En le connectant à un IBOutlet, j'ai pu modifier sa taille de trame dans le code sans aucun problème.

static void _resizeButton( UIButton *button )
{
    const CGRect oldFrame = infoButton.frame;
    const CGFloat desiredWidth = 44.f;
    const CGFloat margin = 
        ( desiredWidth - CGRectGetWidth( oldFrame ) ) / 2.f;
    infoButton.frame = CGRectInset( oldFrame, -margin, -margin );
}

[*]: Les versions ultérieures d'iOS semblent avoir augmenté la zone active du bouton d'information.

5
otto

Mise en œuvre par substitution de UIButton hérité.

Swift 2.2:

// don't forget that negative values are for outset
_button.hitOffset = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
...
class UICustomButton: UIButton {
    var hitOffset = UIEdgeInsets()

    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        guard hitOffset != UIEdgeInsetsZero && enabled && !hidden else {
            return super.pointInside(point, withEvent: event)
        }
        return UIEdgeInsetsInsetRect(bounds, hitOffset).contains(point)
    }
}
3
dimpiax

Le test de hit personnalisé de Chase implémenté en tant que sous-classe de UIButton. Écrit en Objective-C.

Cela semble fonctionner à la fois pour init et buttonWithType: constructeurs. Pour mes besoins, c'est parfait, mais comme les sous-classes UIButton peuvent être poilues, j'aimerais savoir si quelqu'un a un problème.

CustomeHitAreaButton.h

#import <UIKit/UIKit.h>

@interface CustomHitAreaButton : UIButton

- (void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets;

@end

CustomHitAreaButton.m

#import "CustomHitAreaButton.h"

@interface CustomHitAreaButton()

@property (nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;

@end

@implementation CustomHitAreaButton

- (instancetype)initWithFrame:(CGRect)frame {
    if(self = [super initWithFrame:frame]) {
        self.hitTestEdgeInsets = UIEdgeInsetsZero;
    }
    return self;
}

-(void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets {
    self->_hitTestEdgeInsets = hitTestEdgeInsets;
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    if(UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero) || !self.enabled || self.hidden) {
        return [super pointInside:point withEvent:event];
    }
    CGRect relativeFrame = self.bounds;
    CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.hitTestEdgeInsets);
    return CGRectContainsPoint(hitFrame, point);
}

@end
3
Phillip Martin

WJBackgroundInsetButton.h

#import <UIKit/UIKit.h>

@interface WJBackgroundInsetButton : UIButton {
    UIEdgeInsets backgroundEdgeInsets_;
}

@property (nonatomic) UIEdgeInsets backgroundEdgeInsets;

@end

WJBackgroundInsetButton.m

#import "WJBackgroundInsetButton.h"

@implementation WJBackgroundInsetButton

@synthesize backgroundEdgeInsets = backgroundEdgeInsets_;

-(CGRect) backgroundRectForBounds:(CGRect)bounds {
    CGRect sup = [super backgroundRectForBounds:bounds];
    UIEdgeInsets insets = self.backgroundEdgeInsets;
    CGRect r = UIEdgeInsetsInsetRect(sup, insets);
    return r;
}

@end
2
William Jockusch

Ne jamais remplacer la méthode dans la catégorie. Bouton Sous-classe et remplacement - pointInside:withEvent:. Par exemple, si le côté de votre bouton est inférieur à 44 px (recommandé comme zone de saisie minimale), utilisez ceci:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    return (ABS(point.x - CGRectGetMidX(self.bounds)) <= MAX(CGRectGetMidX(self.bounds), 22)) && (ABS(point.y - CGRectGetMidY(self.bounds)) <= MAX(CGRectGetMidY(self.bounds), 22));
}
2
user500

Basé sur la réponse de giaset ci-dessus (pour laquelle j'ai trouvé la solution la plus élégante), voici la version Swift 3:

import UIKit

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

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 isHidden || !isUserInteractionEnabled || alpha < 0.01 { return nil }

        // increase the hit frame to be at least as big as `minimumHitArea`
        let buttonSize = bounds.size
        let widthToAdd = max(minimumHitArea.width - buttonSize.width, 0)
        let heightToAdd = max(minimumHitArea.height - buttonSize.height, 0)
        let largerFrame = bounds.insetBy(dx: -widthToAdd / 2, dy: -heightToAdd / 2)

        // perform hit test on larger frame
        return (largerFrame.contains(point)) ? self : nil
    }
}
2
Nhon Nguyen

J'ai fait un bibliothèque dans ce but précis.

Vous pouvez choisir d’utiliser une catégorie UIView, sans sous-classement requis :

@interface UIView (KGHitTesting)
- (void)setMinimumHitTestWidth:(CGFloat)width height:(CGFloat)height;
@end

Ou vous pouvez sous-classer votre UIView ou UIButton et définir le minimumHitTestWidth et/ou minimumHitTestHeight. Votre zone de test des boutons sera alors représentée par ces 2 valeurs.

Tout comme les autres solutions, il utilise la méthode - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event. La méthode est appelée lorsque iOS effectue un test de hit. This le blog a une bonne description du fonctionnement des tests de hit iOS.

https://github.com/kgaidis/KGHitTestingViews

@interface KGHitTestingButton : UIButton <KGHitTesting>

@property (nonatomic) CGFloat minimumHitTestHeight; 
@property (nonatomic) CGFloat minimumHitTestWidth;

@end

Vous pouvez également simplement créer des sous-classes et utiliser Interface Builder sans écrire de code: enter image description here

2
kgaidis

La réponse de @ antoine formatée pour Swift 4

class ExtendedHitButton: UIButton
{
    override func point( inside point: CGPoint, with event: UIEvent? ) -> Bool
    {
        let relativeFrame = self.bounds
        let hitTestEdgeInsets = UIEdgeInsetsMake( -44, -44, -44, -44 ) // Apple recommended hit target
        let hitFrame = UIEdgeInsetsInsetRect( relativeFrame, hitTestEdgeInsets )
        return hitFrame.contains( point );
    }
}
1
spfursich

Je viens de faire le port de la @Chase solution dans Swift 2.2

import Foundation
import ObjectiveC

private var hitTestEdgeInsetsKey: UIEdgeInsets = UIEdgeInsetsZero

extension UIButton {
    var hitTestEdgeInsets:UIEdgeInsets {
        get {
            let inset = objc_getAssociatedObject(self, &hitTestEdgeInsetsKey) as? NSValue ?? NSValue(UIEdgeInsets: UIEdgeInsetsZero)
            return inset.UIEdgeInsetsValue()
        }
        set {
            let inset = NSValue(UIEdgeInsets: newValue)
            objc_setAssociatedObject(self, &hitTestEdgeInsetsKey, inset, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    public override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        guard !UIEdgeInsetsEqualToEdgeInsets(hitTestEdgeInsets, UIEdgeInsetsZero) && self.enabled == true && self.hidden == false else {
            return super.pointInside(point, withEvent: event)
        }
        let relativeFrame = self.bounds
        let hitFrame = UIEdgeInsetsInsetRect(relativeFrame, hitTestEdgeInsets)
        return CGRectContainsPoint(hitFrame, point)
    }
}

tu peux utiliser comme ça

button.hitTestEdgeInsets = UIEdgeInsetsMake(-10, -10, -10, -10)

Pour toute autre référence, voir https://stackoverflow.com/a/13067285/1728552

1
Serluca

J'utilise cette astuce pour bouton dans tableviewcell.accessoryView pour agrandir sa surface tactile

#pragma mark - Touches

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch                  = [touches anyObject];
    CGPoint location                = [touch locationInView:self];
    CGRect  accessoryViewTouchRect  = CGRectInset(self.accessoryView.frame, -15, -15);

    if(!CGRectContainsPoint(accessoryViewTouchRect, location))
        [super touchesBegan:touches withEvent:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch                  = [touches anyObject];
    CGPoint location                = [touch locationInView:self];
    CGRect  accessoryViewTouchRect  = CGRectInset(self.accessoryView.frame, -15, -15);

    if(CGRectContainsPoint(accessoryViewTouchRect, location) && [self.accessoryView isKindOfClass:[UIButton class]])
    {
        [(UIButton *)self.accessoryView sendActionsForControlEvents:UIControlEventTouchUpInside];
    }
    else
        [super touchesEnded:touches withEvent:event];
}
1
abuharsky

Cette version de Swift vous permet de définir une taille de hit minimale pour tous les boutons-poussoirs UIB. En outre, elle traite également le cas où les boutons UIB sont cachés, ce que beaucoup de réponses négligent.

extension UIButton {
    public override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        // Ignore if button hidden
        if self.hidden {
            return nil
        }

        // If here, button visible so expand hit area
        let hitSize = CGFloat(56.0)
        let buttonSize = self.frame.size
        let widthToAdd = (hitSize - buttonSize.width > 0) ? hitSize - buttonSize.width : 0
        let heightToAdd = (hitSize - buttonSize.height > 0) ? hitSize - buttonSize.height : 0
        let largerFrame = CGRect(x: 0-(widthToAdd/2), y: 0-(heightToAdd/2), width: buttonSize.width+widthToAdd, height: buttonSize.height+heightToAdd)
        return (CGRectContainsPoint(largerFrame, point)) ? self : nil
    }
}
0
Crashalot

La réponse de @jlajlar ci-dessus semblait bonne et simple, mais ne correspond pas à Xamarin.iOS, je l’ai donc convertie en Xamarin. Si vous cherchez une solution sur un Xamarin iOS, le voici:

public override bool PointInside (CoreGraphics.CGPoint point, UIEvent uievent)
{
    var margin = -10f;
    var area = this.Bounds;
    var expandedArea = area.Inset(margin, margin);
    return expandedArea.Contains(point);
}

Vous pouvez ajouter cette méthode à la classe où vous substituez UIView ou UIImageView. Cela a bien fonctionné :)

0
Has AlTaiar

J'ai suivi la réponse de Chase et cela fonctionne très bien. Un seul problème lorsque vous créez un paramètre trop grand, plus grand que la zone où le bouton est désélectionné (si la zone n'était pas plus grande), le sélecteur de l'événement UIControlEventTouchUpInside n'est pas sélectionné. .

Je pense que la taille est supérieure à 200 dans n’importe quelle direction ou quelque chose comme ça.

0
Unreal Dragon

Je suis si tard pour ce jeu, mais je voulais peser sur une technique simple qui pourrait résoudre vos problèmes. Voici un extrait de code UIButton typique pour moi:

UIImage *arrowImage = [UIImage imageNamed:@"leftarrow"];
arrowButton = [[UIButton alloc] initWithFrame:CGRectMake(15.0, self.frame.size.height-35.0, arrowImage.size.width/2, arrowImage.size.height/2)];
[arrowButton setBackgroundImage:arrowImage forState:UIControlStateNormal];
[arrowButton addTarget:self action:@selector(onTouchUp:) forControlEvents:UIControlEventTouchUpOutside];
[arrowButton addTarget:self action:@selector(onTouchDown:) forControlEvents:UIControlEventTouchDown];
[arrowButton addTarget:self action:@selector(onTap:) forControlEvents:UIControlEventTouchUpInside];
[arrowButton addTarget:self action:@selector(onTouchUp:) forControlEvents:UIControlEventTouchDragExit];
[arrowButton setUserInteractionEnabled:TRUE];
[arrowButton setAdjustsImageWhenHighlighted:NO];
[arrowButton setTag:1];
[self addSubview:arrowButton];

Je charge une image png transparente pour mon bouton et définit l'image d'arrière-plan. Je règle le cadre en fonction de UIImage et redimensionne de 50% pour la rétine. OK, peut-être que vous êtes d'accord avec ce qui précède ou pas, MAIS si vous voulez rendre la zone de frappe PLUS GRAND et vous épargner un mal de tête:

Ce que je fais, ouvrez l’image dans photoshop et augmentez simplement la taille de la toile à 120% et économisez. Effectivement, vous venez d'agrandir l'image avec des pixels transparents.

Juste une approche.

0
chrisallick

Aucune des réponses ne me convient parfaitement, car j'utilise une image d'arrière-plan et un titre sur ce bouton. De plus, le bouton sera redimensionné à mesure que la taille de l'écran change.

Au lieu de cela, j'agrandis la zone de prise en agrandissant la zone transparente au format png.

0
Jakehao

Similaire à celui de Zhanserik, avec une extension variable et mis à jour pour 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)
    }

}
0
MT1asli8ghwoi