Domanda UIButton: rende l'area colpita più grande dell'area di attacco predefinita


Ho una domanda riguardante UIButton e la sua area di successo. Sto usando il pulsante Info Dark nel builder dell'interfaccia, ma sto scoprendo che l'area colpita non è abbastanza grande per le dita di alcune persone.

C'è un modo per aumentare l'area attiva di un pulsante a livello di codice o in Interface Builder senza modificare la dimensione del grafico di InfoButton?


176
2018-04-30 18:54


origine


risposte:


Dal momento che sto usando un'immagine di sfondo, nessuna di queste soluzioni ha funzionato bene per me. Ecco una soluzione che fa un po 'di divertimento magico-obiettivo e offre una soluzione in meno con un codice minimo.

Innanzitutto, aggiungi una categoria a UIButton che sovrascrive il test di successo e aggiunge anche una proprietà per espandere il frame di hit 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

Una volta aggiunta questa classe, tutto ciò che devi fare è impostare i margini del tuo pulsante. Nota che ho scelto di aggiungere gli inset, quindi se vuoi rendere più grande l'area colpita, devi usare numeri negativi.

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

Nota: ricordarsi di importare la categoria (#import "UIButton+Extensions.h") nelle tue classi.


134
2017-10-25 11:08



Basta impostare i valori di inserimento del bordo dell'immagine nel generatore di interfacce.


75
2018-04-22 13:44



Ecco una soluzione elegante che utilizza Extensions in Swift. Dà a tutti gli UIButtons un'area di successo di almeno 44x44 punti, secondo le Linee guida di interfaccia umana di 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
    }
}

58
2017-12-29 04:58



Potresti anche creare una sottoclasse UIButton o una consuetudine UIView e override point(inside:with:) con qualcosa come:

Swift 3

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

44
2017-12-20 18:02



Ecco le estensioni UIButton + di Chase in 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)
    }
}

Per usarlo, puoi:

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

30
2017-08-14 04:12



Non impostare il backgroundImage proprietà con la tua immagine, imposta il imageView proprietà. Inoltre, assicurati di averlo imageView.contentMode fissati a UIViewContentModeCenter.


19
2017-11-06 04:45



Consiglio di posizionare un UIButton con il tipo Personalizzato centrato sul pulsante delle informazioni. Ridimensiona il pulsante personalizzato alla dimensione desiderata per l'area attiva. Da lì hai due opzioni:

  1. Seleziona l'opzione "Mostra tocco su evidenziazione" del pulsante personalizzato. Il bagliore bianco apparirà sul pulsante Info, ma nella maggior parte dei casi il dito dell'utente lo coprirà e tutto ciò che vedranno sarà il bagliore intorno all'esterno.

  2. Configura un IBOutlet per il pulsante info e due IBAction per il pulsante personalizzato one per 'Touch Down' e uno per 'Touch Up Inside'. Quindi, in Xcode, l'evento touchdown imposta la proprietà evidenziata del pulsante info su SÌ e l'evento touchupinside imposta la proprietà evidenziata su NO.


18
2018-05-04 04:24



Non c'è niente di sbagliato nelle risposte presentate; tuttavia volevo estendere la risposta di jlarjlar poiché contiene un potenziale incredibile che può aggiungere valore allo stesso problema con altri controlli (ad esempio SearchBar). Questo perché poiché pointInside è collegato a un UIView, si è in grado di sottoclasse qualsiasi controllo per migliorare l'area tattile. Questa risposta mostra anche un esempio completo di come implementare la soluzione completa.

Crea una nuova sottoclasse per il tuo pulsante (o qualsiasi controllo)

#import <UIKit/UIKit.h>

@interface MNGButton : UIButton

@end

Successivamente, ignora il metodo pointInside nell'implementazione della sottoclasse

@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

Sul tuo storyboard / file xib seleziona il controllo in questione e apri l'ispettore identità e digita il nome della tua classe personalizzata.

mngbutton

Nella classe UIViewController per scene contenenti il ​​pulsante, modifica il tipo di classe per il pulsante con il nome della sottoclasse.

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

Collega il tuo pulsante storyboard / xib alla proprietà IBOutlet e la tua area touch verrà espansa per adattarsi all'area definita nella sottoclasse.

Oltre a ignorare il metodo pointInside insieme con il CGRectInset e CGRectContainsPoint metodi, si dovrebbe prendere tempo per esaminare il CGGeometry per estendere l'area tattile rettangolare di qualsiasi sottoclasse UIView. È inoltre possibile trovare alcuni buoni consigli su casi d'uso CGGeometry a NSHipster.

Ad esempio, si potrebbe rendere irregolare l'area tattile usando i metodi sopra menzionati o semplicemente scegliere di ingrandire l'area di tocco della larghezza due volte più grande dell'area di tocco orizzontale:

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

NB: la sostituzione di qualsiasi controllo della classe UI dovrebbe produrre risultati simili estendendo l'area tattile a diversi controlli (o qualsiasi sottoclasse UIView, come UIImageView, ecc.).


12
2017-09-08 16:40



Questo funziona per me:

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
2018-05-12 08:24