UIButton: हिट क्षेत्र को डिफ़ॉल्ट हिट क्षेत्र से बड़ा बनाता है


188

मेरे पास UIButton और इसके हिट क्षेत्र के साथ एक सवाल है। मैं इंटरफ़ेस बिल्डर में जानकारी डार्क बटन का उपयोग कर रहा हूं, लेकिन मुझे पता चल रहा है कि हिट क्षेत्र कुछ लोगों की उंगलियों के लिए पर्याप्त बड़ा नहीं है।

क्या इन्फोटेन ग्राफिक के आकार को बदले बिना प्रोग्राम के बटन या इंटरफ़ेस बिल्डर के हिट क्षेत्र को बढ़ाने का एक तरीका है?


यदि कुछ भी काम नहीं कर रहा है, तो बस एक छवि के बिना एक UIButton जोड़ें और फिर एक ImageView ओवरले डालें!
वरुण

यह पूरी साइट पर सबसे आश्चर्यजनक सरल प्रश्न होगा, जिसके दर्जनों उत्तर हैं। मेरा मतलब है, मैं पूरा जवाब यहां दे सकता हूं: override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { return bounds.insetBy(dx: -20, dy: -20).contains(point) }
फटी

जवाबों:


137

चूंकि मैं एक पृष्ठभूमि छवि का उपयोग कर रहा हूं, इसलिए इनमें से किसी भी समाधान ने मेरे लिए अच्छा काम नहीं किया। यहां एक समाधान है जो कुछ मजेदार उद्देश्य-सी जादू करता है और न्यूनतम कोड के साथ समाधान में एक बूंद प्रदान करता है।

सबसे पहले, 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
किसी श्रेणी में एक विधि को ओवरराइड करना एक बुरा विचार है। क्या होगा अगर कोई अन्य वर्ग भी पॉइंट को ओवरराइड करने की कोशिश करता है: withEvent :? और कॉल [सुपर pointInside: withEvent] UIButton कॉल के संस्करण को कॉल नहीं कर रहा है (यदि यह एक है), लेकिन UIButton के माता-पिता का संस्करण।
मानुषी

@ हॉनस आप सही हैं और एप्पल ऐसा करने की सलाह नहीं देता है। कहा जा रहा है, जब तक यह कोड एक साझा लाइब्रेरी में नहीं है और आपके आवेदन के लिए सिर्फ स्थानीय है, आपको ठीक होना चाहिए।
चेस

हाय चेस, क्या इसके बजाय उप-वर्ग का उपयोग करने का कोई नुकसान है?
सिड

3
आप बहुत अधिक अनावश्यक कार्य कर रहे हैं, आप कोड की दो सरल पंक्तियाँ कर सकते हैं: [[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"arrow.png"] forState:UIControlStateNormal];और बस उस चौड़ाई और ऊँचाई को सेट करें जिसकी आपको आवश्यकता है: `backButton.frame = CGRectMake (5, 28, 45, 30);`
रेस्ट

7
@ अच्छा, वह पृष्ठभूमि छवि का उपयोग कर रहा है, जिसका अर्थ है कि आपका दृष्टिकोण काम नहीं करेगा।
मैटसनसन

77

बस इंटरफ़ेस बिल्डर में छवि बढ़त इनसेट मान सेट करें।


1
मैंने सोचा कि यह मेरे लिए काम नहीं करेगा क्योंकि बैकग्राउंड इमेज इनसेट का जवाब नहीं देती हैं लेकिन मैंने इमेज प्रॉपर्टी को बदल दिया है और फिर अपने टाइटल के लिए नेगेटिव लेफ्ट एज इनसेट सेट किया है और यह इमेज की चौड़ाई के ऊपर वापस आ गया है मेरी छवि।
डेव रॉस

12
यह कोड में भी किया जा सकता है। उदाहरण के लिए,button.imageEdgeInsets = UIEdgeInsetsMake(-10, -10, -10, -10);
बिंग्सबोम

डेव रॉस का जवाब बहुत अच्छा है। जो लोग इसे नहीं देख सकते हैं, उनके लिए -image अपने शीर्षक को स्वयं (जाहिरा तौर पर) के दाईं ओर रखता है, इसलिए आप इसे वापस ले जाने के लिए IB में छोटे क्लिकर पर क्लिक कर सकते हैं (या जैसा कहा गया था)। केवल नकारात्मक मैं देख रहा हूं कि मैं स्ट्रेचेबल छवियों का उपयोग नहीं कर सकता। आईएमओ का भुगतान करने की छोटी कीमत
पॉल ब्रूनो

7
छवि को खींचे बिना यह कैसे करें?
जोनाबी

सामग्री मोड को केंद्र की तरह कुछ और न कि स्केलिंग पर सेट करें।
सैमुअल गुडविन

63

यहां स्विफ्ट में एक्सटेंशन्स का उपयोग करते हुए एक सुरुचिपूर्ण समाधान है। यह Apple के मानव इंटरफ़ेस दिशानिर्देशों ( https://developer.apple.com/ios/human-interface-guidelines/visual-design/layout/ ) के अनुसार सभी यूआईबटन को कम से कम 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
यदि बटन वर्तमान में छिपा हुआ है तो आपको परीक्षण करने की आवश्यकता है। यदि ऐसा है, तो शून्य लौटें।
जोश गफ़नी

धन्यवाद, यह बहुत अच्छा लग रहा है। किसी भी संभावित गिरावट के बारे में पता होना चाहिए?
Crashalot

मुझे यह समाधान पसंद है, मुझे सभी बटन पर अतिरिक्त गुण सेट करने की आवश्यकता नहीं है। धन्यवाद
xtrinch

1
यदि Apple दिशानिर्देश हिट क्षेत्र के लिए इन आयामों की अनुशंसा करते हैं, तो हमें लागू करने में उनकी विफलता को क्यों ठीक करना है? क्या यह बाद के एसडीके में संबोधित किया गया है?
नूडलऑफडैड नीचे

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

उद्देश्य सी

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

1
किसी भी अतिरिक्त श्रेणियों के बिना वास्तव में शांत समाधान धन्यवाद। इसके अलावा, मैं इसे अपने बटनों के साथ एकीकृत करता हूं जो मेरे पास XIB में हैं और यह अच्छा काम करता है। शानदार जवाब
मैट्रसोव अलेक्जेंडर

यह एक महान समाधान है जिसे अन्य सभी नियंत्रणों तक बढ़ाया जा सकता है! मैंने उदाहरण के लिए एक UISearchBar को उप-वर्ग के लिए उपयोगी पाया। आईओएस 2.0 के बाद से पॉइंटइनसाइड विधि हर यूआईवीयूवी पर है। BTW - स्विफ्ट: func pointInside (_ point: CGPoint, withEvent event: UIEvent!) -> बूल।
टॉमी सी।

इसके लिए धन्यवाद! जैसा कि अन्य ने कहा, यह अन्य नियंत्रणों के साथ वास्तव में उपयोगी हो सकता है!
यानची

यह भी खूब रही!! धन्यवाद, आपने मुझे बहुत समय बचाया! :-)
वोज्टा

35

यहां स्विफ्ट 3.0 में चेस का यूआईटूटॉन + एक्सटेंशन्स दिया गया है।


import UIKit

private var pTouchAreaEdgeInsets: UIEdgeInsets = .zero

extension UIButton {

    var touchAreaEdgeInsets: UIEdgeInsets {
        get {
            if let value = objc_getAssociatedObject(self, &pTouchAreaEdgeInsets) as? NSValue {
                var edgeInsets: UIEdgeInsets = .zero
                value.getValue(&edgeInsets)
                return edgeInsets
            }
            else {
                return .zero
            }
        }
        set(newValue) {
            var newValueCopy = newValue
            let objCType = NSValue(uiEdgeInsets: .zero).objCType
            let value = NSValue(&newValueCopy, withObjCType: objCType)
            objc_setAssociatedObject(self, &pTouchAreaEdgeInsets, value, .OBJC_ASSOCIATION_RETAIN)
        }
    }

    open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        if UIEdgeInsetsEqualToEdgeInsets(self.touchAreaEdgeInsets, .zero) || !self.isEnabled || self.isHidden {
            return super.point(inside: point, with: event)
        }

        let relativeFrame = self.bounds
        let hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.touchAreaEdgeInsets)

        return hitFrame.contains(point)
    }
}

इसका उपयोग करने के लिए, आप कर सकते हैं:

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

1
दरअसल, हमें विस्तार करना चाहिए UIControl, नहीं UIButton
वैलेंटाइन शेरजिन

2
और चर pTouchAreaEdgeInsetsहोना चाहिए Int, नहीं UIEdgeInsets। (वास्तव में, यह किसी भी प्रकार का हो सकता है, लेकिन Intसबसे सामान्य और सरल प्रकार है, इसलिए यह इस तरह के निर्माण में आम है)। (इसलिए मैं इस दृष्टिकोण को सामान्य रूप से प्यार करता हूं। धन्यवाद, dcRay!)
वैलेंटाइन शर्लिन

1
मेरे पास टाइटल है एडीजेनेट्स, इमेजएडआईनेट्स, कंटेंटईडगेनसेटसेट। सभी मेरे लिए काम नहीं कर रहे हैं। यह एक्सटेंशन अच्छी तरह से काम कर रहा है। इसे पोस्ट करने के लिए आपका धन्यवाद !! अच्छा काम !!
योगेश पटेल

19

मैं आपकी जानकारी बटन पर केंद्रित कस्टम प्रकार के साथ एक UIButton रखने की सलाह देता हूं। कस्टम बटन को उस आकार का आकार दें जिसमें आप हिट क्षेत्र चाहते हैं। वहां से आपके पास दो विकल्प हैं:

  1. कस्टम बटन के 'टच ऑन हाइलाइट' विकल्प की जाँच करें। सफेद चमक सूचना बटन पर दिखाई देगी, लेकिन ज्यादातर मामलों में उपयोगकर्ता उंगली को कवर करेंगे और वे देखेंगे कि बाहर की चारों ओर चमक है।

  2. सूचना बटन के लिए एक IBOutlet सेट करें और कस्टम बटन के लिए दो IBActions 'टच डाउन' और एक 'टच अप इनसाइड' के लिए। फिर Xcode में टचडाउन इवेंट को इंफो बटन की हाइलाइट की गई प्रॉपर्टी को YES में सेट करें और टचअपिनसाइड इवेंट हाइलाइट की गई प्रॉपर्टी को NO पर सेट करें।


19

backgroundImageअपनी छवि के साथ संपत्ति सेट न करें, संपत्ति सेट करें imageView। इसके अलावा, सुनिश्चित करें कि आपने imageView.contentModeसेट किया है UIViewContentModeCenter


1
अधिक विशेष रूप से, बटन की सामग्री को सेट करें संपत्ति को UIViewContentModeCenter पर सेट करें। फिर आप बटन के फ्रेम को इमेज से बड़ा बना सकते हैं। मेरे लिये कार्य करता है!
अर्लोमेडिया

FYI करें, UIButton UIViewContentMode का सम्मान नहीं करता: stackoverflow.com/a/4503920/361247
Enrico Susatyo

@EnricoSusatyo क्षमा करें, मैंने स्पष्ट किया है कि मैं किस API के बारे में बात कर रहा था। के imageView.contentModeरूप में सेट किया जा सकता है UIContentModeCenter
मौरिज़ियो

यह पूरी तरह से काम करता है, सलाह के लिए धन्यवाद! [[backButton imageView] setContentMode: UIViewContentModeCenter]; [backButton setImage:[UIImage imageNamed:@"image.png"] forState:UIControlStateNormal];
रेस्ट

@ उक्त टिप्पणी मेरे लिए उपर्युक्त टिप्पणी काम नहीं करती है: / छवि बड़े सेट फ्रेम आकार का आकार बदल रही है।
अनिल

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

प्रस्तुत उत्तरों में कुछ भी गलत नहीं है; हालाँकि मैं jlarjlar के उत्तर का विस्तार करना चाहता था क्योंकि यह अद्भुत क्षमता रखता है जो अन्य नियंत्रणों (जैसे सर्चबेल) के साथ एक ही समस्या के लिए मूल्य जोड़ सकता है। इसका कारण यह है कि चूंकि पॉइंटइनसाइड एक UIView से जुड़ा हुआ है, इसलिए कोई भी स्पर्श क्षेत्र को बेहतर बनाने के लिए किसी भी नियंत्रण को उप-वर्ग में सक्षम कर सकता है । यह उत्तर पूर्ण समाधान को लागू करने के तरीके का एक पूर्ण नमूना भी दिखाता है।

अपने बटन (या किसी भी नियंत्रण) के लिए एक नया उपवर्ग बनाएँ

#import <UIKit/UIKit.h>

@interface MNGButton : UIButton

@end

अगला आपके उपवर्ग कार्यान्वयन में पॉइंटइनसाइड विधि को ओवरराइड करता है

@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 से लिंक करें और उप-क्षेत्र में परिभाषित क्षेत्र को फिट करने के लिए आपके स्पर्श क्षेत्र का विस्तार किया जाएगा।

CGRectInset और CGRectContainsPoint विधियों के साथ पॉइंटइनसाइड विधि को ओवरराइड करने के अलावा , किसी भी UIView उप-वर्ग के आयताकार स्पर्श क्षेत्र को विस्तारित करने के लिए CGGeometry की जांच करने के लिए समय निकालना चाहिए । तुम भी पर CGGeometry उपयोग-मामले पर कुछ अच्छा सुझाव के मिल सकता है NSHipster

उदाहरण के लिए, ऊपर बताए गए तरीकों का उपयोग करके कोई भी स्पर्श क्षेत्र को अनियमित बना सकता है या केवल क्षैतिज स्पर्श पथ के रूप में चौड़ाई स्पर्श क्षेत्र को दो बार बड़ा करने के लिए चुन सकता है:

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

एनबी: किसी भी यूआई वर्ग नियंत्रण को अलग-अलग नियंत्रणों (या किसी भी यूआईवाईयूवी उपक्लास, जैसे कि यूआईआईएमजेड्यूवी, आदि) के लिए स्पर्श क्षेत्र का विस्तार करने पर समान परिणाम उत्पन्न करना चाहिए।


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 का उपयोग करना होगा जो आपकी छवि को बटन के नए फ्रेम में स्केल करेगा।
मिहाई डेमियन

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 है, तो नकारात्मक तरीकों की संख्या अधिक होने पर इस पद्धति को कॉल नहीं किया जाएगा - जैसे किसी और का उल्लेख किया गया है। अन्यथा यह काम करता है।
जेम्स ओ ब्रायन

यह मेरे लिए काम नहीं करता है। हिटफ्रैम की गणना ठीक से दिखाई देती है, लेकिन पॉइंट इनसाइड: कभी नहीं कहा जाता है जब बिंदु स्व.बाउंड्स के बाहर हो। अगर (CGRectContainsPoint (hitFrame, point) और&! CGRectContainsPoint (self.bounds, point)) {NSLog (@ "Success!"); // कभी नहीं बुलाया}
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वहाँ एक प्रकार के रूप में सीधे उपलब्ध नहीं है , लेकिन CGRectयह भी चार के साथ एक संरचना के होते हैं CGFloat"दोष" का चयन करके और इस तरह से मूल्यों में भरने से यह त्रुटिपूर्ण काम करता है {{top, left}, {bottom, right}}:।


6

मैं स्विफ्ट में निम्न वर्ग का उपयोग कर रहा हूं, मार्जिन को समायोजित करने के लिए एक इंटरफ़ेस बिल्डर संपत्ति को सक्षम करने के लिए:

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

इस बिंदु को मैन्युअल रूप से कैसे बदलें? अगर मैंने पुराने मार्जिन के साथ एक यूआईब्यूटॉन बनाया है, लेकिन अब मैं इसे बदलना चाहता हूं?
टॉमसॉयर

5

ठीक है, आप अपने यूआईबटन को एक पारदर्शी और थोड़े बड़े यूआईवीयूवी के अंदर रख सकते हैं, और फिर यूआईबीवाईई इंस्टीट्यूट पर टच इवेंट्स को पकड़ सकते हैं। इस तरह, आपके पास अभी भी आपका बटन होगा, लेकिन एक बड़े स्पर्श क्षेत्र के साथ। उपयोगकर्ता द्वारा बटन के बजाय दृश्य को छूने पर आपको मैन्युअल रूप से चयनित और हाइलाइट किए गए राज्यों से निपटना होगा।

अन्य संभावना में UIButton के बजाय UIImage का उपयोग करना शामिल है।


5

मैं प्रोग्राम बटन के हिट क्षेत्र को प्रोग्रामेटिक रूप से बढ़ाने में सक्षम हूं। "I" ग्राफिक स्केल नहीं बदलता है और नए बटन फ्रेम में केंद्रित रहता है।

इंटरफ़ेस बिल्डर में जानकारी बटन का आकार 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

यह मेरा स्विफ्ट 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

चेस के कस्टम हिट टेस्ट को यूआईबुट्टन के उपवर्ग के रूप में लागू किया गया। ऑब्जेक्टिव-सी में लिखा है।

यह निर्माणकर्ता 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।

स्विफ्ट 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 px से छोटा है (जिसे न्यूनतम टैप करने योग्य क्षेत्र के रूप में अनुशंसित किया गया है) इसका उपयोग करें:

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

या आप अपने यूआईवीवाई या यूआईब्यूटॉन को उप-वर्ग कर सकते हैं minimumHitTestWidthऔर / और / या सेट कर सकते हैं minimumHitTestHeight। आपका बटन हिट-परीक्षण क्षेत्र तब इन 2 मूल्यों द्वारा दर्शाया जाएगा।

अन्य समाधानों की तरह, यह - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)eventविधि का उपयोग करता है । जब iOS हिट-परीक्षण करता है, तो विधि को कहा जाता है। इस ब्लॉग पोस्ट में आईओएस हिट-टेस्टिंग कैसे काम करती है, इसका अच्छा वर्णन है।

https://github.com/kgaidis/KGHitTestingViews

@interface KGHitTestingButton : UIButton <KGHitTesting>

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

@end

आप किसी भी कोड को लिखे बिना इंटरफ़ेस बिल्डर को केवल उपवर्ग और उपयोग कर सकते हैं: यहां छवि विवरण दर्ज करें


1
मैंने गति के लिए इसे स्थापित करना समाप्त कर दिया। IBInspectable को जोड़ना इसको एक साथ रखने के लिए एक महान विचार था।
user3344977

2

ऊपर गीसेट के उत्तर के आधार पर (जो मुझे सबसे सुरुचिपूर्ण समाधान मिला), यहाँ स्विफ्ट 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 में @ कैच समाधान का पोर्ट किया था

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 के लिए प्रारूपित

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

मैंने चेस की प्रतिक्रिया का पालन किया है और यह बहुत अच्छा काम करता है, एक एकल समस्या जब आप arrea को बहुत बड़ा बनाते हैं, तो उस क्षेत्र से बड़ा, जहां बटन को हटा दिया जाता है (यदि क्षेत्र बड़ा नहीं था) तो यह UIControlElTouchUpInside घटना के लिए चयनकर्ता को कॉल नहीं करता है ।

मुझे लगता है कि आकार किसी भी दिशा में 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];

मैं अपने बटन के लिए एक पारदर्शी पीएनजी इमेज लोड कर रहा हूं और बैकग्राउंड इमेज सेट कर रहा हूं। मैं UIImage पर आधारित फ्रेम सेट कर रहा हूं और रेटिना के लिए 50% तक स्केलिंग कर रहा हूं। ठीक है, हो सकता है कि आप ऊपर के साथ सहमत हों या न हों, लेकिन अगर आप हिट क्षेत्र को बड़ा बनाना चाहते हैं और खुद को सिरदर्द से बचा सकते हैं:

मैं क्या करता हूं, फ़ोटोशॉप में छवि खोलें और बस कैनवास का आकार बढ़ाकर 120% करें और बचत करें। प्रभावी रूप से आपने सिर्फ पारदर्शी पिक्सेल के साथ छवि को बड़ा किया है।

बस एक दृष्टिकोण।


0

@jajajlar का उत्तर ऊपर अच्छा और सीधा लग रहा था, लेकिन 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 पारदर्शी क्षेत्र को बड़ा बनाकर नल क्षेत्र को बड़ा करता हूं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.