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?
É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.
Il suffit de définir les valeurs d'incrustation de l'image dans le générateur d'interface.
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
}
}
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);
}
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)
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
.
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:
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.
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.
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)
}
}
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.
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.).
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)];
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
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}}
.
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.
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
}
}
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)
}
}
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.
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)
}
}
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.
#import <UIKit/UIKit.h>
@interface CustomHitAreaButton : UIButton
- (void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets;
@end
#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
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
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));
}
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
}
}
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:
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 );
}
}
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
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];
}
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
}
}
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é :)
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.
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.
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.
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)
}
}