UIButton: зробить область звернення більшою, ніж область звернення за замовчуванням


188

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

Чи можна збільшити область натискання кнопки програмно чи в Інтерфейс Builder, не змінюючи розмір графіки InfoButton?


Якщо нічого не працює, просто додайте UIButton без зображення, а потім покладіть накладку ImageView!
Варун

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

Відповіді:


137

Оскільки я використовую фонове зображення, жодне з цих рішень не спрацювало для мене. Ось рішення, яке виконує забавну магію об'єктивного c і пропонує краплі рішення з мінімальним кодом.

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

UIButton + Розширення.h

@interface UIButton (Extensions)

@property(nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;

@end

UIButton + розширення.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

@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, він використовує фонове зображення, тобто ваш підхід не працюватиме.
mattsson

77

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


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

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

Відповідь Дейва Росса чудова. Для тих, хто не може його зобразити, -image стикає ваш заголовок праворуч від себе (мабуть), так що ви можете натиснути маленький клік в IB, щоб перемістити його назад (або в коді, як було зазначено). Єдиний мінус, який я бачу, це те, що я не можу використовувати розтяжні зображення. Невелика ціна, яку потрібно заплатити IMO
Пол Бруно

7
як це зробити, не розтягуючи зображення?
zonabi

Встановіть режим вмісту на зразок Центр, а не що-небудь масштабування.
Семюел Гудвін

63

Ось елегантне рішення за допомогою розширень у Swift. Він дає всім UIButtons зону звернення принаймні 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 на корисну, своєчасну та точну інформацію про те, як виправити їх найпоширеніші, основні проблеми.
Уомбл

49

Ви також можете підкласи UIButtonабо користувацькі UIViewта змінити point(inside:with:)щось на зразок:

Швидкий 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)
}

Ціль-С

- (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. BTW - Swift: func pointInside (_ точка: CGPoint, withEvent подія: UIEvent!) -> Bool.
Tommie C.

Дякую за це! Як було сказано, це може бути дуже корисно з іншими елементами управління
Янчі

Це чудово!! Дякую, ти врятував мені багато часу! :-)
Войта

35

Ось UIButton + розширення Chase у 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 типу "Спеціальний центр" над інформаційною кнопкою. Змініть розмір спеціальної кнопки до того розміру, який ви хочете, щоб була область звернення. Звідти у вас є два варіанти:

  1. Установіть прапорець "Показати торкання підсвітки" спеціальної кнопки. Біле сяйво з’явиться над інформаційною кнопкою, але в більшості випадків палець користувачів прикриє це, і все, що вони побачать, - це світіння зовні.

  2. Встановіть IBOutlet для інформаційної кнопки та дві IBActions для спеціальної кнопки, одну для "Touch Down" та одну для "Touch Up Inside". Тоді в Xcode зробіть подію торкання, встановіть виділену властивість інформаційної кнопки YES, а подія touchupinside встановить виділене властивість на NO.


19

Не встановлюйте backgroundImageвластивість за допомогою зображення, встановіть його imageView. Також переконайтеся, що ви imageView.contentModeвстановили UIViewContentModeCenter.


1
Більш конкретно, встановіть для властивості contentMode кнопки значення UIViewContentModeCenter. Тоді ви можете зробити рамку кнопки більшою, ніж на зображенні. Для мене працює!
arlomedia

FYI, UIButton не поважає UIViewContentMode: stackoverflow.com/a/4503920/361247
Енріко Susatyo

@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

Якщо ваша кнопка розміром 40x40, цей спосіб не буде викликатися, якщо від'ємні числа більше - як хтось згаданий. Інакше це працює.
Джеймс О'Браєн

Це не працює для мене. Показ hitFrame обчислюється належним чином, але pointInside: ніколи не викликається, коли точка знаходиться поза межами self.bounds. if (CGRectContainsPoint (hitFrame, point) &&! CGRectContainsPoint (self.bounds, point)) {NSLog (@ "Успіх!"); // Ніколи не закликав}
арсеній

7

Я використовую більш узагальнений підхід за допомогою шипіння -[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)
    }
}

як вручну змінити цю точкуInside? Якщо я створив UIButton зі старим запасом, але тепер я хочу його змінити?
TomSawyer

5

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

Інша можливість передбачає використання UIImage замість UIButton.


5

Мені вдалося програмно збільшити область звернення кнопок інформації. Графіка "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, схоже, збільшили область звернення кнопок інформації.


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.

Швидкий 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 було чудовою ідеєю спасибі за те, що це було разом.
користувач3344977

2

Виходячи з відповіді 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
    }
}

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 у швидкому 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

@ відповідь 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 );
    }
}

0

Я стежив за реакцією Чейза, і він працює чудово, одна єдина проблема, коли ви створюєте arrea занадто великий, більший за зону, де кнопка не відбирається (якщо зона була не більшою), вона не викликає селектор для події 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. Якщо ви шукаєте рішення для iam 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.