मैं पारदर्शी UIView के पीछे एक बटन कैसे क्लिक कर सकता हूं?


177

मान लीजिए कि हमारे पास एक उप दृश्य के साथ एक दृश्य नियंत्रक है। सबव्यू स्क्रीन के केंद्र को 100 पीएक्स मार्जिन के साथ सभी तरफ ले जाता है। फिर हम उस सबव्यू के अंदर क्लिक करने के लिए थोड़ा सामान का एक गुच्छा जोड़ते हैं। हम केवल नए फ्रेम (x = 0, y = 0) का लाभ लेने के लिए सबव्यू का उपयोग कर रहे हैं, सबवू के अंदर वास्तव में मूल दृश्य में 100,100 है)।

फिर, कल्पना करें कि हमारे पास मेनू के समान सबव्यू के पीछे कुछ है। मैं चाहता हूं कि उपयोगकर्ता सबव्यू में "छोटे सामान" में से किसी एक का चयन करने में सक्षम हो, लेकिन अगर वहां कुछ भी नहीं है, तो मैं चाहता हूं कि इसके माध्यम से गुजरने के लिए टच (इसके बाद से पृष्ठभूमि स्पष्ट है)।

मैं यह कैसे कर सकता हूँ? ऐसा लगता है कि टचबेगन से गुजरता है, लेकिन बटन काम नहीं करते हैं।


1
मैंने सोचा कि पारदर्शी (अल्फा 0) UIView को घटनाओं को छूने के लिए प्रतिक्रिया देने की आवश्यकता नहीं है?
एवडेन वू

1
मैंने उसके लिए एक छोटी कक्षा लिखी है। (उत्तरों में एक उदाहरण जोड़ा गया)। वहाँ का समाधान स्वीकृत उत्तर की तुलना में कुछ बेहतर है क्योंकि आप अभी भी उस पर क्लिक कर सकते हैं UIButtonजो एक अर्ध पारदर्शी के तहत है UIViewजबकि गैर पारदर्शी भाग UIViewअभी भी स्पर्श घटनाओं पर प्रतिक्रिया देगा।
सेगेव

जवाबों:


315

अपने कंटेनर के लिए एक कस्टम दृश्य बनाएं और बिंदु को ओवरराइड करें: जब कोई योग्य बच्चा दृश्य के भीतर न हो, तो झूठी वापसी करने के लिए संदेश: जैसे:

स्विफ्ट:

class PassThroughView: UIView {
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        for subview in subviews {
            if !subview.isHidden && subview.isUserInteractionEnabled && subview.point(inside: convert(point, to: subview), with: event) {
                return true
            }
        }
        return false
    }
}

उद्देश्य सी:

@interface PassthroughView : UIView
@end

@implementation PassthroughView
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    for (UIView *view in self.subviews) {
        if (!view.hidden && view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event])
            return YES;
    }
    return NO;
}
@end

एक कंटेनर के रूप में इस दृश्य का उपयोग करने से उसके किसी भी बच्चे को स्पर्श प्राप्त करने की अनुमति मिलेगी, लेकिन दृश्य स्वयं घटनाओं के लिए पारदर्शी होगा।


4
दिलचस्प। मुझे उत्तरदाता श्रृंखला में अधिक गोता लगाने की आवश्यकता है।
सीन क्लार्क हेस

1
आपको यह देखने की आवश्यकता है कि क्या दृश्य दिखाई दे रहा है या नहीं, तो आपको यह भी जांचने की आवश्यकता है कि क्या परीक्षण के पहले उप-दृश्य दिखाई दे रहे हैं।
आलसी कोडर

2
दुर्भाग्य से यह ट्रिक tabBarController के लिए काम नहीं करती है, जिसके लिए दृश्य को बदला नहीं जा सकता है। किसी को इस विचार को घटनाओं के प्रति भी पारदर्शी बनाने का विचार है?
साइरिलचम्पियर

1
आपको दृश्य के अल्फ़ा की भी जाँच करनी चाहिए। काफी बार मैं अल्फा को शून्य पर सेट करके एक दृश्य छिपाऊंगा। शून्य अल्फा के साथ एक दृश्य को छिपे हुए दृश्य की तरह कार्य करना चाहिए।
जेम्स एंड्रयूज

2
मैंने एक स्विफ्ट संस्करण किया: शायद आप इसे दृश्यता के लिए उत्तर में शामिल कर सकते हैं। gith.github.com/eyeballz/17945454447c7ae766cb
eyeballz

107

मैं भी इस्तेमाल करता हूं

myView.userInteractionEnabled = NO;

उपवर्ग की जरूरत नहीं है। ठीक काम करता है।


76
यह किसी भी
साक्षात्कार के

जैसा कि @pixelfreak ने उल्लेख किया है, यह सभी मामलों के लिए आदर्श समाधान नहीं है। ऐसे मामले जहां उस दृश्य के साक्षात्कार पर बातचीत अभी भी वांछित है, उस ध्वज को सक्षम बने रहने की आवश्यकता है।
अगस्तो कार्मो

20

Apple से:

इवेंट फ़ॉरवर्डिंग कुछ अनुप्रयोगों द्वारा उपयोग की जाने वाली तकनीक है। आप किसी अन्य प्रत्युत्तर ऑब्जेक्ट के ईवेंट-हैंडलिंग तरीकों को लागू करके स्पर्श घटनाओं को अग्रेषित करते हैं। यद्यपि यह एक प्रभावी तकनीक हो सकती है, आपको सावधानी के साथ इसका उपयोग करना चाहिए। UIKit फ्रेमवर्क की कक्षाओं को उन स्पर्शों को प्राप्त करने के लिए डिज़ाइन नहीं किया गया है जो उनके लिए बाध्य नहीं हैं .... यदि आप अपने आवेदन में अन्य उत्तरदाताओं को सशर्त रूप से अग्रेषित करना चाहते हैं, तो इन सभी उत्तरदाताओं को UIView के आपके अपने उपवर्गों के उदाहरण होने चाहिए।

सेब सबसे अच्छा अभ्यास :

स्पॉनडर श्रृंखला (नेक्स्टप्रोंडर के माध्यम से) को स्पष्ट रूप से घटनाओं को न भेजें; इसके बजाय, सुपरक्लास कार्यान्वयन लागू करें और UIKit को रिस्पोंडर-चेन ट्रैवर्सल को संभालने दें।

इसके बजाय आप ओवरराइड कर सकते हैं:

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event

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


1
यह उद्धरण आपके द्वारा उद्धृत किए जा रहे डॉक्स के लिए लिंक के साथ-साथ स्वयं के लिए भी भयानक होगा।
मोर्गनकोड्स 20

1
मैंने आपके लिए प्रलेखन में संबंधित स्थानों के लिंक जोड़ दिए हैं
जैकस्लैश

1
~~ क्या आप दिखा सकते हैं कि उस ओवरराइड को कैसा दिखना होगा? ~~ एक और सवाल का जवाब मिला कि यह कैसा दिखेगा; यह सचमुच लौट रहा है NO;
कंटूर 9'15

यह संभवतः स्वीकृत उत्तर होना चाहिए। यह सबसे सरल तरीका है और अनुशंसित तरीका है।
इक्वाडोर

8

एक बहुत आसान तरीका इंटरफ़ेस बिल्डर में सक्षम "उपयोगकर्ता-सहभागिता" को "अन-चेक" करना है। "यदि आप एक स्टोरीबोर्ड का उपयोग कर रहे हैं"

यहां छवि विवरण दर्ज करें


6
जैसा कि अन्य लोगों ने एक समान उत्तर में बताया है, यह इस मामले में मदद नहीं करता है, क्योंकि यह अंतर्निहित दृश्य में स्पर्श घटनाओं को भी अक्षम कर रहा है। दूसरे शब्दों में: इस दृश्य के नीचे का बटन टैप नहीं किया जा सकता है, भले ही आप इस सेटिंग को एन या अक्षम कर दें।
Auco

6

जॉन ने जो पोस्ट किया, उस पर बिल्डिंग, यहां एक उदाहरण है जो स्पर्श घटनाओं को बटन के अलावा किसी दृश्य के सभी साक्षात्कारों से गुजरने की अनुमति देगा:

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    // Allow buttons to receive press events.  All other views will get ignored
    for( id foundView in self.subviews )
    {
        if( [foundView isKindOfClass:[UIButton class]] )
        {
            UIButton *foundButton = foundView;

            if( foundButton.isEnabled  &&  !foundButton.hidden &&  [foundButton pointInside:[self convertPoint:point toView:foundButton] withEvent:event] )
                return YES;
        }        
    }
    return NO;
}

यह देखने के लिए कि क्या दृश्य दिखाई दे रहा है, और यदि उप-दृश्य दिखाई दे रहे हैं, तो जाँच करने की आवश्यकता है।
आलसी कोडर

सही समाधान! एक और भी सरल तरीका होगा एक UIViewउपवर्ग का निर्माण करना जो PassThroughViewकि -(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { return YES; }विचारों को कम करने के लिए किसी भी स्पर्श की घटनाओं को आगे बढ़ाना चाहता हो।
auco

6

हाल ही में मैंने एक वर्ग लिखा है जो मुझे बस उसी के साथ मदद करेगा। यह एक कस्टम वर्ग के रूप में UIButtonया के लिए UIViewएक पारदर्शी पिक्सेल पर निष्पादित की गई स्पर्श घटनाओं को पारित करेगा।

यह समाधान स्वीकृत उत्तर की तुलना में कुछ हद तक बेहतर है क्योंकि आप अभी भी UIButtonएक अर्ध पारदर्शी के नीचे क्लिक कर सकते हैं UIViewजबकि गैर पारदर्शी भाग UIViewअभी भी स्पर्श घटनाओं पर प्रतिक्रिया देगा।

GIF

जैसा कि आप जीआईएफ में देख सकते हैं, जिराफ बटन एक साधारण आयत है, लेकिन पारदर्शी क्षेत्रों पर स्पर्श की घटनाओं को पीले रंग के UIButtonनीचे से पारित किया जाता है।

कक्षा से लिंक करें


1
हालांकि यह समाधान काम कर सकता है, उत्तर के भीतर अपने समाधान के प्रासंगिक हिस्सों को रखना बेहतर है।
कोएन।

4

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

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    UIView *view = [super hitTest:point withEvent:event];
    if (view == self) {
        return nil; //avoid delivering touch events to the container view (self)
    }
    else{
        return view; //the subviews will still receive touch events
    }
}

3

स्विफ्ट 3

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    for subview in subviews {
        if subview.frame.contains(point) {
            return true
        }
    }
    return false
}

2

'IPhone अनुप्रयोग प्रोग्रामिंग गाइड' के अनुसार:

स्पर्श घटनाओं के वितरण को बंद करना। डिफ़ॉल्ट रूप से, एक दृश्य स्पर्श ईवेंट प्राप्त करता है, लेकिन आप ईवेंट के वितरण को बंद करने के लिए अपनी उपयोगकर्ताअनुरोधी संपत्ति को NO पर सेट कर सकते हैं। एक दृश्य भी घटनाओं को प्राप्त नहीं करता है यदि यह छिपा हुआ है या यदि यह पारदर्शी है

http://developer.apple.com/iphone/library/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/EventHandling/EventHandling.html

अपडेट किया गया : हटाए गए उदाहरण - प्रश्न को फिर से पढ़ें ...

क्या आपके पास उन विचारों पर कोई इशारा प्रक्रिया है जो बटन को प्राप्त करने से पहले नल को संसाधित कर सकते हैं? क्या बटन काम करता है जब आपके पास उस पर पारदर्शी दृश्य नहीं होता है?

गैर-काम कोड के किसी भी कोड नमूने?


हां, मैंने अपने प्रश्न में लिखा था कि सामान्य स्पर्श विचारों के नीचे काम करता है, लेकिन यूब्यूटॉन और अन्य तत्व काम नहीं करते हैं।
सीन क्लार्क हेस

1

जहां तक ​​मुझे पता है, आप हिटस्टेस्ट: विधि को पछाड़कर ऐसा करने में सक्षम हैं । मैंने इसे करने की कोशिश की, लेकिन इसे ठीक से काम करने के लिए नहीं मिला।

अंत में मैंने टाउचेबल ऑब्जेक्ट के चारों ओर पारदर्शी विचारों की एक श्रृंखला बनाई ताकि वे इसे कवर न करें। मेरे मुद्दे के लिए एक हैक के बिट यह ठीक काम किया।


1

अन्य उत्तरों से सुझाव लेते हुए और Apple के दस्तावेज़ीकरण को पढ़ते हुए, मैंने आपकी समस्या को हल करने के लिए यह सरल पुस्तकालय बनाया:
https://github.com/natrosoft/NATouchThroughView
इससे इंटरफ़ेस बिल्डर में विचार आकर्षित करना आसान हो जाता है जिसे स्पर्श से गुजरना चाहिए एक अंतर्निहित दृश्य।

मुझे लगता है कि विधि स्विज़लिंग ओवरकिल है और उत्पादन कोड में करने के लिए बहुत खतरनाक है क्योंकि आप सीधे ऐप्पल के आधार कार्यान्वयन के साथ खिलवाड़ कर रहे हैं और एक आवेदन-व्यापक परिवर्तन कर रहे हैं जो अनपेक्षित परिणाम पैदा कर सकता है।

एक डेमो प्रोजेक्ट है और उम्मीद है कि README एक अच्छा काम करता है जिसमें बताया गया है कि क्या करना है। ओपी को संबोधित करने के लिए, आप स्पष्ट यूआईवाईईवाई को बदल देंगे जिसमें इंटरफ़ेस बिल्डर में NATouchThroughView वर्ग के बटन शामिल हैं। फिर स्पष्ट UIView ढूंढें जो उस मेनू को ओवरले करता है जिसे आप टैप-सक्षम करना चाहते हैं। इंटरफ़ेस बिल्डर में NARootTouchTroughView वर्ग के लिए UIView बदलें। यहां तक ​​कि यह आपके व्यू कंट्रोलर का रूट यूआईयूवी भी हो सकता है यदि आप उन टच को अंतर्निहित व्यू कंट्रोलर से गुजरने का इरादा रखते हैं। यह कैसे काम करता है यह देखने के लिए डेमो प्रोजेक्ट देखें। यह वास्तव में काफी सरल, सुरक्षित और गैर-आक्रामक है


1

मैंने ऐसा करने के लिए एक श्रेणी बनाई।

एक छोटा सा तरीका है और swizzling विधि सुनहरा है।

हेडर

//UIView+PassthroughParent.h
@interface UIView (PassthroughParent)

- (BOOL) passthroughParent;
- (void) setPassthroughParent:(BOOL) passthroughParent;

@end

कार्यान्वयन फ़ाइल

#import "UIView+PassthroughParent.h"

@implementation UIView (PassthroughParent)

+ (void)load{
    Swizz([UIView class], @selector(pointInside:withEvent:), @selector(passthroughPointInside:withEvent:));
}

- (BOOL)passthroughParent{
    NSNumber *passthrough = [self propertyValueForKey:@"passthroughParent"];
    if (passthrough) return passthrough.boolValue;
    return NO;
}
- (void)setPassthroughParent:(BOOL)passthroughParent{
    [self setPropertyValue:[NSNumber numberWithBool:passthroughParent] forKey:@"passthroughParent"];
}

- (BOOL)passthroughPointInside:(CGPoint)point withEvent:(UIEvent *)event{
    // Allow buttons to receive press events.  All other views will get ignored
    if (self.passthroughParent){
        if (self.alpha != 0 && !self.isHidden){
            for( id foundView in self.subviews )
            {
                if ([foundView alpha] != 0 && ![foundView isHidden] && [foundView pointInside:[self convertPoint:point toView:foundView] withEvent:event])
                    return YES;
            }
        }
        return NO;
    }
    else {
        return [self passthroughPointInside:point withEvent:event];// Swizzled
    }
}

@end

आपको मेरा Swizz.h और Swizz.m जोड़ना होगा

यहाँ स्थित है

उसके बाद, आप केवल अपने {प्रोजेक्ट} -Prefix.pch फ़ाइल में UIView + PassthroughParent.h आयात करें, और हर दृश्य में यह क्षमता होगी।

हर दृश्य अंक ले जाएगा, लेकिन रिक्त स्थान में से कोई भी नहीं करेगा।

मैं एक स्पष्ट पृष्ठभूमि का उपयोग करने की भी सलाह देता हूं।

myView.passthroughParent = YES;
myView.backgroundColor = [UIColor clearColor];

संपादित करें

मैंने अपना खुद का प्रॉपर्टी बैग बनाया, और वह पहले शामिल नहीं था।

शीर्ष लेख फ़ाइल

// NSObject+PropertyBag.h

#import <Foundation/Foundation.h>

@interface NSObject (PropertyBag)

- (id) propertyValueForKey:(NSString*) key;
- (void) setPropertyValue:(id) value forKey:(NSString*) key;

@end

कार्यान्वयन फ़ाइल

// NSObject+PropertyBag.m

#import "NSObject+PropertyBag.h"



@implementation NSObject (PropertyBag)

+ (void) load{
    [self loadPropertyBag];
}

+ (void) loadPropertyBag{
    @autoreleasepool {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Swizz([NSObject class], NSSelectorFromString(@"dealloc"), @selector(propertyBagDealloc));
        });
    }
}

__strong NSMutableDictionary *_propertyBagHolder; // Properties for every class will go in this property bag
- (id) propertyValueForKey:(NSString*) key{
    return [[self propertyBag] valueForKey:key];
}
- (void) setPropertyValue:(id) value forKey:(NSString*) key{
    [[self propertyBag] setValue:value forKey:key];
}
- (NSMutableDictionary*) propertyBag{
    if (_propertyBagHolder == nil) _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100];
    NSMutableDictionary *propBag = [_propertyBagHolder valueForKey:[[NSString alloc] initWithFormat:@"%p",self]];
    if (propBag == nil){
        propBag = [NSMutableDictionary dictionary];
        [self setPropertyBag:propBag];
    }
    return propBag;
}
- (void) setPropertyBag:(NSDictionary*) propertyBag{
    if (_propertyBagHolder == nil) _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100];
    [_propertyBagHolder setValue:propertyBag forKey:[[NSString alloc] initWithFormat:@"%p",self]];
}

- (void)propertyBagDealloc{
    [self setPropertyBag:nil];
    [self propertyBagDealloc];//Swizzled
}

@end

passthroughPointInside मेथड मेरे लिए अच्छी तरह से काम कर रहा है (यहां तक ​​कि किसी भी pststhroughparent या swizz सामान का उपयोग किए बिना - बस नाम बदलने के लिए passthroughPointInside to pointInside), बहुत बहुत धन्यवाद।
बेंजामिन पीट्टे

प्रॉपर्टीवेल्यूफ़ोरके क्या है?
स्कोच

1
हम्म। इससे पहले किसी ने इशारा नहीं किया। मैंने एक कस्टम पॉइंटर शब्दकोश बनाया, जो एक क्लास एक्सटेंशन में गुण रखता है। बीमार देखें कि क्या मुझे इसे यहां शामिल करने के लिए मिल सकता है।
आलसी कोडर

स्विज़लिंग विधियों को दुर्लभ मामलों और डेवलपर मज़ा के लिए एक नाजुक हैकिंग समाधान के रूप में जाना जाता है। यह निश्चित रूप से ऐप स्टोर सुरक्षित नहीं है क्योंकि यह अगले ओएस अपडेट के साथ आपके ऐप को तोड़ने और क्रैश करने की काफी संभावना है। सिर्फ ओवरराइड क्यों नहीं pointInside: withEvent:?
Auco

0

यदि आप किसी श्रेणी या उपवर्ग का उपयोग करने के लिए परेशान नहीं कर सकते हैं, तो आप केवल बटन को आगे ला सकते हैं ताकि यह पारदर्शी दृश्य के सामने हो। आपके आवेदन के आधार पर यह हमेशा संभव नहीं होगा, लेकिन इसने मेरे लिए काम किया। आप हमेशा बटन को फिर से वापस ला सकते हैं या इसे छिपा सकते हैं।


2
नमस्ते, अतिप्रवाह ढेर करने के लिए आपका स्वागत है। एक नए उपयोगकर्ता के रूप में आपके लिए बस एक संकेत: आप अपने स्वर से सावधान रहना चाहते हैं। मैं "अगर आप परेशान नहीं कर सकते हैं" जैसे वाक्यांशों से बचेंगे; यह कृपालु के रूप में प्रकट हो सकता है
नाममात्र

सिर के लिए धन्यवाद .. यह कृपालु की तुलना में एक आलसी दृष्टिकोण से अधिक था, लेकिन बिंदु लिया गया।
शाकेद सायग

0

इसे इस्तेमाल करे

class PassthroughToWindowView: UIView {
        override func test(_ point: CGPoint, with event: UIEvent?) -> UIView? {
            var view = super.hitTest(point, with: event)
            if view != self {
                return view
            }

            while !(view is PassthroughWindow) {
                view = view?.superview
            }
            return view
        }
    } 

0

backgroundColorअपने के transparentViewरूप में सेट की कोशिश करो UIColor(white:0.000, alpha:0.020)। तब आप touchesBegan/ के touchesMovedतरीकों में टच इवेंट प्राप्त कर सकते हैं । कोड को अपने दृष्टिकोण के नीचे रखें:

self.alpha = 1
self.backgroundColor = UIColor(white: 0.0, alpha: 0.02)
self.isMultipleTouchEnabled = true
self.isUserInteractionEnabled = true

0

मैं ओवरराइड विधि बिंदु के बजाय इसका उपयोग करता हूं (अंदर: CGPoint, साथ: UIEvent)

  override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        guard self.point(inside: point, with: event) else { return nil }
        return self
    }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.