UIButton : 적중 영역을 기본 적중 영역보다 크게 만들기


188

UIButton과 히트 영역을 다루는 질문이 있습니다. 인터페이스 빌더에서 Info Dark 버튼을 사용하고 있지만 히트 영역이 일부 사람들의 손가락에 비해 충분히 크지 않다는 것을 알았습니다.

InfoButton 그래픽의 크기를 변경하지 않고 프로그래밍 방식으로 또는 Interface Builder에서 버튼의 적중 영역을 늘리는 방법이 있습니까?


아무것도 작동하지 않으면 이미지없이 UIButton을 추가 한 다음 ImageView 오버레이를 넣으십시오!
Varun

이것은 전체 사이트에서 가장 놀랍도록 간단한 질문이어야하는데 수십 개의 답변이 있습니다. 여기에 전체 답변을 붙여 넣을 수 있습니다. override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { return bounds.insetBy(dx: -20, dy: -20).contains(point) }
Fattie

답변:


137

배경 이미지를 사용하고 있기 때문에 이러한 솔루션 중 어느 것도 나를 위해 잘 작동하지 않았습니다. 다음은 재미있는 objective-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

@honus 당신이 옳고 Apple은 이것을 권장하지 않습니다. 즉,이 코드가 공유 라이브러리에 있지 않고 응용 프로그램에 국한되어 있으면 괜찮을 것입니다.
체이스

안녕 체이스, 대신 서브 클래스를 사용하는 것의 단점이 있습니까?
Sid

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
배경 이미지가 삽입에 응답하지 않기 때문에 이것이 작동하지 않는다고 생각했지만 이미지 속성 설정으로 변경 한 다음 제목의 왼쪽 가장자리 삽입을 제목의 너비로 설정하고 이미지의 상단으로 돌아갔습니다. 내 이미지.
Dave Ross

12
이것은 코드에서도 가능합니다. 예 :button.imageEdgeInsets = UIEdgeInsetsMake(-10, -10, -10, -10);
bengoesboom

데이브 로스의 대답은 훌륭합니다. 그림을 볼 수없는 사람들을 위해 -image는 제목을 오른쪽으로 자르기 때문에 (명백하게) IB에서 작은 클릭기를 클릭하여 뒤로 이동하거나 명시된대로 코드에서 이동할 수 있습니다. 내가 볼 수있는 유일한 단점은 확장 가능한 이미지를 사용할 수 없다는 것입니다. 작은 가격은 IMO 지불
폴 Bruneau

7
이미지를 늘리지 않고이 작업을 수행하는 방법은 무엇입니까?
zonabi

컨텐츠 모드를 스케일링이 아닌 센터와 같은 것으로 설정하십시오.
Samuel Goodwin

63

다음은 Swift에서 확장을 사용하는 우아한 솔루션입니다. Apple의 휴먼 인터페이스 지침 ( https://developer.apple.com/ios/human-interface-guidelines/visual-design/layout/ )에 따라 모든 UIButton에 최소 44x44 포인트의 히트 영역을 제공합니다.

스위프트 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
버튼이 현재 숨겨져 있는지 테스트해야합니다. 그렇다면 nil을 반환하십시오.
Josh Gafni

고마워요. 알고 있어야 할 잠재적 단점이 있습니까?
Crashalot

나는이 솔루션을 좋아하며 모든 버튼에 추가 속성을 설정할 필요가 없습니다. 감사합니다
xtrinch

1
Apple 지침에서 적중 영역에 대해 이러한 차원을 권장하는 경우 구현 실패를 수정해야하는 이유는 무엇입니까? 이것은 이후 SDK에서 해결됩니까?
NoodleOfDeath

2
스택 오버플로와 그 기여자들에게 감사합니다. 가장 일반적인 기본 문제를 해결하는 방법에 대한 유용하고시기 적절한 정확한 정보를 Apple에 의존 할 수는 없기 때문입니다.
Womble

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

목표 -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에있는 버튼과 간단하게 통합하고 잘 작동합니다. 큰 답변
Matrosov Alexander

이것은 다른 모든 컨트롤로 확장 할 수있는 훌륭한 솔루션입니다! 예를 들어 UISearchBar를 서브 클래스 화하는 것이 유용하다는 것을 알았습니다. pointInside 메소드는 iOS 2.0부터 모든 UIView에 있습니다. BTW-스위프트 : func pointInside (_ point : CGPoint, withEvent 이벤트 : UIEvent!)-> Bool.
Tommie C.

감사합니다! 다른 말했듯이, 이것은 다른 컨트롤에서 실제로 유용 할 수 있습니다!
Yanchi

대단해 !! 고마워요, 시간을 많이 절약했습니다! :-)
Vojta

35

다음은 Swift 3.0의 Chase UIButton + Extensions입니다.


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.
Valentin Shergin

2
변수 pTouchAreaEdgeInsetsInt아니 어야합니다 UIEdgeInsets. (실제로 모든 유형이 될 수 있지만 Int가장 일반적이고 간단한 유형이므로 이러한 구성에서 일반적입니다). (따라서 저는이 접근법을 일반적으로 좋아합니다. dcRay 감사합니다!)
Valentin Shergin

1
titleEdgeInsets, imageEdgeInsets, contentEdgeInset이 모두 작동하지 않습니다.이 확장 프로그램이 제대로 작동합니다. 이것을 게시 해 주셔서 감사합니다 !! 잘 했어 !!
Yogesh Patel

19

정보 버튼 위에 맞춤 유형의 UIButton을 배치하는 것이 좋습니다. 맞춤 검색 버튼의 크기를 적중 영역의 크기로 조정하십시오. 거기에는 두 가지 옵션이 있습니다.

  1. 맞춤 검색 버튼의 '강조 표시시 터치'옵션을 확인하십시오. 정보 버튼 위에 흰색 광선이 표시되지만 대부분의 경우 사용자 손가락으로이 부분을 덮을 수 있으며 외부에서 광선 만 보입니다.

  2. 정보 버튼에 IBOutlet을 설정하고 커스텀 버튼에 대해 'Touch Down'에 대한 하나와 'Touch Up Inside'에 대한 두 개의 IBAction을 설정하십시오. 그런 다음 Xcode에서 터치 다운 이벤트가 정보 버튼의 강조 표시된 속성을 YES로 설정하고 touchupinside 이벤트가 강조 표시된 속성을 NO로 설정하십시오.


19

backgroundImage이미지로 속성을 설정하지 말고 속성을 설정하십시오 imageView. 또한, 메이크업은 확실히 당신이 한 imageView.contentMode설정 UIViewContentModeCenter.


1
보다 구체적으로, 버튼의 contentMode 속성을 UIViewContentModeCenter로 설정하십시오. 그런 다음 버튼의 프레임을 이미지보다 크게 만들 수 있습니다. 나를 위해 작동합니다!
arlomedia

참고로, UIButton은 UIViewContentMode를 존중하지 않습니다 : stackoverflow.com/a/4503920/361247
Enrico Susatyo

@EnricoSusatyo 죄송합니다, 내가 말하고있는 API가 무엇인지 명확히했습니다. 가 imageView.contentMode로 설정할 수 있습니다 UIContentModeCenter.
Maurizio

조언을 주셔서 감사합니다. [[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"image.png"] forState:UIControlStateNormal];
Resty

@Resty 위에서 언급 한 주석이 작동하지 않습니다 : / 이미지가 설정된 프레임 크기보다 크게 조정됩니다.
Anil

14

스위프트 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

제시된 답변에는 아무런 문제가 없습니다. 그러나 다른 컨트롤 (예 : SearchBar)과 같은 문제에 가치를 더할 수있는 놀라운 잠재력을 가지고 있기 때문에 jlarjlar의 답변 을 확장하고 싶었습니다 . 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 속성에 연결하면 터치 영역이 하위 클래스에 정의 된 영역에 맞게 확장됩니다.

CGRectInsetCGRectContainsPoint 메소드 와 함께 pointInside 메소드 를 대체하는 것 외에도 UIView 서브 클래스의 사각형 터치 영역을 확장 하기 위해 CGGeometry 를 검사하는 데 시간이 걸립니다 . 당신은 또한에 CGGeometry의 사용 사례에 대한 몇 가지 좋은 팁 찾을 수 있습니다 NSHipster을 .

예를 들어 위에서 언급 한 방법을 사용하여 터치 영역을 불규칙하게 만들거나 가로 터치 영역보다 너비 터치 영역을 두 배 크게 만들도록 선택할 수 있습니다.

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

NB : UI 클래스 컨트롤을 대체하면 다른 컨트롤 (또는 UIImageView 등의 UIView 하위 클래스)에 대한 터치 영역을 확장 할 때 비슷한 결과가 나타납니다.


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를 사용하여 이미지를 버튼의 새 프레임으로 조정합니다.
Mihai Damian

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 인 경우 다른 사람과 같이 음수가 큰 경우이 메소드가 호출되지 않습니다. 그렇지 않으면 이것이 작동합니다.
James O'Brien

이것은 나를 위해 작동하지 않습니다. hitFrame이 올바르게 계산 된 것처럼 보이지만 점이 self.bounds 외부에있을 때는 pointInside :가 호출되지 않습니다. if (CGRectContainsPoint (hitFrame, point) &&! CGRectContainsPoint (self.bounds, point)) {NSLog (@ "성공!"); // 절대 호출되지 않음
arsenius

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직접 유형으로 사용할 수는 없지만 CGRect4가있는 구조체로 구성되어 있기 때문에 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 안에 넣은 다음 UIButton에서와 같이 UIView 인스턴스에서 터치 이벤트를 잡을 수 있습니다. 이렇게하면 여전히 단추가 있지만 더 큰 터치 영역이 있습니다. 사용자가 버튼 대신보기를 터치하면 버튼을 선택하고 강조 표시 한 상태를 수동으로 처리해야합니다.

다른 가능성은 UIButton 대신 UIImage를 사용하는 것입니다.


5

정보 버튼의 적중 영역을 프로그래밍 방식으로 늘릴 수있었습니다. "i"그래픽은 크기가 변경되지 않으며 새 버튼 프레임의 중앙에 유지됩니다.

Interface Builder에서 정보 버튼의 크기는 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

Chase의 사용자 정의 적중 테스트는 UIButton의 하위 클래스로 구현되었습니다. Objective-C로 작성되었습니다.

for initbuttonWithType:생성자 모두에서 작동하는 것 같습니다 . 내 요구에 완벽하지만 하위 클래스 이후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:. 예를 들어 버튼의 측면이 44px보다 작은 경우 (최소 탭 가능 영역으로 권장 됨) 다음을 사용하십시오.

- (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의 답변을 바탕으로 (가장 우아한 해결책을 찾았습니다) 다음은 신속한 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

방금 스위프트 2.2에서 @Chase 솔루션 의 포트를 수행했습니다.

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

스위프트 4에 대한 @antoine의 답변 형식

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으로 변환했습니다. Xamarin iOS에서 솔루션을 찾고 있다면 여기에 있습니다.

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 투명 영역을 더 크게하여 탭 영역을 확대합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.