UIButton:ヒット領域をデフォルトのヒット領域より大きくする


188

UIButtonとそのヒット領域について質問があります。インターフェイスビルダーで[Info Dark]ボタンを使用していますが、一部の人の指にはヒット領域が十分に大きくないことがわかりました。

InfoButtonグラフィックのサイズを変更せずに、プログラムまたはInterface Builderでボタンのヒット領域を増やす方法はありますか?


何も機能しない場合は、画像なしでUIButtonを追加してから、ImageViewオーバーレイを配置します。
Varun 2017年

これは、サイト全体で最も驚くほど単純な質問でなければならず、何十もの回答があります。私はここで全体の答えを貼り付けることができ、意味: 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 '19 / 07/19

@honusあなたは正しいですし、Appleはこれを行うことを推奨していません。そうは言っても、このコードが共有ライブラリになく、アプリケーションのローカルにある限り、問題はありません。
2013

こんにちはチェイス、代わりにサブクラスを使用することの欠点はありますか?
2014年

3
不要な作業が多すぎて、次の2行の簡単なコード[[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"arrow.png"] forState:UIControlStateNormal];を実行できます。必要な幅と高さを設定するだけです。`backButton.frame = CGRectMake(5、28、45、30); `
Resty

7
@Resty、彼は背景画像を使用しています。つまり、あなたのアプローチは機能しません。
mattsson 2014

77

インターフェイスビルダーで画像のエッジインセット値を設定するだけです。


1
背景画像がインセットに応答しないため、これは機能しないと思いましたが、画像プロパティを設定してから、タイトルの負の左端インセットを画像の幅に設定しました。私のイメージ。
デイブロス

12
これはコードで行うこともできます。たとえば、button.imageEdgeInsets = UIEdgeInsetsMake(-10, -10, -10, -10);
bengoesboom 2013年

デイブ・ロスの答えは素晴らしいです。想像できない人のために、-imageはタイトルをそれ自体の右側に(明らかに)ぶつけますので、IBの小さなクリッカーをクリックして、それを元に戻すことができます(または前述のコード内)。私が目にする唯一の欠点は、伸縮可能な画像を使用できないことです。IMOに支払う低価格
Paul Bruneau 14

7
画像を引き伸ばさずにこれを行う方法?
zonabi 14

コンテンツモードを、スケーリングなどではなく、センターなどに設定します。
Samuel Goodwin 2014年

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 2015

よろしくお願いします。知っておくべき潜在的な欠点はありますか?
クラシャロット

私はこのソリューションが好きです。すべてのボタンに追加のプロパティを設定する必要はありません。おかげで
xtrinch

1
アップルのガイドラインがヒット領域のこれらの寸法を推奨している場合、なぜ実装の失敗を修正する必要があるのですか?これは後のSDKで対処されていますか?
NoodleOfDeath 2016

2
Stack Overflowとその貢献者に感謝します。最も一般的な基本的な問題を解決する方法に関する有用でタイムリーな正確な情報を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)
}

Objective-C

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGFloat margin = 5.0;
    CGRect area = CGRectInset(self.bounds, -margin, -margin);
    return CGRectContainsPoint(area, point);
}

1
追加のカテゴリなしの本当にクールなソリューションに感謝します。また、XIBにあるボタンと簡単に統合することもできます。すばらしい答え
Matrosov Alexander

これは、他のすべてのコントロールに拡張できる優れたソリューションです。たとえば、UISearchBarをサブクラス化すると便利です。pointInsideメソッドは、iOS 2.0以降のすべてのUIViewにあります。BTW-Swift:func pointInside(_ point:CGPoint、withEvent event:UIEvent!)-> Bool。
Tommie C. 2014

これをありがとう!他の人が言ったように、これは他のコントロールで本当に役立ちます!
Yanchi、2015

これは素晴らしい!!ありがとう、あなたは私に多くの時間を節約してくれました!:-)
Vojta 2016年

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 16

2
そして、変数pTouchAreaEdgeInsetsはでIntはなく、でなければなりませんUIEdgeInsets。(実際には、どのタイプでもかまいませんIntが、最も一般的で単純なタイプなので、そのような構成では一般的です)。(したがって、私はこのアプローチが一般的に大好きです。ありがとう、dcRay!)
Valentin Shergin

1
私はtitleEdgeInsets、imageEdgeInsets、contentEdgeInsetを試しましたが、この拡張機能はうまく機能していません。これを投稿していただきありがとうございます!よくやった !!
Yogesh Patel

19

カスタムタイプのUIButtonを情報ボタンの上に配置することをお勧めします。カスタムボタンのサイズを、ヒット領域にするサイズに変更します。そこから2つのオプションがあります。

  1. カスタムボタンの[ハイライトにタッチを表示する]オプションをオンにします。情報ボタンの上に白い輝きが表示されますが、ほとんどの場合、ユーザーの指はこれを覆い、外側にある輝きだけが表示されます。

  2. 情報ボタン用にIBOutletを設定し、カスタムボタン用に2つのIBActionを設定します。1つは「タッチダウン」用、もう1つは「タッチタッチインサイド」用です。次に、Xcodeで、タッチダウンイベントで情報ボタンの強調表示されたプロパティをYESに設定し、タッチアップイベントで強調表示されたプロパティをNOに設定します。


19

backgroundImage画像でプロパティを設定するのではなく、プロパティを設定してくださいimageView。また、にimageView.contentMode設定したことを確認してくださいUIViewContentModeCenter


1
具体的には、ボタンのcontentModeプロパティをUIViewContentModeCenterに設定します。次に、ボタンのフレームを画像より大きくすることができます。私のために働く!
arlomedia 2012

参考までに、UIButtonはUIViewContentModeを尊重しません:stackoverflow.com/a/4503920/361247
Enrico Susatyo 2013

@EnricoSusatyoすみません、私が話しているAPIを明確にしました。imageView.contentMode設定することができますUIContentModeCenter
マウリツィオ

これは完璧に機能します、アドバイスをありがとう![[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"image.png"] forState:UIControlStateNormal];
Resty

@Resty上記のコメントは私には機能しません:/画像は、より大きな設定フレームサイズにサイズ変更されます。
Anil

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ファイルで問題のコントロールを選択し、IDインスペクターを開いて、カスタムクラスの名前を入力します。

mngbutton

ボタンを含むシーンのUIViewControllerクラスで、ボタンのクラスタイプをサブクラスの名前に変更します。

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

ストーリーボード/ xibボタンをプロパティIBOutletにリンクすると、サブクラスで定義された領域に合わせてタッチ領域が拡張されます。

pointInsideメソッドCGRectInsetおよびCGRectContainsPointメソッドと共にオーバーライドすることに加えて、UIViewサブクラスの長方形のタッチ領域を拡張するためにCGGeometryを調べるのに時間をかける必要があります。あなたはまたでCGGeometryのユースケースにいくつかの素晴らしいヒントを見つけることができNSHipster

たとえば、上記の方法を使用してタッチ領域を不規則にしたり、幅のタッチ領域を水平のタッチ領域の2倍にしたりすることができます。

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

注意: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は正しく計算されたように見えますが、pointInside:は、ポイントがself.boundsの外にある場合は呼び出されません。if(CGRectContainsPoint(hitFrame、point)&&!CGRectContainsPoint(self.bounds、point)){NSLog(@ "Success!"); //呼び出されなかった}
arsenius 2014年

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は直接型として使用できませんが、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)
    }
}

このpointInsideを手動で変更する方法 古いマージンのある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

UIButtonのサブクラスとして実装されたChaseのカスタムヒットテスト。Objective-Cで記述されています。

両方のために動作しているようですinitし、buttonWithType:コンストラクタ。私のニーズには完璧ですが、サブクラス化UIButtonは複雑になる可能性があるので、誰かが問題を抱えているかどうか知りたいです。

CustomeHitAreaButton.h

#import <UIKit/UIKit.h>

@interface CustomHitAreaButton : UIButton

- (void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets;

@end

CustomHitAreaButton.m

#import "CustomHitAreaButton.h"

@interface CustomHitAreaButton()

@property (nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;

@end

@implementation CustomHitAreaButton

- (instancetype)initWithFrame:(CGRect)frame {
    if(self = [super initWithFrame:frame]) {
        self.hitTestEdgeInsets = UIEdgeInsetsZero;
    }
    return self;
}

-(void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets {
    self->_hitTestEdgeInsets = hitTestEdgeInsets;
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    if(UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero) || !self.enabled || self.hidden) {
        return [super pointInside:point withEvent:event];
    }
    CGRect relativeFrame = self.bounds;
    CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.hitTestEdgeInsets);
    return CGRectContainsPoint(hitFrame, point);
}

@end

3

オーバーライドされたUIButtonによる実装。

Swift 2.2

// don't forget that negative values are for outset
_button.hitOffset = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
...
class UICustomButton: UIButton {
    var hitOffset = UIEdgeInsets()

    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        guard hitOffset != UIEdgeInsetsZero && enabled && !hidden else {
            return super.pointInside(point, withEvent: event)
        }
        return UIEdgeInsetsInsetRect(bounds, hitOffset).contains(point)
    }
}

2

WJBackgroundInsetButton.h

#import <UIKit/UIKit.h>

@interface WJBackgroundInsetButton : UIButton {
    UIEdgeInsets backgroundEdgeInsets_;
}

@property (nonatomic) UIEdgeInsets backgroundEdgeInsets;

@end

WJBackgroundInsetButton.m

#import "WJBackgroundInsetButton.h"

@implementation WJBackgroundInsetButton

@synthesize backgroundEdgeInsets = backgroundEdgeInsets_;

-(CGRect) backgroundRectForBounds:(CGRect)bounds {
    CGRect sup = [super backgroundRectForBounds:bounds];
    UIEdgeInsets insets = self.backgroundEdgeInsets;
    CGRect r = UIEdgeInsetsInsetRect(sup, insets);
    return r;
}

@end

2

カテゴリのメソッドをオーバーライドしないでください。ボタンをサブクラス化してオーバーライドします- pointInside:withEvent:。たとえば、ボタンの側面が44ピクセル(最小タップ可能領域として推奨)より小さい場合は、次のようにします。

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    return (ABS(point.x - CGRectGetMidX(self.bounds)) <= MAX(CGRectGetMidX(self.bounds), 22)) && (ABS(point.y - CGRectGetMidY(self.bounds)) <= MAX(CGRectGetMidY(self.bounds), 22));
}

2

私はまさにこの目的のために図書館を作りました。

UIViewカテゴリの使用を選択できます。サブクラス化は必要ありません

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

または、UIViewまたはUIButtonをサブクラス化して、minimumHitTestWidthおよび/またはminimumHitTestHeight。ボタンのヒットテスト領域は、これらの2つの値で表されます。

他のソリューションと同様に、この- (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

Swift 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

私はチェイスの応答を追跡しましたが、それがうまく機能します。ボタンが選択解除されるゾーンよりも大きいエリアを作成すると、1つの問題が発生します(ゾーンが大きくない場合)、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に基づいてフレームを設定し、Retinaの場合は50%スケーリングします。OK、多分あなたは上記に同意するかもしれませんが、ヒット領域をより大きくして頭痛を軽減したい場合:

私が何をしているのか、フォトショップで画像を開き、キャンバスのサイズを120%に増やして保存します。事実上、透明なピクセルで画像を大きくしただけです。

ただ1つのアプローチ。


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.