IOS के लिए ईवेंट हैंडलिंग - कैसे हिटटैस्ट: withEvent: और pointInside: withEvent: संबंधित हैं?


145

जबकि अधिकांश ऐप्पल दस्तावेज़ बहुत अच्छी तरह से लिखे गए हैं, मुझे लगता है कि ' आईओएस के लिए इवेंट हैंडलिंग गाइड ' एक अपवाद है। मेरे लिए यह स्पष्ट करना कठिन है कि वहां क्या वर्णन किया गया है।

दस्तावेज़ कहता है,

हिट-टेस्टिंग में, एक विंडो hitTest:withEvent:पदानुक्रम के शीर्ष-सबसे अधिक दृश्य पर कॉल करती है; यह विधि pointInside:withEvent:प्रत्येक दृश्य पर पुनरावर्ती कॉल द्वारा आगे बढ़ती है जो पदानुक्रम में YES को लौटाती है, पदानुक्रम को नीचे तक आगे बढ़ाती है जब तक कि वह सबव्यू नहीं पाता जिसके भीतर स्पर्श हुआ था। वह दृश्य हिट-टेस्ट दृश्य बन जाता है।

तो क्या ऐसा है कि hitTest:withEvent:सिस्टम द्वारा केवल सबसे शीर्ष दृश्य को बुलाया जाता है, जो pointInside:withEvent:सभी साक्षात्कारों को कॉल करता है, और यदि किसी विशिष्ट सबव्यू से वापसी YES है, तो pointInside:withEvent:उस सबव्यू के उपवर्गों को कॉल करता है?


3
एक बहुत अच्छा ट्यूटोरियल जिसने मुझे लिंक
anneblue

इसके लिए समतुल्य नया दस्तावेज़ अब डेवलपर
Cœur

जवाबों:


173

यह काफी बुनियादी सवाल है। लेकिन मैं आपके साथ सहमत हूं कि दस्तावेज़ अन्य दस्तावेजों की तरह स्पष्ट नहीं है, इसलिए यहां मेरा जवाब है।

hitTest:withEvent:UIResponder का कार्यान्वयन निम्नलिखित कार्य करता है:

  • यह कॉल करता pointInside:withEvent:हैself
  • यदि hitTest:withEvent:रिटर्न NO है, तो रिटर्न nil। कहानी की समाप्ति।
  • यदि रिटर्न YES है, तो यह hitTest:withEvent:उसके सबव्यू में संदेश भेजता है । यह शीर्ष-स्तरीय सबव्यू से शुरू होता है, और तब तक अन्य विचारों के लिए जारी रहता है जब तक कि कोई सबव्यू गैर- nilऑब्जेक्ट नहीं लौटाता है , या सभी साक्षात्कार संदेश प्राप्त करते हैं।
  • यदि कोई सबव्यू nilपहली बार में एक गैर- ऑब्जेक्ट लौटाता है , तो पहला hitTest:withEvent:उस ऑब्जेक्ट को लौटाता है। कहानी की समाप्ति।
  • यदि कोई सबव्यू गैर- nilऑब्जेक्ट नहीं लौटाता है , तो पहला hitTest:withEvent:रिटर्नself

यह प्रक्रिया पुनरावर्ती रूप से दोहराती है, इसलिए आमतौर पर पदानुक्रम का पत्ता दृश्य अंततः वापस आ जाता है।

हालाँकि, आप hitTest:withEventकुछ अलग करने के लिए ओवरराइड कर सकते हैं । कई मामलों में, ओवरराइडिंग pointInside:withEvent:सरल है और अभी भी आपके आवेदन में ईवेंट हैंडलिंग से जुड़ने के लिए पर्याप्त विकल्प प्रदान करता है।


क्या आप hitTest:withEvent:सभी साक्षात्कारों का मतलब अंततः निष्पादित होते हैं?
realstuff02

2
हाँ। बस hitTest:withEvent:अपने विचारों में ओवरराइड करें (और pointInsideयदि आप चाहते हैं), एक लॉग प्रिंट करें और [super hitTest...यह पता लगाने के लिए कॉल करें कि hitTest:withEvent:किस क्रम में कहा जाता है।
एमएचसी

चरण 3 नहीं होना चाहिए जहां आप उल्लेख करते हैं "यदि रिटर्न हां है, तो यह सबसे हिट भेजता है: withEvent: ... क्या यह बिंदु नहीं होना चाहिए: withEvent? मैंने सोचा कि यह बिंदु सभी बिंदुओं को भेजता है?
prostock

फरवरी में वापस पहली बार हिटस्टेस्ट: विथ एवेंट :, जिसमें एक पॉइंट इनसाइड: विथ एवेंट: को ही भेजा गया था। मैंने एसडीके संस्करणों के साथ इस व्यवहार की फिर से जाँच नहीं की है, लेकिन मुझे लगता है कि हिटटेस्ट भेजना: रोकना: अधिक समझ में आता है क्योंकि यह एक घटना के एक दृश्य से संबंधित है या नहीं इसका उच्च-स्तरीय नियंत्रण प्रदान करता है; pointInside: withEvent: बताता है कि इवेंट लोकेशन व्यू पर है या नहीं, इवेंट व्यू के अंतर्गत आता है या नहीं। उदाहरण के लिए, कोई सबव्यू किसी घटना को संभालना नहीं चाहेगा, भले ही उसका स्थान सबव्यू पर हो।
एमएचसी

1
WWDC2014 सत्र 235 - उन्नत स्क्रॉल साक्षात्कार और टच हैंडलिंग तकनीक इस समस्या के लिए बहुत व्याख्या और उदाहरण देती है।
antonio081014

297

मुझे लगता है कि आप दृश्य पदानुक्रम के साथ उपवर्ग को भ्रमित कर रहे हैं। डॉक जो कहता है वह इस प्रकार है। कहते हैं कि आपके पास यह दृष्टिकोण पदानुक्रम है। पदानुक्रम द्वारा मैं वर्ग पदानुक्रम के बारे में बात नहीं कर रहा हूं, लेकिन पदानुक्रम के विचारों के भीतर विचार, निम्नानुसार हैं:

+----------------------------+
|A                           |
|+--------+   +------------+ |
||B       |   |C           | |
||        |   |+----------+| |
|+--------+   ||D         || |
|             |+----------+| |
|             +------------+ |
+----------------------------+

कहते हैं आप अपनी उंगली अंदर डालिए D। यहाँ क्या होगा:

  1. hitTest:withEvent:पर कहा जाता है A, पदानुक्रम के शीर्ष-सबसे अधिक दृश्य।
  2. pointInside:withEvent: प्रत्येक दृश्य पर पुनरावर्ती कहा जाता है।
    1. pointInside:withEvent:कहा जाता है A, और रिटर्नYES
    2. pointInside:withEvent:कहा जाता है B, और रिटर्नNO
    3. pointInside:withEvent:कहा जाता है C, और रिटर्नYES
    4. pointInside:withEvent:कहा जाता है D, और रिटर्नYES
  3. लौट आए विचारों पर YES, यह पदानुक्रम पर नीचे की ओर देखेगा जहां स्पर्श हुआ था। इस मामले में, से A, Cऔर D, यह होगा D
  4. D हिट-टेस्ट दृश्य होगा

जवाब के लिए धन्यवाद। आपने जो वर्णन किया है वह मेरे दिमाग में भी था, लेकिन @MHC hitTest:withEvent:बी, सी और डी के बारे में कहता है। यदि D C का उप-भाग है, तो A नहीं, तो क्या होगा? मुझे लगता है कि मैं भ्रमित हो गया ...
realstuff02

2
मेरी ड्राइंग में, D
pgb

1
के रूप में अच्छी तरह से, और के रूप में Aवापस नहीं होगा ? YESCD
मार्टिन विकमैन

2
यह मत भूलिए कि वे दृश्य जो अदृश्य हैं (या तो .hidden या opacity 0.1 से नीचे), या उपयोगकर्ता सहभागिता बंद है, कभी भी हिटटेस्ट का जवाब नहीं देगा। मुझे नहीं लगता कि इन वस्तुओं को पहली जगह पर हिटटेस्ट कहा जा रहा है।
जॉनी

बस उस हिटटेस्ट को जोड़ना चाहता था: withEvent: को उनके पदानुक्रम के आधार पर सभी विचारों पर बुलाया जा सकता है।
आदित्य

47

मुझे यह हिट-टेस्टिंग iOS में बहुत मददगार लगता है

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

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
        return nil;
    }
    if ([self pointInside:point withEvent:event]) {
        for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
            CGPoint convertedPoint = [subview convertPoint:point fromView:self];
            UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
            if (hitTestView) {
                return hitTestView;
            }
        }
        return self;
    }
    return nil;
}

स्विफ्ट 4 संपादित करें:

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    if self.point(inside: point, with: event) {
        return super.hitTest(point, with: event)
    }
    guard isUserInteractionEnabled, !isHidden, alpha > 0 else {
        return nil
    }

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

तो आपको इसे UIView के उपवर्ग में जोड़ने की आवश्यकता है और आपके पदानुक्रम में सभी विचार इससे प्राप्त होते हैं?
गुइग

21

जवाब के लिए धन्यवाद, उन्होंने मुझे "ओवरले" विचारों के साथ स्थिति को हल करने में मदद की।

+----------------------------+
|A +--------+                |
|  |B  +------------------+  |
|  |   |C            X    |  |
|  |   +------------------+  |
|  |        |                |
|  +--------+                | 
|                            |
+----------------------------+

मान लें X- उपयोगकर्ता का स्पर्श। रिटर्न pointInside:withEvent:पर , इसलिए रिटर्न । जब आप शीर्ष दृश्यमान दृश्य पर स्पर्श प्राप्त करना चाहते हैं, तो मैंने इस मुद्दे को संभालने के लिए श्रेणी लिखी ।BNOhitTest:withEvent:AUIView

- (UIView *)overlapHitTest:(CGPoint)point withEvent:(UIEvent *)event {
    // 1
    if (!self.userInteractionEnabled || [self isHidden] || self.alpha == 0)
        return nil;

    // 2
    UIView *hitView = self;
    if (![self pointInside:point withEvent:event]) {
        if (self.clipsToBounds) return nil;
        else hitView = nil;
    }

    // 3
    for (UIView *subview in [self.subviewsreverseObjectEnumerator]) {
        CGPoint insideSubview = [self convertPoint:point toView:subview];
        UIView *sview = [subview overlapHitTest:insideSubview withEvent:event];
        if (sview) return sview;
    }

    // 4
    return hitView;
}
  1. हमें छिपी या पारदर्शी विचारों या userInteractionEnabledसेट के साथ दृश्य के लिए स्पर्श की घटनाओं को नहीं भेजना चाहिए NO;
  2. यदि स्पर्श अंदर है self, selfतो संभावित परिणाम माना जाएगा।
  3. हिट के लिए पुनरावर्ती सभी सबव्यू देखें। यदि कोई हो, तो उसे वापस करें।
  4. 2 चरण से परिणाम के आधार पर स्व वापसी या शून्य।

ध्यान दें, [self.subviewsreverseObjectEnumerator]सबसे ऊपर से नीचे तक पदानुक्रम को देखने के लिए आवश्यक है। और clipsToBoundsयह सुनिश्चित करने के लिए जांच करें कि नकाबपोश साक्षात्कार का परीक्षण न करें।

उपयोग:

  1. आपके उपवर्ग दृश्य में आयात श्रेणी।
  2. बदलें hitTest:withEvent:इस के साथ
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    return [self overlapHitTest:point withEvent:event];
}

आधिकारिक एप्पल के गाइड कुछ अच्छे चित्र भी प्रदान करते हैं।

आशा है कि यह किसी की मदद करता है।


गजब का! स्पष्ट तर्क और ग्रेट कोड स्निपेट के लिए धन्यवाद, मेरे सिर-खरोंच को हल किया!
थॉम्पसन

@ शेर, अच्छा जवाब। इसके अलावा, आप पहले चरण में स्पष्ट रंग के लिए समानता की जांच कर सकते हैं।
एक्वेरियम_मोसे

3

यह इस तरह दिखाता है!

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    if (self.hidden || !self.userInteractionEnabled || self.alpha < 0.01)
    {
        return nil;
    }

    if (![self pointInside:point withEvent:event])
    {
        return nil;
    }

    __block UIView *hitView = self;

    [self.subViews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {   

        CGPoint thePoint = [self convertPoint:point toView:obj];

        UIView *theSubHitView = [obj hitTest:thePoint withEvent:event];

        if (theSubHitView != nil)
        {
            hitView = theSubHitView;

            *stop = YES;
        }

    }];

    return hitView;
}

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

@DouglasHill आपके सुधार के लिए धन्यवाद। सादर
हिप्पो

1

@ लायन का स्निपेट एक आकर्षण की तरह काम करता है। मैंने इसे 2.1 स्विफ्ट करने के लिए पोर्ट किया और इसे UIView के एक्सटेंशन के रूप में इस्तेमाल किया। मैं इसे यहाँ पोस्ट कर रहा हूँ अगर किसी को इसकी आवश्यकता हो।

extension UIView {
    func overlapHitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        // 1
        if !self.userInteractionEnabled || self.hidden || self.alpha == 0 {
            return nil
        }
        //2
        var hitView: UIView? = self
        if !self.pointInside(point, withEvent: event) {
            if self.clipsToBounds {
                return nil
            } else {
                hitView = nil
            }
        }
        //3
        for subview in self.subviews.reverse() {
            let insideSubview = self.convertPoint(point, toView: subview)
            if let sview = subview.overlapHitTest(insideSubview, withEvent: event) {
                return sview
            }
        }
        return hitView
    }
}

इसका उपयोग करने के लिए, बस हिट को ओवरराइड करें: बिंदु: अपने uiview में निम्नानुसार काम करें:

override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
    let uiview = super.hitTest(point, withEvent: event)
    print("hittest",uiview)
    return overlapHitTest(point, withEvent: event)
}

0

वर्ग आरेख

परीक्षण मारो

लगता है First Responder

First Responderइस मामले में सबसे गहरी UIView point()विधि है, जो सच है

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

आंतरिक रूप से hitTest()जैसा दिखता है

hitTest() {

    if (isUserInteractionEnabled == false || isHidden == true || alpha == 0 || point() == false) { return nil }

    for subview in subviews {
        if subview.hitTest() != nil {
            return subview
        }
    }

    return nil

}

टच इवेंट को भेजें First Responder

//UIApplication.shared.sendEvent()

//UIApplication, UIWindow
func sendEvent(_ event: UIEvent)

//UIResponder
func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?)

आइए एक उदाहरण देखें

प्रत्युत्तर श्रृंखला

//UIApplication.shared.sendAction()
func sendAction(_ action: Selector, to target: Any?, from sender: Any?, for event: UIEvent?) -> Bool

उदाहरण देखिए

class AppDelegate: UIResponder, UIApplicationDelegate {
    @objc
    func foo() {
        //this method is called using Responder Chain
        print("foo") //foo
    }
}

class ViewController: UIViewController {
    func send() {
        UIApplication.shared.sendAction(#selector(AppDelegate.foo), to: nil, from: view1, for: nil)
    }
}

[Android onTouch]

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