UIButton: сделать область попадания больше, чем область попадания по умолчанию


188

У меня есть вопрос, касающийся UIButton и его хитовой области. Я использую кнопку Info Dark в конструкторе интерфейсов, но обнаружил, что область попадания недостаточно велика для пальцев некоторых людей.

Есть ли способ увеличить область нажатия кнопки программно или в Интерфейсном Разработчике без изменения размера графики InfoButton?


Если ничего не работает, просто добавьте UIB-кнопку без изображения, а затем установите оверлей ImageView!
Варун

Это должен быть самый удивительно простой вопрос на всем сайте, на который есть десятки ответов. Я имею в виду, я могу вставить весь ответ здесь: override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { return bounds.insetBy(dx: -20, dy: -20).contains(point) }
Толстяк

Ответы:


137

Поскольку я использую фоновое изображение, ни одно из этих решений не помогло мне. Вот решение, которое делает некоторую забавную магию target-c и предлагает решение с минимальным кодом.

Сначала добавьте категорию, UIButtonкоторая переопределяет тест на попадание, а также добавляет свойство для расширения рамки теста на попадание.

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

Как только этот класс будет добавлен, все, что вам нужно сделать, это установить края вставки вашей кнопки. Обратите внимание, что я решил добавить вставки, поэтому, если вы хотите увеличить область попадания, вы должны использовать отрицательные числа.

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

Примечание: не забудьте импортировать категорию ( #import "UIButton+Extensions.h") в ваших классах.


26
Переопределение метода в категории - плохая идея. Что если другая категория также попытается переопределить pointInside: withEvent :? И вызов [super pointInside: withEvent] вызывает не версию вызова UIButton (если она есть), а версию родителя UIButton.
Хонус

@honus Вы правы, и Apple не рекомендует делать это. При этом, пока этот код не находится в разделяемой библиотеке и только локально для вашего приложения, у вас все должно быть в порядке.
Погоня

Привет Чейз, есть ли недостаток использования подкласса вместо этого?
Сид

3
Вы выполняете слишком много ненужной работы, вы можете сделать две простые строки кода: [[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"arrow.png"] forState:UIControlStateNormal];и просто установить нужную ширину и высоту: `backButton.frame = CGRectMake (5, 28, 45, 30);`
Resty

7
@Resty, он использует фоновое изображение, что означает, что ваш подход не будет работать.
Мэттссон

77

Просто установите значения вставки края изображения в конструкторе интерфейса.


1
Я думал, что это не будет работать для меня, потому что фоновые изображения не реагируют на вставки, но я переключился на настройку свойства изображения, а затем установил отрицательную левую границу для моего заголовка, ширину изображения, и он снова оказался сверху мое изображение.
Дейв Росс

12
Это также может быть сделано в коде. Например,button.imageEdgeInsets = UIEdgeInsetsMake(-10, -10, -10, -10);
bengoesboom

Ответ Дэйва Росса великолепен. Для тех, кто не может изобразить его, -image поднимает ваш заголовок вправо от себя (очевидно), поэтому вы можете щелкнуть по маленькому кликеру в IB, чтобы переместить его назад (или в коде, как было указано). Единственный минус, который я вижу, это то, что я не могу использовать растягиваемые изображения. Маленькая цена, чтобы заплатить ИМО
Поль Бруно

7
как это сделать без растяжения изображения?
zonabi

Установите режим содержимого на что-то вроде Center, а не Scaling.
Сэмюэль Гудвин

63

Вот элегантное решение с использованием расширений в Swift. Это дает всем UIB-кнопкам область попадания, по крайней мере, 44x44 точки, в соответствии с Руководством Apple по человеческому интерфейсу ( https://developer.apple.com/ios/human-interface-guidelines/visual-design/layout/ )

Свифт 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
    }
}

Свифт 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
    }
}

3
Вам нужно проверить, скрыта ли кнопка в данный момент. Если так, верните ноль.
Джош Гафни

Спасибо, это выглядит великолепно. Есть ли потенциальные недостатки, о которых нужно знать?
Crashalot

Мне нравится это решение, не требующее от меня установки дополнительных свойств для всех кнопок. Спасибо
xtrinch

1
Если руководящие принципы Apple рекомендуют эти размеры для области попадания, зачем заставлять нас исправлять их неисполнение? Обращается ли это в последующих SDK?
NoodleOfDeath

2
Слава богу, у нас есть Stack Overflow и его участники. Потому что мы, безусловно, не можем полагаться на Apple в получении полезной, своевременной и точной информации о том, как решить наиболее распространенные, основные проблемы.
Womble

49

Вы также можете UIButtonсоздать подкласс или пользовательский UIViewи переопределить point(inside:with:)что-то вроде:

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

1
спасибо действительно классное решение без каких-либо дополнительных категорий. Также я просто интегрирую его с моими кнопками, которые есть в XIB, и он работает хорошо. отличный ответ
Матросов Александр

Это отличное решение, которое можно распространить на все остальные элементы управления! Я нашел полезным для подкласса UISearchBar, например. Метод pointInside есть на каждом UIView начиная с iOS 2.0. Кстати - Swift: func pointInside (_point: CGPoint, с событием withEvent: UIEvent!) -> Bool.
Томми С.

Спасибо за это! Как уже говорилось, это может быть действительно полезно с другими элементами управления!
Янчи

Это круто!! Спасибо, вы сэкономили мне много времени! :-)
Войта

35

Вот Чейз UIButton + Расширения в 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)
    }
}

Чтобы использовать его, вы можете:

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

1
На самом деле, мы должны расширить UIControl, а не UIButton.
Валентин Шергин

2
И переменная pTouchAreaEdgeInsetsдолжна быть Int, а не UIEdgeInsets. (На самом деле, это может быть любой тип, но он Intявляется наиболее общим и простым типом, поэтому он часто встречается в такой конструкции). (Поэтому я люблю этот подход в целом. Спасибо, dcRay!)
Валентин Шергин

1
Я пробовал titleEdgeInsets, imageEdgeInsets, contentEdgeInset все не работает для меня, это расширение работает хорошо. Спасибо за публикацию !! Прекрасная работа !!
Йогеш Патель

19

Я рекомендую разместить UIButton с типом Custom по центру над вашей информационной кнопкой. Измените размер пользовательской кнопки до размера, который вы хотите, чтобы область попадания была. Оттуда у вас есть два варианта:

  1. Установите флажок «Показать касание при выделении» пользовательской кнопки. Белое свечение появится над информационной кнопкой, но в большинстве случаев это будет скрыто пальцем пользователя, и все, что они увидят, это свечение вокруг снаружи.

  2. Установите IBOutlet для информационной кнопки и два IBActions для пользовательской кнопки, одну для «Touch Down» и одну для «Touch Up Inside». Затем в XCode сделайте событие приземления, установите выделенное свойство кнопки информации в YES, и событие touchupinside установите выделенное свойство в НЕТ.


19

Не устанавливайте backgroundImageсвойство с вашим изображением, установите imageViewсвойство. Также убедитесь, что вы imageView.contentModeустановили на UIViewContentModeCenter.


1
В частности, установите для свойства contentMode кнопки значение UIViewContentModeCenter. Затем вы можете сделать рамку кнопки больше, чем изображение. Работает для меня!
arlomedia

К вашему сведению, UIButton не уважает UIViewContentMode: stackoverflow.com/a/4503920/361247
Энрико Сусатио

@EnricoSusatyo извините, я прояснил, о каких API я говорил. imageView.contentModeМожет быть установлен UIContentModeCenter.
Маурицио

Это работает отлично, спасибо за совет! [[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"image.png"] forState:UIControlStateNormal];
Resty

@Resty Вышеупомянутый комментарий не работает для меня: / Размер изображения увеличивается до большего установленного размера кадра.
Анил

14

Мое решение на 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)
    }
}

12

В представленных ответах нет ничего плохого; однако я хотел бы расширить ответ jlarjlar, поскольку он обладает удивительным потенциалом, который может добавить ценность к той же проблеме с другими элементами управления (например, SearchBar). Это связано с тем, что поскольку pointInside подключен к UIView, можно создать подкласс любого элемента управления. для улучшения области касания. Этот ответ также показывает полный пример того, как реализовать полное решение.

Создайте новый подкласс для вашей кнопки (или любого элемента управления)

#import <UIKit/UIKit.h>

@interface MNGButton : UIButton

@end

Затем переопределите метод pointInside в вашей реализации подкласса

@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

В файле раскадровки / xib выберите нужный элемент управления, откройте инспектор идентификации и введите имя своего пользовательского класса.

mngbutton

В вашем классе UIViewController для сцены, содержащей кнопку, измените тип класса для кнопки на имя вашего подкласса.

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

Свяжите свою раскадровку / кнопку xib со свойством IBOutlet, и ваша область касания будет расширена, чтобы соответствовать области, определенной в подклассе.

В дополнение к переопределению метода pointInside вместе с методами CGRectInset и CGRectContainsPoint , необходимо потратить время на изучение CGGeometry для расширения прямоугольной области касания любого подкласса UIView. Вы также можете найти несколько полезных советов по применению CGGeometry на NSHipster. .

Например, можно сделать область касания нерегулярной, используя методы, упомянутые выше, или просто сделать так, чтобы область касания шириной была в два раза больше горизонтальной области касания:

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

Примечание: замена любого элемента управления класса UI должна давать аналогичные результаты при расширении сенсорной области для различных элементов управления (или любого подкласса UIView, такого как UIImageView и т. Д.).


10

Это работает для меня:

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

3
Это работает, но будьте осторожны, если вам нужно установить изображение и заголовок для кнопки. В этом случае вам нужно будет использовать setBackgoundImage, который будет масштабировать ваше изображение до нового кадра кнопки.
Михай Дамиан

7

Не меняйте поведение 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

Если ваша кнопка 40х40, этот метод не будет вызываться, если отрицательные числа больше - как кто-то упоминал. В противном случае это работает.
Джеймс О'Брайен

Это не работает для меня. hitFrame выглядит правильно, но pointInside: никогда не вызывается, когда точка находится за пределами self.bounds. if (CGRectContainsPoint (hitFrame, point) &&! CGRectContainsPoint (self.bounds, point)) {NSLog (@ "Success!"); // Никогда не звонил}
Арсений

7

Я использую более общий подход к Swizzling -[UIView pointInside:withEvent:]. Это позволяет мне модифицировать поведение тестирования на попадание на любом UIView, а не толькоUIButton .

Часто внутри контейнера отображается кнопка, которая также ограничивает проверку попадания. Например, когда кнопка находится в верхней части представления контейнера и вы хотите расширить цель касания вверх, вам также необходимо расширить цель касания представления контейнера.

@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

Хорошая вещь в этом подходе заключается в том, что вы можете использовать его даже в раскадровках, добавив определяемый пользователем атрибут времени выполнения. К сожалению, UIEdgeInsetsне доступны непосредственно в качестве типа там , но так CGRectже состоит из структуры с четырьмя CGFloatработает безупречно, выбрав «Rect» и заполнение значений , как это: {{top, left}, {bottom, right}}.


6

Я использую следующий класс в Swift, чтобы также включить свойство Interface Builder для настройки поля:

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

как вручную изменить эту точку внутри? Если я создал UIButton со старым полем, но теперь я хочу изменить его?
TomSawyer

5

Ну, вы можете поместить вашу UIButton внутри прозрачного и немного большего UIView, а затем перехватить сенсорные события в экземпляре UIView, как в UIButton. Таким образом, у вас все еще будет кнопка, но с большей сенсорной областью. Вам придется вручную работать с выбранными и выделенными состояниями с помощью кнопки, если пользователь нажимает на вид вместо кнопки.

Другая возможность предполагает использование UIImage вместо UIButton.


5

Я смог увеличить область попадания кнопки информации программно. Рисунок «i» не меняет масштаб и остается в центре новой рамки кнопки.

Кажется, размер кнопки информации в Интерфейсном Разработчике установлен на 18x19 [*]. Подключив его к IBOutlet, я смог без проблем изменить размер кадра в коде.

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

[*]: Более поздние версии iOS, похоже, увеличили область нажатия кнопки информации.


5

Это мое решение Swift 3 (на основе этого поста в блоге: http://bdunagan.com/2010/03/01/iphone-tip-larger-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
    }
}

3

Пользовательский тест попадания Чейза реализован как подкласс UIButton. Написано в Objective-C.

Кажется, работает как для, так initи для buttonWithType:конструкторов. Для моих нужд это идеально, но поскольку подклассы UIButtonмогут быть волосатыми, мне было бы интересно узнать, есть ли у кого-то в этом ошибка.

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

Реализация через переопределение наследуется UIButton.

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

2

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

Никогда не переопределять метод в категории. Кнопка подкласса и переопределение - pointInside:withEvent:. Например, если сторона вашей кнопки меньше 44 пикселей (что рекомендуется в качестве минимальной площади нажатия), используйте это:

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

Я сделал библиотеку для этой цели.

Вы можете использовать UIViewкатегорию без подклассов :

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

Или вы можете создать подкласс UIView или UIButton и установить minimumHitTestWidthи / илиminimumHitTestHeight . Ваша область проверки нажатия кнопки будет представлена ​​этими двумя значениями.

Как и другие решения, он использует - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)eventметод. Метод вызывается, когда iOS выполняет тестирование нажатия. В этом посте есть хорошее описание того, как работает iOS-тестирование.

https://github.com/kgaidis/KGHitTestingViews

@interface KGHitTestingButton : UIButton <KGHitTesting>

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

@end

Вы также можете просто создать подкласс и использовать Interface Builder без написания какого-либо кода: введите описание изображения здесь


1
Я закончил тем, что установил это только ради скорости. Добавление IBInspectable было отличной идеей, спасибо, что соединили это.
user3344977

2

Основываясь на ответе giaset выше (который я нашел самое элегантное решение), вот версия 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

Это просто так:

class ChubbyButton: UIButton {
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        return bounds.insetBy(dx: -20, dy: -20).contains(point)
    }
}

Вот и все.


1

Я использую этот трюк для кнопки внутри tableviewcell.accessoryView, чтобы увеличить ее область касания

#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

Я только что сделал порт решения @Chase в 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)
    }
}

вы можете использовать как это

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

Для любой другой ссылки см. Https://stackoverflow.com/a/13067285/1728552


1

@ Антуан ответ отформатирован для 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 );
    }
}

0

Я следил за ответом Чейза, и он прекрасно работает, единственная проблема, когда вы создаете слишком большую область, большую, чем зона, в которой кнопка отменяется (если зона не была больше), она не вызывает селектор для события UIControlEventTouchUpInside ,

Я думаю, что размер более 200 в любом направлении или что-то в этом роде.


0

Я так опаздываю к этой игре, но хотел взвесить простую технику, которая может решить ваши проблемы. Вот типичный программный фрагмент UIButton для меня:

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];

Я загружаю прозрачное изображение PNG для своей кнопки и устанавливаю фоновое изображение. Я устанавливаю кадр на основе UIImage и масштабирую на 50% для сетчатки. Хорошо, может быть, вы согласны с вышеизложенным или нет, НО, если вы хотите сделать область удара БОЛЬШЕ и избавить себя от головной боли:

Что я делаю, открываю изображение в фотошопе и просто увеличиваю размер холста до 120% и сохраняю. Фактически вы только что увеличили изображение с помощью прозрачных пикселей.

Всего один подход.


0

Ответ @jlajlar выше казался хорошим и прямым, но не соответствует Xamarin.iOS, поэтому я перевел его на Xamarin. Если вы ищете решение для iOS Xamarin, вот оно:

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

Вы можете добавить этот метод в класс, где вы переопределяете UIView или UIImageView. Это сработало


0

Ни один из ответов не работает идеально для меня, потому что я использую фоновое изображение и заголовок на этой кнопке. Кроме того, размер кнопки будет изменяться при изменении размера экрана.

Вместо этого я увеличиваю область крана, увеличивая прозрачную область png.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.