हिटवेस्ट का उपयोग करके अपने सुपरवाइवे के फ्रेम के बाहर एक सबव्यू पर टचिंग कैप्चर करें: withEvent:


94

मेरी समस्या: मेरे पास एक पर्यवेक्षक है EditViewजो मूल रूप से पूरे एप्लिकेशन फ़्रेम को MenuViewलेता है , और एक सबव्यू जो केवल नीचे ~ 20% तक होता है, और फिर MenuViewइसका अपना सबव्यू होता है ButtonViewजो वास्तव में MenuViewसीमा के बाहर रहता है (ऐसा कुछ:) ButtonView.frame.origin.y = -100

(ध्यान दें: EditViewअन्य साक्षात्कार हैं जो MenuViewपदानुक्रम के दृश्य का हिस्सा नहीं हैं , लेकिन उत्तर को प्रभावित कर सकते हैं।)

आप शायद पहले से ही इस मुद्दे को पता है: जब ButtonViewकी सीमा के भीतर है MenuView(या, और अधिक विशेष रूप से, जब मेरे छूता भीतर कर रहे हैं MenuViewकी सीमा), ButtonViewप्रतिक्रिया की घटनाओं को छूने के लिए। जब मेरा स्पर्श MenuViewसीमा के बाहर होता है (लेकिन फिर भी ButtonViewसीमा के भीतर ), कोई स्पर्श घटना प्राप्त नहीं होती है ButtonView

उदाहरण:

  • (() EditViewसभी विचारों का जनक है
  • (M) MenuView, EditView का एक उप-भाग है
  • (बी) ButtonViewMenuView का एक उपक्षेत्र है

चित्र:

+------------------------------+
|E                             |
|                              |
|                              |
|                              |
|                              |
|+-----+                       |
||B    |                       |
|+-----+                       |
|+----------------------------+|
||M                           ||
||                            ||
|+----------------------------+|
+------------------------------+

क्योंकि (B) बाहरी (M) फ्रेम है, (B) क्षेत्र में एक टैप कभी भी (M) को नहीं भेजा जाएगा - वास्तव में, (M) इस मामले में कभी भी स्पर्श का विश्लेषण नहीं करता है, और स्पर्श को भेजा जाता है पदानुक्रम में अगली वस्तु।

लक्ष्य: मैं इकट्ठा करता हूं कि ओवरराइडिंग hitTest:withEvent:इस समस्या को हल कर सकती है, लेकिन मुझे समझ में नहीं आता कि कैसे। मेरे मामले में, (मेरे 'मास्टर' पर्यवेक्षण) को hitTest:withEvent:ओवरराइड किया जाना चाहिए EditView? या इसे ओवरराइड किया जाना चाहिए MenuView, बटन का प्रत्यक्ष पर्यवेक्षण जो स्पर्श नहीं प्राप्त कर रहा है? या मैं इस बारे में गलत सोच रहा हूं?

यदि इसके लिए एक लंबी व्याख्या की आवश्यकता है, तो एक अच्छा ऑनलाइन संसाधन मददगार होगा - सिवाय Apple के UIView डॉक्स के, जिन्होंने मुझे यह स्पष्ट नहीं किया है।

धन्यवाद!

जवाबों:


145

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

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

    if (self.clipsToBounds) {
        return nil;
    }

    if (self.hidden) {
        return nil;
    }

    if (self.alpha == 0) {
        return nil;
    }

    for (UIView *subview in self.subviews.reverseObjectEnumerator) {
        CGPoint subPoint = [subview convertPoint:point fromView:self];
        UIView *result = [subview hitTest:subPoint withEvent:event];

        if (result) {
            return result;
        }
    }

    return nil;
}

स्विफ्ट 3

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {

    if clipsToBounds || isHidden || alpha == 0 {
        return nil
    }

    for subview in subviews.reversed() {
        let subPoint = subview.convert(point, from: self)
        if let result = subview.hitTest(subPoint, with: event) {
            return result
        }
    }

    return nil
}

मुझे उम्मीद है कि यह किसी को भी अधिक जटिल उपयोग मामलों के लिए इस समाधान का उपयोग करने की कोशिश कर रहा है।


यह अच्छा लग रहा है और इसे करने के लिए धन्यवाद। हालांकि, एक सवाल था: आप क्यों वापसी कर रहे हैं [सुपर हिटेस्ट: पॉइंट विथ एवेंट: इवेंट]; ? यदि टच को ट्रिगर करने वाला कोई सबव्यू नहीं है तो क्या आप शून्य नहीं लौटाएंगे? Apple का कहना है कि अगर कोई सबव्यू टच नहीं है तो हिट रिटर्न मिलता है।
सेर पॉज़

हम्म ... हाँ, यह सही के बारे में लगता है। साथ ही, सही व्यवहार के लिए, वस्तुओं को उल्टे क्रम में पुनरावृत्त करना होगा (क्योंकि अंतिम एक सबसे ऊपरी रूप से एक है)। मिलान करने के लिए संपादित कोड।
नोम

3
मैंने आपके समाधान का उपयोग केवल यूआईबूटन को छूने पर कब्जा करने के लिए किया है, और यह एक यूआईवीयूवी के अंदर है जो कि यूआईसीओलेक्टियन व्यूसेल के अंदर है (जाहिर है) एक यूआईसीओलेक्टियन व्यू। मुझे हिट को ओवरराइड करने के लिए UICollectionView और UICollectionViewCell को उप-वर्ग में लाना पड़ा: withEvent: इस तीन वर्गों पर। और यह आकर्षण की तरह काम करता है !! धन्यवाद !!
डैनियल गार्सिया

3
वांछित उपयोग के आधार पर, इसे या तो वापस आना चाहिए [सुपर हिटटेस्ट: पॉइंट विथ एवेंट: इवेंट] या नील। स्वयं लौटने से उसे सब कुछ प्राप्त होगा।
Noam

1
इस तकनीक पर Apple का एक क्यू एंड ए तकनीकी दस्तावेज है: developer.apple.com/library/ios/qa/qa2013/qa1812.html
जेम्स कुआंग

33

ठीक है, मैंने कुछ खुदाई और परीक्षण किया, यहां बताया गया है कि कैसे hitTest:withEventकाम करता है - कम से कम उच्च स्तर पर। इस परिदृश्य को देखें:

  • (ई) एडिट व्यू है , जो सभी विचारों का जनक है
  • (एम) MenuView, EditView का एक उपक्षेत्र है
  • (बी) ButtonView , MenuView का एक उपक्षेत्र है

चित्र:

+------------------------------+
|E                             |
|                              |
|                              |
|                              |
|                              |
|+-----+                       |
||B    |                       |
|+-----+                       |
|+----------------------------+|
||M                           ||
||                            ||
|+----------------------------+|
+------------------------------+

क्योंकि (B) बाहरी (M) फ्रेम है, (B) क्षेत्र में एक टैप कभी भी (M) को नहीं भेजा जाएगा - वास्तव में, (M) इस मामले में कभी भी स्पर्श का विश्लेषण नहीं करता है, और स्पर्श को भेजा जाता है पदानुक्रम में अगली वस्तु।

हालांकि, यदि आप hitTest:withEvent:(एम) में लागू करते हैं, तो आवेदन में कहीं भी टैप करने पर (एम) (या यह कम से कम यह उनके बारे में जानता है) को भेजा जाएगा। आप उस स्थिति में स्पर्श को संभालने के लिए कोड लिख सकते हैं और उस वस्तु को वापस कर सकते हैं जिसे स्पर्श प्राप्त करना चाहिए।

अधिक विशेष रूप से: का लक्ष्य hitTest:withEvent:उस वस्तु को वापस करना है जिसे हिट प्राप्त करना चाहिए। तो, (एम) में आप इस तरह कोड लिख सकते हैं:

// need this to capture button taps since they are outside of self.frame
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{   
    for (UIView *subview in self.subviews) {
        if (CGRectContainsPoint(subview.frame, point)) {
            return subview;
        }
    }

    // use this to pass the 'touch' onward in case no subviews trigger the touch
    return [super hitTest:point withEvent:event];
}

मैं अभी भी इस पद्धति और इस समस्या के लिए बहुत नया हूं, इसलिए यदि कोड लिखने के अधिक कुशल या सही तरीके हैं, तो कृपया टिप्पणी करें।

मुझे उम्मीद है कि बाद में इस सवाल को हिट करने वाले किसी और की मदद करेगा। :)


धन्यवाद, यह मेरे लिए काम कर रहा था, हालांकि मुझे यह निर्धारित करने के लिए कुछ और तर्क करना था कि वास्तव में हिट प्राप्त करने वाले कौन से साक्षात्कार हैं। मैंने आपके उदाहरण btw से एक अनावश्यक तोड़ दिया।
डैनियल सैडी

जब सबव्यू फ्रेम पैरेंट व्यू का सुपरबाउंड फ्रेम था, तो क्लिक इवेंट प्रतिक्रिया नहीं दे रहा था! आपके समाधान ने इस समस्या को ठीक कर दिया। बहुत बहुत धन्यवाद :-)
byJeevan

@toblerpwn (मजाकिया उपनाम :)) आपको इस उत्कृष्ट पुराने उत्तर को संपादित करना चाहिए ताकि यह स्पष्ट हो सके (USE LARGE BOLD CAPS) आपको किस वर्ग में इसे जोड़ना है। चीयर्स!
फेटी

26

स्विफ्ट 5 में

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    guard !clipsToBounds && !isHidden && alpha > 0 else { return nil }
    for member in subviews.reversed() {
        let subPoint = member.convert(point, from: self)
        guard let result = member.hitTest(subPoint, with: event) else { continue }
        return result
    }
    return nil
}

यह केवल तभी काम करेगा जब आप "दुर्व्यवहार" दृश्य के प्रत्यक्ष पर्यवेक्षण पर ओवरराइड को लागू करते हैं
हुडी आईफेल्ड

2

मेरे पास क्या होगा दोनों में ButtonView और MenuView दोनों एक ही स्तर पर दृश्य पदानुक्रम में मौजूद हैं, उन दोनों को एक कंटेनर में रखकर जिसका फ्रेम पूरी तरह से दोनों को फिट करता है। इस तरह से क्लिपड आइटम के इंटरएक्टिव क्षेत्र को नजरअंदाज नहीं किया जाएगा क्योंकि यह पर्यवेक्षी की सीमा है।


मैं साथ ही इस समाधान के बारे में सोचा - इसका अर्थ है मैं कुछ स्थान तर्क नकल करना होगा, लेकिन यह वास्तव में अंत में मेरे सबसे अच्छे विकल्प हो सकता है .. (या refactor कुछ गंभीर कोड!)
toblerpwn

1

यदि आपके पास अपने माता-पिता के दृश्य के अंदर कई अन्य साक्षात्कार हैं, तो संभवत: अधिकांश अन्य इंटरैक्टिव विचार काम नहीं करेंगे यदि आप उपरोक्त समाधानों का उपयोग करते हैं, तो उस स्थिति में आप कुछ इस तरह उपयोग कर सकते हैं (स्विफ्ट 3.2 में):

class BoundingSubviewsViewExtension: UIView {

    @IBOutlet var targetView: UIView!

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        // Convert the point to the target view's coordinate system.
        // The target view isn't necessarily the immediate subview
        let pointForTargetView: CGPoint? = targetView?.convert(point, from: self)
        if (targetView?.bounds.contains(pointForTargetView!))! {
            // The target view may have its view hierarchy,
            // so call its hitTest method to return the right hit-test view
            return targetView?.hitTest(pointForTargetView ?? CGPoint.zero, with: event)
        }
        return super.hitTest(point, with: event)
    }
}

0

अगर किसी को इसकी आवश्यकता है, तो यहां स्विफ्ट विकल्प है

override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
    if !self.clipsToBounds && !self.hidden && self.alpha > 0 {
        for subview in self.subviews.reverse() {
            let subPoint = subview.convertPoint(point, fromView:self);

            if let result = subview.hitTest(subPoint, withEvent:event) {
                return result;
            }
        }
    }

    return nil
}

0

अपने दृश्य पदानुक्रम में कोड की पंक्तियों के नीचे रखें:

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
    UIView* hitView = [super hitTest:point withEvent:event];
    if (hitView != nil)
    {
        [self.superview bringSubviewToFront:self];
    }
    return hitView;
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event
{
    CGRect rect = self.bounds;
    BOOL isInside = CGRectContainsPoint(rect, point);
    if(!isInside)
    {
        for (UIView *view in self.subviews)
        {
            isInside = CGRectContainsPoint(view.frame, point);
            if(isInside)
                break;
        }
    }
    return isInside;
}

अधिक स्पष्टीकरण के लिए, यह मेरे ब्लॉग में समझाया गया था: "कस्टम कॉलआउट: बटन क्लिक करने योग्य मुद्दा नहीं है" के बारे में "goaheadwithiphonetech"।

मुझे उम्मीद है कि उससे आप मदद मिलती है...!!!


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