UIButton: İsabet alanını varsayılan isabet alanından daha büyük yapma


188

UIButton ve isabet alanıyla ilgili bir sorum var. Arayüz oluşturucuda Info Dark düğmesini kullanıyorum, ancak hit alanının bazı insanların parmakları için yeterince büyük olmadığını görüyorum.

InfoButton grafiğinin boyutunu değiştirmeden bir düğmenin vuruş alanını programlı olarak veya Arabirim Oluşturucu'da artırmanın bir yolu var mı?


Hiçbir şey çalışmıyorsa, görüntü olmadan bir UIButton ekleyin ve bir ImageView yer paylaşımı koyun!
Varun

Bu, düzinelerce cevabı olan tüm sitede en şaşırtıcı basit soru olmalı. Yani, tüm cevabı buraya yapıştırabilirim: override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { return bounds.insetBy(dx: -20, dy: -20).contains(point) }
Fattie

Yanıtlar:


137

Arka plan resmi kullandığım için, bu çözümlerin hiçbiri benim için iyi sonuç vermedi. İşte bazı eğlenceli objektif c sihir yapan ve minimum kod ile çözümde bir düşüş sunan bir çözüm.

İlk olarak, UIButtonisabet testini geçersiz kılan bir kategori ekleyin ve isabet testi çerçevesini genişletmek için bir özellik ekler.

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

Bu sınıf eklendiğinde, tek yapmanız gereken düğmenizin kenar iç metinlerini ayarlamaktır. Ekleri eklemeyi seçtiğime dikkat edin, böylece isabet alanını büyütmek istiyorsanız negatif sayılar kullanmalısınız.

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

Not: #import "UIButton+Extensions.h"Sınıflarınıza kategori ( ) aktarmayı unutmayın.


26
Bir kategorideki bir yöntemi geçersiz kılmak kötü bir fikirdir. Başka bir kategori de pointInside: withEvent: ile geçersiz kılmaya çalışırsa? Ve [super pointInside: withEvent] çağrısı, UIButton'un çağrısının sürümünü (varsa) değil, UIButton'un üst öğesinin sürümünü çağırır.
Honus

@honus Haklısınız ve Apple bunu yapmanızı önermiyor. Bununla birlikte, bu kod paylaşılan bir kütüphanede olmadığı ve yalnızca uygulamanız için yerel olduğu sürece, iyi olmalısınız.
Chase

Merhaba Chase, bunun yerine alt sınıf kullanmanın bir dezavantajı var mı?
Sid

3
Çok fazla gereksiz iş yapıyorsunuz, iki basit kod satırı yapabilirsiniz: [[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"arrow.png"] forState:UIControlStateNormal];Ve sadece ihtiyacınız olan genişlik ve yüksekliği ayarlayın: `backButton.frame = CGRectMake (5, 28, 45, 30);`
Resty

7
@Resty, arka plan resmini kullanıyor, yani yaklaşımınız işe yaramıyor.
mattsson

77

Arayüz oluşturucuda görüntü kenarı iç değerlerini ayarlamanız yeterlidir.


1
Arka plan görüntüleri eklere yanıt vermediği için bunun benim için işe yaramayacağını düşündüm, ancak görüntü özelliğini ayarlamaya ve ardından başlığım için görüntünün genişliğine negatif bir sol kenar eki ayarladım ve benim görselim.
Dave Ross

12
Bu kod olarak da yapılabilir. Örneğin,button.imageEdgeInsets = UIEdgeInsetsMake(-10, -10, -10, -10);
bengoesboom

Dave Ross'un cevabı harika. Resim yapamayanlar için, -image başlığınızı kendisinin sağına (görünüşe göre) çarptırır, böylece geri taşımak için (veya belirtildiği gibi kodda) IB'deki küçük tıklamayı tıklayabilirsiniz. Gördüğüm tek olumsuz, gerilebilir görüntüleri kullanamam. IMO ödemek için küçük bir fiyat
Paul Bruneau

7
görüntüyü germeden nasıl yapılır?
zonabi

İçerik modunu Ölçekleme gibi değil Merkez gibi bir şeye ayarlayın.
Samuel Goodwin

63

Swift'te Extensions kullanan zarif bir çözüm. Apple'ın İnsan Arayüz Kılavuzlarına göre tüm UIButton'lara en az 44x44 puanlık bir isabet alanı verir ( https://developer.apple.com/ios/human-interface-guidelines/visual-design/layout/ )

Hızlı 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
    }
}

Hızlı 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
Düğmenin şu anda gizli olup olmadığını test etmeniz gerekir. Öyleyse sıfır döndürün.
Josh Gafni

Teşekkürler, bu harika görünüyor. Fark edilmesi gereken herhangi bir olumsuz yanı var mı?
Crashalot

Bu çözümü seviyorum, tüm düğmelerde ek özellikler ayarlamam gerekmiyor. Teşekkürler
xtrinch

1
Apple yönergeleri vuruş alanı için bu boyutları önerirse, neden uygulama başarısızlıklarını düzeltmemiz gerekiyor? Bu daha sonraki SDK'larda ele alınıyor mu?
NoodleOfDeath

2
Çok şükür Stack Overflow ve katkıda bulunanlar var. Çünkü en yaygın, temel sorunlarının nasıl çözüleceğine dair yararlı, zamanında, doğru bilgiler için Apple'a kesinlikle güvenemeyiz.
Womble

49

Ayrıca, alt sınıfı UIButtonveya özel bir öğeyi kullanabilir UIViewve aşağıdakilerle geçersiz kılabilirsiniz point(inside:with:):

Hızlı 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
herhangi bir ek kategori olmadan gerçekten harika bir çözüm sayesinde. Ayrıca basitçe XIB'de bulunan düğmelerim ile entegre ediyorum ve iyi çalışıyor. büyük cevap
Matrosov Alexander

Bu, diğer tüm kontrollere genişletilebilecek harika bir çözümdür! Örneğin bir UISearchBar alt sınıf için yararlı buldum. PointInside yöntemi, iOS 2.0'dan beri her UIView'de. BTW - Swift: func pointInside (_ noktası: CGPoint, withEvent olayı: UIEvent!) -> Bool.
Tommie C.8

Bunun için teşekkürler! Diğerleri gibi, bu diğer kontroller için gerçekten yararlı olabilir!
Yanchi

Bu harika!! Teşekkürler, bana çok zaman kazandın! :-)
Vojta

35

İşte Swift 3.0'da Chase'in UIButton + Uzantıları.


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

Kullanmak için şunları yapabilirsiniz:

button.touchAreaEdgeInsets = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)

1
Aslında, uzatmalıyız UIControl, değil UIButton.
Valentin Shergin

2
Ve değişken pTouchAreaEdgeInsetsolmalı Int, değil UIEdgeInsets. (Aslında, herhangi bir tür olabilir, ancak Inten genel ve basit türdür, bu nedenle bu tür yapılarda yaygındır). (Bu yüzden genel olarak bu yaklaşımı seviyorum. Teşekkür ederim, dcRay!)
Valentin Shergin

1
BaşlıkEdgeInsets, imageEdgeInsets, contentEdgeInset hepsi benim için çalışmıyor bu uzantı iyi çalışıyor deneyin. Bunu gönderdiğiniz için teşekkür ederiz !! İyi iş !!
Yogesh Patel

19

Bilgi düğmenizin üzerinde ortalanmış Özel türüne sahip bir UIButton yerleştirmenizi öneririz. Özel düğmeyi, isabet alanının olmasını istediğiniz boyuta getirin. Oradan iki seçeneğiniz var:

  1. Özel düğmenin 'Vurguda dokunmayı göster' seçeneğini işaretleyin. Beyaz ışıltı bilgi düğmesinin üzerinde görünecektir, ancak çoğu durumda kullanıcıların parmağı bunu kapsayacaktır ve görecekleri tek şey dışarıdaki ışıltıdır.

  2. Bilgi düğmesi için bir IBOutlet ve biri 'Aşağıya Dokunuş' için özel düğme için ve diğeri 'İçeride Rötuş' için iki IBActions ayarlayın. Daha sonra Xcode'da, touchdown olayını bilgi düğmesinin vurgulanan özelliğini EVET olarak ve touchupinside olayı vurgulanan özelliği NO olarak ayarlayın.


19

Belirlemeyin backgroundImagegörüntü ile mülkiyet, set imageViewözelliği. Ayrıca, imageView.contentModeadresinde ayarladığınızdan emin olun UIViewContentModeCenter.


1
Daha spesifik olarak, düğmenin contentMode özelliğini UIViewContentModeCenter olarak ayarlayın. Ardından düğmenin çerçevesini görüntüden daha büyük yapabilirsiniz. Benim için çalışıyor!
arlomedia

FYI, UIButton UIViewContentMode'a saygı duymuyor: stackoverflow.com/a/4503920/361247
Enrico Susatyo 19:13

@EnricoSusatyo üzgünüm, hangi API'lardan bahsettiğimi açıkladım. Olarak imageView.contentModeayarlanabilir UIContentModeCenter.
Maurizio

Bu mükemmel çalışıyor, tavsiye için teşekkürler! [[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"image.png"] forState:UIControlStateNormal];
Resty

@ Resty Yukarıda belirtilen yorum benim için çalışmıyor: / Görüntü daha büyük çerçeve boyutuna göre yeniden boyutlandırılıyor.
Anıl

14

Swift 3'teki çözümüm:

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

Sunulan cevaplarda yanlış bir şey yok; Ancak jlarjlar'ın cevabını , diğer kontrollerle (örneğin SearchBar) aynı soruna değer katabilecek inanılmaz bir potansiyele sahip olarak genişletmek istedim . Bunun nedeni, pointInside bir UIView'a bağlı olduğu için, dokunma alanını geliştirmek için herhangi bir kontrolü alt sınıflandırabilmesidir . Bu cevap aynı zamanda komple çözümün nasıl uygulanacağının tam bir örneğini gösterir.

Düğmeniz (veya herhangi bir denetiminiz) için yeni bir alt sınıf oluşturun

#import <UIKit/UIKit.h>

@interface MNGButton : UIButton

@end

Ardından, alt sınıf uygulamanızdaki pointInside yöntemini geçersiz kılın

@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

Film şeridi / xib dosyanızda söz konusu kontrolü seçin ve kimlik denetçisini açın ve özel sınıfınızın adını yazın.

mngbutton

Düğmeyi içeren sahne için UIViewController sınıfınızda, düğmenin sınıf türünü alt sınıfınızın adıyla değiştirin.

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

Film şeridi / xib düğmenizi IBOutlet özelliğine bağlayın; dokunma alanınız alt sınıfta tanımlanan alana uyacak şekilde genişletilir.

PointInside yöntemini CGRectInset ve CGRectContainsPoint yöntemleriyle birlikte geçersiz kılmanın yanı sıra , herhangi bir UIView alt sınıfının dikdörtgen dokunma alanını genişletmek için CGGeometry'nin incelenmesi zaman almalıdır . Ayrıca at CGGeometry kullanım senaryoları üzerinde bazı güzel ipuçları bulabilirsiniz NSHipster .

Örneğin, yukarıda belirtilen yöntemleri kullanarak dokunma alanını düzensiz hale getirebilir veya genişlik dokunma alanını yatay dokunma alanının iki katı kadar büyük yapmayı seçebiliriz:

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

Not: Herhangi bir UI Sınıf kontrolünün değiştirilmesi, farklı kontroller (veya UIImageView, vb. Gibi herhangi bir UIView alt sınıfı) için dokunmatik alanın genişletilmesinde benzer sonuçlar üretmelidir.


10

Bu benim için çalışıyor:

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
Bu işe yarar, ancak bir düğmeye hem resim hem de başlık ayarlamanız gerektiğinde dikkatli olun. Bu durumda, görüntünüzü düğmenin yeni çerçevesine ölçeklendirecek setBackgoundImage'ı kullanmanız gerekir.
Mihai Damian

7

UIButton'un davranışını değiştirmeyin.

@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

Düğmeniz 40x40 ise, negatif sayılar daha büyükse (başka biri gibi) bu yöntem çağrılmaz. Aksi takdirde bu işe yarar.
James O'Brien

Bu benim için işe yaramıyor. hitFrame düzgün hesaplanmış görünür, ancak nokta self.bounds dışındaysa asla pointInside: çağrılmaz. if (CGRectContainsPoint (hitFrame, nokta) &&! CGRectContainsPoint (self.bounds, point)) {NSLog (@ "Başarılı!"); // Asla çağrılmadı}
arsenius

7

Ben swizzling tarafından daha genel bir yaklaşım kullanıyorum -[UIView pointInside:withEvent:]. Bu, isabet testi davranışını UIViewyalnızcaUIButton .

Çoğu zaman, bir konteyner görünümünün içine, isabet testini de sınırlayan bir düğme yerleştirilir. Örneğin, bir düğme kapsayıcı görünümünün üstündeyken ve dokunma hedefini yukarı doğru genişletmek istediğinizde, kapsayıcı görünümünün dokunma hedefini de uzatmanız gerekir.

@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

Bu yaklaşımın güzel yanı, bunu Kullanıcı Tanımlı Çalışma Zamanı Özelliği ekleyerek Storyboard'larda bile kullanabilmenizdir. Ne yazık ki, UIEdgeInsetsorada bir türü olarak doğrudan kullanılamaz ancak o zamandan beri CGRectde dört ile bir yapı oluşur CGFloato "Rect" seçme ve bunun gibi değerlerle doldurarak kusursuz çalışır: {{top, left}, {bottom, right}}.


6

Ayrıca, bir arayüz oluşturucu özelliği kenar boşluğunu ayarlamak için etkinleştirmek için Swift, aşağıdaki sınıfı kullanıyorum:

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

Bu noktayı manuel olarak nasıl değiştirebilirim? Eski marjlı bir UIButton oluşturduysam, şimdi değiştirmek istiyorum?
TomSawyer

5

UIButton'unuzu şeffaf ve biraz daha büyük bir UIView içine yerleştirebilir ve ardından UIView örneğinde olduğu gibi UIView örneğinde dokunma olaylarını yakalayabilirsiniz. Bu şekilde, düğmeniz yine de olacak, ancak daha geniş bir dokunmatik alana sahip olacaksınız. Kullanıcı düğme yerine görünüme dokunursa, düğme ile seçilen ve vurgulanan durumlarla ilgilenmeniz gerekir.

Diğer olasılık, bir UIButton yerine bir UIImage kullanılmasını içerir.


5

Bilgi düğmesinin vuruş alanını programlı olarak artırabildim. "İ" grafiği ölçeği değiştirmez ve yeni düğme çerçevesinde ortalanmış olarak kalır.

Bilgi düğmesinin boyutu, Arayüz Oluşturucu'da 18x19 [*] olarak sabit gibi görünüyor. Bir IBOutlet'e bağlayarak, koddaki çerçeve boyutunu herhangi bir sorun olmadan değiştirebildim.

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

[*]: İOS'un sonraki sürümlerinde bilgi düğmesinin isabet alanını artırdığı görülüyor.


5

Bu benim Swift 3 Çözümüm (bu blog yazısına dayanarak: 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'in UIButton alt sınıfı olarak uygulanan özel isabet testi. Amaç-C ile yazılmıştır.

Hem iş gibi görünüyor initve buttonWithType:kurucular. Benim ihtiyaçlarım için mükemmel, ama alt sınıftan beriUIButton kıllı olabileceğinden, kimsenin onunla bir hatası olup olmadığını bilmek isterim.

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

Geçersiz kılınan UIButton aracılığıyla uygulama.

Hızlı 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

Kategorideki yöntemi asla geçersiz kılmayın. Alt sınıf düğmesi ve geçersiz kılma - pointInside:withEvent:. Örneğin, düğmenizin tarafı 44 pikselden küçükse (minimum dokunma alanı olarak önerilir) bunu kullanın:

- (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

Bunun için bir kütüphane yaptım .

Bir UIViewkategori kullanmayı seçebilirsiniz , alt sınıflama gerekmez :

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

Veya UIView veya UIButton'unuzu alt sınıflara ayırabilir ve minimumHitTestWidthve / veya ayarlayabilirsiniz minimumHitTestHeight. Düğme vuruş testi alanınız bu 2 değerle temsil edilir.

Diğer çözümler gibi, - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)eventyöntemi kullanır . Yöntem, isabet testi gerçekleştirdiğinde çağrılır. Bu blog gönderisinde iOS hit testinin nasıl çalıştığı hakkında iyi bir açıklama var.

https://github.com/kgaidis/KGHitTestingViews

@interface KGHitTestingButton : UIButton <KGHitTesting>

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

@end

Ayrıca, herhangi bir kod yazmadan sadece Ara Sınıf Oluşturucu'yu kullanabilir ve kullanabilirsiniz: resim açıklamasını buraya girin


1
Bunu sadece hız uğruna kurmaya başladım. IBInspectable'ı eklemek, bunu bir araya getirdiğiniz için teşekkürler.
user3344977

2

Giaset'in cevabına (en zarif çözümü bulduğum) dayanarak, işte hızlı 3 versiyonu:

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

Sadece bu kadar basit:

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

Her şey bu.


1

Touchview alanını genişletmek için tableviewcell.accessoryView içindeki düğmeyi kullanıyorum

#pragma mark - Touches

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch                  = [touches anyObject];
    CGPoint location                = [touch locationInView:self];
    CGRect  accessoryViewTouchRect  = CGRectInset(self.accessoryView.frame, -15, -15);

    if(!CGRectContainsPoint(accessoryViewTouchRect, location))
        [super touchesBegan:touches withEvent:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch                  = [touches anyObject];
    CGPoint location                = [touch locationInView:self];
    CGRect  accessoryViewTouchRect  = CGRectInset(self.accessoryView.frame, -15, -15);

    if(CGRectContainsPoint(accessoryViewTouchRect, location) && [self.accessoryView isKindOfClass:[UIButton class]])
    {
        [(UIButton *)self.accessoryView sendActionsForControlEvents:UIControlEventTouchUpInside];
    }
    else
        [super touchesEnded:touches withEvent:event];
}

1

@Chase çözümünün portunu hızlı 2.2'de yaptım

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

böyle kullanabilirsiniz

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

Başka referanslar için bkz. Https://stackoverflow.com/a/13067285/1728552


1

@ antoine cevabı Swift 4 için biçimlendirildi

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

Chase'in yanıtını izledim ve harika çalışıyor, arrea'yı çok büyük, düğmenin seçiminin kaldırıldığı bölgeden daha büyük oluşturduğunuzda (bölge daha büyük değilse) UIControlEventTouchUpInside olayı için seçici çağırmıyor .

Bence boyutu herhangi bir yön ya da bunun gibi bir şey 200 üzerinde.


0

Bu oyuna çok geç kaldım, ancak sorunlarınızı çözebilecek basit bir tekniğe ağırlık vermek istedim. İşte benim için tipik bir programlı UIButton snippet'i:

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

Düğmem için şeffaf bir png resmi yüklüyorum ve arka plan resmini ayarlıyorum. Çerçeveyi UIImage'a göre ayarlıyorum ve retina için% 50 ölçeklendiriyorum. Tamam, belki yukarıdakileri kabul edersiniz ya da etmezsiniz, ancak hit alanı BÜYÜK yapmak ve kendinizi baş ağrısından kurtarmak istiyorsanız:

Yaptığım şey, görüntüyü photoshop'ta açın ve tuval boyutunu% 120'ye artırın ve kaydedin. Etkili saydam piksellerle görüntüyü daha büyük hale getirdiniz.

Sadece bir yaklaşım.


0

@jlajlar'ın yukarıdaki cevabı iyi ve anlaşılır görünüyordu, ancak Xamarin.iOS ile eşleşmiyor, bu yüzden Xamarin'e dönüştürdüm. Bir Xamarin iOS'ta bir çözüm arıyorsanız, işte burada:

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

Bu yöntemi, UIView veya UIImageView'ı geçersiz kıldığınız sınıfa ekleyebilirsiniz. Bu güzel çalıştı :)


0

Cevapların hiçbiri benim için mükemmel değil, çünkü o düğme üzerinde arka plan resmi ve başlık kullanıyorum. Ayrıca, ekran boyutu değiştikçe düğme yeniden boyutlandırılır.

Bunun yerine, png saydam alanını daha büyük hale getirerek musluk alanını genişletiyorum.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.