UIButton: увеличение области попадания по умолчанию - PullRequest
183 голосов
/ 30 апреля 2009

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

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

Ответы [ 34 ]

138 голосов
/ 25 октября 2012

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

75 голосов
/ 22 апреля 2011

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

61 голосов
/ 29 декабря 2014

Вот элегантное решение с использованием расширений в Swift. Это дает всем UIB-кнопкам область попадания, по крайней мере, 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
    }
}
46 голосов
/ 20 декабря 2012

Вы также можете создать подкласс 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);
}
32 голосов
/ 14 августа 2015

Вот Чейз 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)
19 голосов
/ 06 ноября 2010

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

18 голосов
/ 04 мая 2009

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

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

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

12 голосов
/ 08 сентября 2014

Нет ничего плохого в представленных ответах; однако я хотел бы расширить ответ 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 и т. Д.).

12 голосов
/ 01 августа 2017

Мое решение на 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)
    }
}
10 голосов
/ 12 мая 2010

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

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