UIButton: Membuat area hit lebih besar dari area hit standar

188

Saya punya pertanyaan berurusan dengan UIButton dan area hit-nya. Saya menggunakan tombol Info Dark di pembangun antarmuka, tetapi saya menemukan bahwa area hit tidak cukup besar untuk jari-jari beberapa orang.

Apakah ada cara untuk menambah area tekan tombol baik secara terprogram atau di Interface Builder tanpa mengubah ukuran grafik InfoButton?

Kevin Bomberry
sumber
Jika tidak ada yang berfungsi, tambahkan saja UIButton tanpa gambar dan kemudian taruh hamparan ImageView!
Varun
Ini harus menjadi pertanyaan paling luar biasa sederhana di seluruh situs, yang memiliki puluhan jawaban. Maksudku, aku bisa menempelkan seluruh jawaban di sini: override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { return bounds.insetBy(dx: -20, dy: -20).contains(point) }
Fattie

Jawaban:

137

Karena saya menggunakan gambar latar belakang, tidak ada solusi yang bekerja dengan baik untuk saya. Berikut adalah solusi yang melakukan beberapa keajaiban obyektif-c dan menawarkan solusi drop in dengan kode minimal.

Pertama, tambahkan kategori ke UIButtonyang menimpa tes hit dan juga menambahkan properti untuk memperluas kerangka uji hit.

UIButton + Extensions.h

@interface UIButton (Extensions)

@property(nonatomic, assign) UIEdgeInsets hitTestEdgeInsets;

@end

UIButton + Ekstensi.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

Setelah kelas ini ditambahkan, yang perlu Anda lakukan adalah mengatur insets tepi tombol Anda. Perhatikan bahwa saya memilih untuk menambahkan insets jadi jika Anda ingin membuat area hit lebih besar, Anda harus menggunakan angka negatif.

[button setHitTestEdgeInsets:UIEdgeInsetsMake(-10, -10, -10, -10)];

Catatan: Ingatlah untuk mengimpor kategori ( #import "UIButton+Extensions.h") di kelas Anda.

Mengejar
sumber
26
Mengganti metode dalam kategori adalah ide yang buruk. Bagaimana jika kategori lain juga mencoba mengesampingkan pointInside: withEvent :? Dan panggilan ke [super pointInside: withEvent] tidak memanggil versi panggilan UIButton (jika ada) tetapi versi orang tua UIButton.
Honus
@ Yhonus Anda benar dan Apple tidak merekomendasikan melakukan ini. Yang sedang berkata, selama kode ini tidak di perpustakaan bersama dan hanya lokal untuk aplikasi Anda, Anda harus baik-baik saja.
Mengejar
Hai Chase, apakah ada kerugian menggunakan subkelas?
Sid
3
Anda melakukan terlalu banyak pekerjaan yang tidak perlu, Anda dapat melakukan dua baris kode sederhana: [[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"arrow.png"] forState:UIControlStateNormal];Dan cukup set lebar & tinggi yang Anda butuhkan: `backButton.frame = CGRectMake (5, 28, 45, 30);`
Resty
7
@Resty, dia menggunakan gambar latar belakang, yang berarti bahwa pendekatan Anda tidak akan berhasil.
mattsson
77

Cukup atur nilai inset tepi gambar di pembuat antarmuka.

Samuel Goodwin
sumber
1
Saya pikir ini tidak akan bekerja untuk saya karena gambar latar belakang tidak menanggapi inset tapi saya mengubah pengaturan properti gambar dan kemudian menetapkan inset tepi kiri negatif untuk judul saya lebar gambar dan itu kembali di atas gambar saya.
Dave Ross
12
Ini juga bisa dilakukan dalam kode. Misalnya,button.imageEdgeInsets = UIEdgeInsetsMake(-10, -10, -10, -10);
bengoesboom
Jawaban Dave Ross sangat bagus. Bagi mereka yang tidak bisa membayangkannya, -gambar menabrak judul Anda ke kanan sendiri (rupanya), sehingga Anda dapat mengklik clicker kecil di IB untuk memindahkannya kembali (atau dalam kode seperti yang dinyatakan). Satu-satunya negatif yang saya lihat adalah saya tidak dapat menggunakan gambar yang dapat diregangkan. Harga kecil untuk membayar IMO
Paul Bruneau
7
bagaimana melakukan ini tanpa meregangkan gambar?
zonabi
Atur mode konten ke sesuatu seperti Pusat dan bukan Penskalaan apa pun.
Samuel Goodwin
63

Inilah solusi elegan menggunakan Extensions in Swift. Ini memberi semua UIButtons area hit setidaknya 44x44 poin, sesuai dengan Panduan Antarmuka Manusia 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
    }
}
giaset
sumber
3
Anda perlu menguji apakah tombol saat ini disembunyikan. Jika demikian, kembalikan nihil.
Josh Gafni
Terima kasih, ini terlihat hebat. Adakah potensi kerugian yang harus diwaspadai?
Crashalot
Saya suka solusi ini, tidak mengharuskan saya untuk mengatur properti tambahan di semua tombol. Terima kasih
xtrinch
1
Jika pedoman Apple merekomendasikan dimensi ini untuk area hit, mengapa membuat kita harus memperbaiki kegagalan mereka untuk mengimplementasikan? Apakah ini dibahas di SDK nanti?
NoodleOfDeath
2
Syukurlah kita memiliki Stack Overflow, dan kontributornya. Karena kita tentu saja tidak dapat mengandalkan Apple untuk informasi yang berguna, tepat waktu, akurat tentang cara memperbaiki masalah-masalah mendasar mereka yang paling umum.
Womble
49

Anda juga dapat subkelas UIButtonatau kustom UIViewdan menimpa point(inside:with:)dengan sesuatu seperti:

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

Objektif-C

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGFloat margin = 5.0;
    CGRect area = CGRectInset(self.bounds, -margin, -margin);
    return CGRectContainsPoint(area, point);
}
jlajlar
sumber
1
terima kasih solusi yang sangat keren tanpa kategori tambahan. Saya juga mengintegrasikannya dengan tombol saya di XIB dan berfungsi dengan baik. jawaban yang bagus
Matrosov Alexander
Ini adalah solusi hebat yang dapat diperluas ke semua kontrol lain! Saya merasa bermanfaat untuk subkelas UISearchBar misalnya. Metode pointInside ada di setiap UIView sejak iOS 2.0. BTW - Swift: func pointInside (_ point: CGPoint, withEvent event: UIEvent!) -> Bool.
Tommie C.
Terima kasih untuk ini! Seperti yang lain katakan, ini bisa sangat berguna dengan kontrol lain!
Yanchi
Ini bagus !! Terima kasih, Anda menghemat banyak waktu! :-)
Vojta
35

Inilah UIButton + Extensions Chase di 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)
    }
}

Untuk menggunakannya, Anda dapat:

button.touchAreaEdgeInsets = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
dcRay
sumber
1
Sebenarnya, kita harus memperluas UIControl, bukan UIButton.
Valentin Shergin
2
Dan variabel pTouchAreaEdgeInsetsharus Int, bukan UIEdgeInsets. (Sebenarnya, itu bisa berupa jenis apa saja, tetapi Intmerupakan jenis yang paling umum dan sederhana, sehingga umum dalam konstruksi seperti itu). (Karena itu saya sangat menyukai pendekatan ini secara umum. Terima kasih, dcRay!)
Valentin Shergin
1
Saya telah mencoba titleEdgeInsets, imageEdgeInsets, contentEdgeInset semuanya tidak berfungsi untuk saya, ekstensi ini berfungsi dengan baik. Terima kasih telah memposting ini !! Kerja bagus !!
Yogesh Patel
19

Saya sarankan menempatkan UIButton dengan tipe Kustom di tengah tombol info Anda. Ubah ukuran tombol kustom ke ukuran yang Anda inginkan menjadi area hit. Dari sana Anda memiliki dua opsi:

  1. Periksa opsi 'Tampilkan sentuhan pada highlight' dari tombol kustom. Cahaya putih akan muncul di atas tombol info, tetapi dalam kebanyakan kasus jari pengguna akan membahas hal ini dan yang akan mereka lihat hanyalah cahaya di luar.

  2. Siapkan IBOutlet untuk tombol info dan dua IBAksi untuk tombol kustom satu untuk 'Touch Down' dan satu untuk 'Touch Up Inside'. Kemudian dalam Xcode buat acara touchdown atur properti yang disorot dari tombol info ke YES dan event touchupinside atur properti yang disorot menjadi TIDAK.

Kyle
sumber
19

Jangan atur backgroundImageproperti dengan gambar Anda, atur imageViewproperti. Juga, pastikan Anda telah imageView.contentModemengatur di UIViewContentModeCenter.

Maurizio
sumber
1
Lebih khusus lagi, setel properti contentMode tombol ke UIViewContentModeCenter. Kemudian Anda dapat membuat bingkai tombol lebih besar dari gambar. Bekerja untukku!
arlomedia
FYI, UIButton tidak menghormati UIViewContentMode: stackoverflow.com/a/4503920/361247
Enrico Susatyo
@EnricoSusatyo maaf, saya sudah mengklarifikasi API apa yang saya bicarakan. The imageView.contentModedapat ditetapkan sebagai UIContentModeCenter.
Maurizio
Ini bekerja dengan baik, terima kasih atas sarannya! [[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"image.png"] forState:UIControlStateNormal];
Resty
@Resty Komentar yang disebutkan di atas tidak bekerja untuk saya: / Gambar ini mengubah ukuran ke ukuran bingkai yang lebih besar.
Anil
14

Solusi saya di 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)
    }
}
Zhanserik
sumber
12

Tidak ada yang salah dengan jawaban yang disajikan; namun saya ingin memperluas jawaban jlarjlar karena memiliki potensi luar biasa yang dapat menambah nilai untuk masalah yang sama dengan kontrol lain (misalnya SearchBar). Ini karena sejak pointInside terpasang ke UIView, seseorang dapat mensubklasifikasikan kontrol apa pun untuk meningkatkan area sentuh. Jawaban ini juga menunjukkan sampel lengkap tentang bagaimana menerapkan solusi lengkap.

Buat subkelas baru untuk tombol Anda (atau kontrol apa pun)

#import <UIKit/UIKit.h>

@interface MNGButton : UIButton

@end

Selanjutnya timpa metode pointInside dalam implementasi subclass Anda

@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

Pada file storyboard / xib Anda pilih kontrol yang dimaksud dan buka inspektur identitas dan ketikkan nama kelas khusus Anda.

mngbutton

Di kelas UIViewController Anda untuk adegan yang berisi tombol, ubah tipe kelas untuk tombol tersebut ke nama subkelas Anda.

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

Hubungkan tombol storyboard / xib Anda ke IBOutlet properti dan area sentuh Anda akan diperluas agar sesuai dengan area yang ditentukan dalam subkelas.

Selain mengganti metode pointInside bersama-sama dengan metode CGRectInset dan CGRectContainsPoint , kita harus meluangkan waktu untuk memeriksa CGGeometry untuk memperluas area sentuh persegi panjang dari setiap subkelas UIView. Anda juga dapat menemukan beberapa tips bagus tentang kasus penggunaan CGGeometry di NSHipster .

Misalnya seseorang dapat membuat area sentuh tidak teratur menggunakan metode yang disebutkan di atas atau hanya memilih untuk membuat area sentuh lebar dua kali lebih besar dari area sentuh horizontal:

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

NB: Mengganti kontrol Kelas UI harus menghasilkan hasil yang serupa pada perluasan area sentuh untuk kontrol yang berbeda (atau subkelas UIView lainnya, seperti UIImageView, dll.).

Tommie C.
sumber
10

Ini bekerja untuk saya:

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)];
Olof
sumber
3
Ini berfungsi, tetapi berhati-hatilah jika Anda perlu mengatur gambar dan judul menjadi tombol. Dalam hal ini, Anda harus menggunakan setBackgoundImage yang akan menskala gambar Anda ke bingkai tombol yang baru.
Mihai Damian
7

Jangan mengubah perilaku 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
pengguna3109079
sumber
Jika tombol Anda adalah 40x40, metode ini tidak akan dipanggil jika angka negatifnya lebih besar - seperti orang lain yang disebutkan. Kalau tidak, ini berhasil.
James O'Brien
Ini tidak berhasil untuk saya. hitFrame muncul terhitung dengan benar, tetapi pointInside: tidak pernah dipanggil saat titik berada di luar self.bounds. if (CGRectContainsPoint (hitFrame, point) &&! CGRectContainsPoint (self.bounds, point)) {NSLog (@ "Success!"); // Tidak pernah memanggil}
arsenius
7

Saya menggunakan pendekatan yang lebih umum dengan swizzling -[UIView pointInside:withEvent:]. Ini memungkinkan saya untuk memodifikasi perilaku pengujian hit pada apa pun UIView, bukan hanya UIButton.

Seringkali, sebuah tombol ditempatkan di dalam tampilan wadah yang juga membatasi pengujian hit. Misalnya, ketika sebuah tombol berada di atas tampilan wadah dan Anda ingin memperpanjang target sentuh ke atas, Anda juga harus memperluas target sentuh dari tampilan wadah.

@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

Yang menyenangkan tentang pendekatan ini adalah Anda dapat menggunakannya bahkan di Storyboards dengan menambahkan Atribut Runtime yang Ditentukan Pengguna. Sayangnya, UIEdgeInsetstidak langsung tersedia sebagai jenis ada tapi karena CGRectjuga terdiri dari struct dengan empat CGFloatbekerja sempurna dengan memilih "Rect" dan mengisi nilai-nilai seperti ini: {{top, left}, {bottom, right}}.

Ortwin Gentz
sumber
6

Saya menggunakan kelas berikut di Swift, untuk juga mengaktifkan properti Interface Builder untuk menyesuaikan margin:

@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)
    }
}
Antoine
sumber
bagaimana cara mengubah titik ini secara manual? Jika saya telah membuat UIButton dengan margin lama, tetapi sekarang saya ingin mengubahnya?
TomSawyer
5

Nah, Anda dapat menempatkan UIButton Anda di dalam UIView yang transparan dan sedikit lebih besar, dan kemudian menangkap peristiwa sentuhan pada instance UIView seperti pada UIButton. Dengan begitu, Anda masih memiliki tombol, tetapi dengan area sentuh yang lebih besar. Anda harus secara manual berurusan dengan negara-negara yang dipilih & disorot dari tombol jika pengguna menyentuh tampilan bukan tombol.

Kemungkinan lain melibatkan penggunaan UIImage, bukan UIButton.

Pablo Santa Cruz
sumber
5

Saya dapat meningkatkan area klik tombol info secara terprogram. Grafik "i" tidak mengubah skala dan tetap berpusat di bingkai tombol baru.

Ukuran tombol info tampaknya diperbaiki hingga 18x19 [*] di Interface Builder. Dengan menghubungkannya ke IBOutlet, saya dapat mengubah ukuran frame dalam kode tanpa masalah.

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

[*]: Versi iOS belakangan tampaknya meningkatkan area hit dari tombol info.

otto
sumber
5

Ini adalah Solusi Swift 3 saya (berdasarkan blog ini: 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
    }
}
arthurschiller
sumber
3

Tes hit kustom Chase diimplementasikan sebagai subkelas UIButton. Ditulis dalam Objective-C.

Tampaknya berfungsi untuk initdan buttonWithType:konstruktor. Untuk kebutuhan saya itu sempurna, tetapi karena subkelas UIButtonbisa berbulu, saya akan tertarik untuk mengetahui apakah ada yang salah dengan itu.

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
Phillip Martin
sumber
3

Implementasi melalui menimpa UIButton yang diwarisi.

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)
    }
}
dimpiax
sumber
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
William Jockusch
sumber
2

Jangan pernah menimpa metode dalam kategori. Tombol subclass dan override - pointInside:withEvent:. Misalnya jika sisi tombol Anda lebih kecil dari 44 px (yang disarankan sebagai area tappable minimum) gunakan ini:

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

Saya membuat perpustakaan untuk tujuan ini.

Anda dapat memilih untuk menggunakan UIViewkategori, tidak perlu subklas :

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

Atau Anda dapat mensubklasifikasikan UIView atau UIButton Anda dan mengatur minimumHitTestWidth dan / atauminimumHitTestHeight . Area tes tekan tombol Anda kemudian akan diwakili oleh 2 nilai ini.

Sama seperti solusi lain, ia menggunakan - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)eventmetode ini. Metode ini dipanggil ketika iOS melakukan pengujian hit. Posting blog ini memiliki deskripsi yang baik tentang cara kerja uji coba iOS.

https://github.com/kgaidis/KGHitTestingViews

@interface KGHitTestingButton : UIButton <KGHitTesting>

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

@end

Anda juga bisa hanya subkelas dan menggunakan Pembuat Antarmuka tanpa menulis kode apa pun: masukkan deskripsi gambar di sini

kgaidis
sumber
1
Saya akhirnya menginstal ini hanya demi kecepatan. Menambahkan IBInspectable adalah ide bagus, terima kasih telah menyatukan ini.
user3344977
2

Berdasarkan jawaban giaset di atas (yang saya temukan solusi paling elegan), berikut adalah versi cepat 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
    }
}
Nhon Nguyen
sumber
2

Sederhana saja:

class ChubbyButton: UIButton {
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        return bounds.insetBy(dx: -20, dy: -20).contains(point)
    }
}

Itu semuanya.

Fattie
sumber
1

Saya menggunakan trik ini untuk tombol di dalam tableviewcell.accessoryView untuk memperbesar area sentuhnya

#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];
}
abuharsky
sumber
1

Saya baru saja melakukan port dari solusi @Chase di swift 2.2

import Foundation
import ObjectiveC

private var hitTestEdgeInsetsKey: UIEdgeInsets = UIEdgeInsetsZero

extension UIButton {
    var hitTestEdgeInsets:UIEdgeInsets {
        get {
            let inset = objc_getAssociatedObject(self, &hitTestEdgeInsetsKey) as? NSValue ?? NSValue(UIEdgeInsets: UIEdgeInsetsZero)
            return inset.UIEdgeInsetsValue()
        }
        set {
            let inset = NSValue(UIEdgeInsets: newValue)
            objc_setAssociatedObject(self, &hitTestEdgeInsetsKey, inset, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    public override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
        guard !UIEdgeInsetsEqualToEdgeInsets(hitTestEdgeInsets, UIEdgeInsetsZero) && self.enabled == true && self.hidden == false else {
            return super.pointInside(point, withEvent: event)
        }
        let relativeFrame = self.bounds
        let hitFrame = UIEdgeInsetsInsetRect(relativeFrame, hitTestEdgeInsets)
        return CGRectContainsPoint(hitFrame, point)
    }
}

Anda dapat menggunakan seperti ini

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

Untuk referensi lain, lihat https://stackoverflow.com/a/13067285/1728552

Serluca
sumber
1

@ jawaban antoine diformat untuk 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 );
    }
}
spfursich
sumber
0

Saya telah mengikuti respons Chase dan berfungsi dengan baik, satu masalah tunggal ketika Anda membuat arrea terlalu besar, lebih besar dari zona tempat tombolnya tidak dipilih (jika zona tidak lebih besar) tidak memanggil pemilih untuk acara UIControlEventTouchUpInside .

Saya pikir ukurannya lebih dari 200 arah atau sesuatu seperti itu.

Naga tidak nyata
sumber
0

Saya sangat terlambat ke permainan ini, tetapi ingin mempertimbangkan teknik sederhana yang dapat memecahkan masalah Anda. Berikut adalah cuplikan UIButton terprogram terprogram untuk saya:

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

Saya memuat Gambar png transparan untuk tombol saya dan mengatur gambar latar belakang. Saya mengatur frame berdasarkan UIImage dan scaling 50% untuk retina. OK, mungkin Anda setuju dengan yang di atas atau tidak, TETAPI jika Anda ingin membuat area hit lebih BESAR dan menyelamatkan diri Anda dari sakit kepala:

Apa yang saya lakukan, buka gambar di photoshop dan cukup tambahkan ukuran kanvas hingga 120% dan hemat. Secara efektif Anda baru saja membuat gambar lebih besar dengan piksel transparan.

Hanya satu pendekatan.

chrisallick
sumber
0

@ jlajlar jawaban di atas tampak bagus dan langsung tetapi tidak cocok dengan Xamarin.iOS, jadi saya mengubahnya menjadi Xamarin. Jika Anda mencari solusi pada Xamarin iOS, ini dia:

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

Anda bisa menambahkan metode ini ke kelas tempat Anda meng-override UIView atau UIImageView. Ini bekerja dengan baik :)

Memiliki AlTaiar
sumber
0

Tidak ada jawaban yang berfungsi sempurna untuk saya, karena saya menggunakan gambar latar belakang dan judul pada tombol itu. Selain itu, tombol akan mengubah ukuran seiring perubahan ukuran layar.

Sebagai gantinya, saya memperbesar area keran dengan membuat area transparan png lebih besar.

Jakehao
sumber