UIButton: создание области попадания, превышающей область хита по умолчанию

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

Есть ли способ увеличить область попадания кнопки либо программно, либо в Interface Builder без изменения размера графического объекта InfoButton?

Ответ 1

Поскольку я использую фоновое изображение, ни одно из этих решений не помогло мне. Вот решение, которое делает некоторую забавную задачу 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") в свои классы.

Ответ 2

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

Ответ 3

Здесь представлено элегантное решение с использованием расширений в Swift. Это дает всем UIButton зону хита не менее 44x44 точек, в соответствии с Руководством по человеческому интерфейсу 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
    }
}

Ответ 4

Вы также можете подклассом 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);
}

Ответ 5

Здесь Chase UIButton + Extensions в 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)

Ответ 6

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

Ответ 7

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

  • Отметьте опцию "Показать прикосновение к подсветке" на пользовательской кнопке. Белое свечение появится над информационной кнопкой, но в большинстве случаев пальчик пользователя будет охватывать это, и все, что они увидят, - это свечение вокруг снаружи.

  • Настройте IBOutlet для кнопки информации и двух IBActions для пользовательской кнопки one для "Touch Down" и для "Touch Up Inside". Затем в приложении Xcode событие touchdown задало для выделенного свойства кнопки информации значение YES, а событие touchupinside установило для выделенного свойства значение NO.

Ответ 8

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

Ответ 9

Нет ничего плохого в представленных ответах; однако я хотел расширить jlarjlar answer, поскольку он обладает удивительным потенциалом, который может добавить ценность к той же проблеме с другими элементами управления (например, 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)];

Ответ 11

Я использую более общий подход, используя 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

Приятная вещь в этом подходе заключается в том, что вы можете использовать это даже в Storiesboard, добавив атрибут Runtime User Defined Runtime. К сожалению, UIEdgeInsets напрямую не доступен в качестве типа, но поскольку CGRect также состоит из структуры с четырьмя CGFloat, она работает безупречно, выбирая "Rect" и заполняя такие значения: {{top, left}, {bottom, right}}.

Ответ 12

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

Ответ 13

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

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

Ответ 14

Я смог программно увеличить область хитов информационной кнопки. График "i" не изменяет масштаб и остается в центре новой рамки кнопок.

Размер кнопки информации, по-видимому, фиксирован в 18x19 [*] в Interface Builder. Подключив его к 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, похоже, увеличили область хитов информационной кнопки.

Ответ 15

Я использую следующий класс в 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)
    }
}

Ответ 16

Это мое решение Swift 3 (на основе этого blogpost: 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
    }
}

Ответ 17

Чейзский пользовательский хит-тест реализован как подкласс 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

Ответ 18

Реализация через переопределение унаследованного 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)
    }
}

Ответ 19

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

Ответ 20

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

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

Ответ 21

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

Вы можете выбрать категорию 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 hit-testing.

https://github.com/kgaidis/KGHitTestingViews

@interface KGHitTestingButton : UIButton <KGHitTesting>

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

@end

Вы также можете просто подклассы и использовать построитель интерфейсов без написания кода: enter image description here

Ответ 22

Основание для ответа на giaset выше (которое я нашел наиболее изящное решение), вот версия быстрого 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
    }
}

Ответ 23

Я использую этот трюк для кнопки внутри 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];
}

Ответ 24

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

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

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

Для любой другой ссылки см. fooobar.com/questions/38300/...

Ответ 25

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

Ответ 26

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

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

Ответ 27

Я так опаздываю до этой игры, но хочу взвесить на простой технике, которая может решить ваши проблемы. Вот типичный программный фрагмент 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% для сетчатки. Хорошо, может быть, вы согласны с вышеизложенным или нет, НО если вы хотите сделать область поражения BIGGER и сохранить себе головную боль:

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

Только один подход.

Ответ 28

@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. Это хорошо работало:)

Ответ 29

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

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

Ответ 30

Эта версия Swift позволяет определить минимальный размер ударов для всех UIButtons. Реально, он также обрабатывает случай, когда UIButtons скрыты, что многие ответы игнорируются.

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