Soru HitTest'i kullanarak süper görünümünün çerçevesinin dışındaki bir alt görünüme dokunarak yakalama: withEvent:


Benim sorunum: Süper görüşüm var EditView temelde tüm uygulama çerçevesini ve bir alt görünümü kaplar MenuView sadece ~% 20 kadar alır ve sonra MenuView kendi alt görünümünü içeriyor ButtonView aslında dışında MenuViewsınırlar (böyle bir şey: ButtonView.frame.origin.y = -100).

(Not: EditView bir parçası olmayan diğer alt görünümleri var MenuViewHiyerarşiyi görüntüle, fakat cevabı etkileyebilir.)

Muhtemelen sorunu zaten biliyorsunuz: ButtonView sınırları içinde MenuView (ya da daha özel olarak, benim dokunuşlarımın içinde olduğunda MenuViewsınırlar) ButtonView Dokunma olaylarına cevap verir. Benim dokunuşlarımın dışında olduğunda MenuViewsınırlar (ama yine de içinde ButtonViewsınırlar, hiçbir dokunma olayı tarafından alınmaz ButtonView.

Örnek:

  • (E) EditView, tüm görünümlerin ana
  • (M) MenuView, EditView'ın bir alt görünümü
  • (B) ButtonView, MenuView'ın bir alt görünümü

Diyagram: 

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

(B) (M) 'nin çerçevesinin dışında olduğundan (B) bölgesinde bir dokunma asla (M)' ye gönderilmeyecektir - aslında, (M) bu durumda dokunuşu asla analiz edemez ve dokunma hiyerarşideki bir sonraki nesne.

Hedef: Ben bunu geçersiz kıldım hitTest:withEvent: Bu sorunu çözebilir, ama tam olarak nasıl olduğunu anlamıyorum. Benim durumumda hitTest:withEvent: içinde olmak EditView ('master' süper görüşüm)? Ya da içinde geçersiz kılınmalıdır MenuView, dokunma almayan düğmenin doğrudan süper görünümü? Yoksa bunu yanlış mı düşünüyorum?

Bu uzun bir açıklama gerektiriyorsa, iyi bir çevrimiçi kaynak yardımcı olacaktır - Apple'ın UIView belgeleri hariç, bana açıklık getirmedi.

Teşekkürler!


76
2017-08-02 03:41


Menşei




Cevaplar:


Kabul edilen cevap kodunun daha genel olması için değiştirdim - görünümün alt görünümleri sınırlarına sığdırdığı, gizlenebileceği ve daha önemlisi: alt görünümlerin karmaşık görünüm hiyerarşileri olması durumunda, doğru alt görüntünün geri getirileceği durumları ele alır.

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

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

Umarım bu, daha karmaşık kullanım durumları için bu çözümü kullanmaya çalışan herkes için yardımcı olur.


123
2018-02-14 13:13



Bu iyi görünüyor ve bunu yaptığınız için teşekkürler. Ancak, bir sorum vardı: neden geri dönüş yapıyorsun [süper hitTest: point withEvent: event]; ? Dokunuşu tetikleyen bir alt görünüm yoksa sadece geri dönmez miydin? Apple, herhangi bir alt görünümde dokunma varsa, hitTest geri gönderir. - Ser Pounce
Hm ... Evet, bu doğru geliyor. Ayrıca, doğru davranış için, nesnelerin ters sırada yinelenmesi gerekir (çünkü sonuncusu en görsel olanıdır). Düzenlenen kod eşleşecek. - Noam
Mükemmel, bu bana özel UIButtons'u menü olarak getirdiğim projem için özel bir yan menü yapmama yardımcı oldu - Jason Renaldo
Ben sadece UIButton dokunuşunu yakalamak için çözümünü kullandım ve bir UICollectionViewCell içinde bir UICollectionView içinde (içinde) açıkça bir UIView içinde. Bu üç sınıfta hitTest: withEvent: öğesini geçersiz kılmak için UICollectionView ve UICollectionViewCell alt sınıflarını kullanmam gerekiyordu. Ve çekicilik gibi çalışır! Teşekkürler !! - Daniel García
İstenen kullanıma bağlı olarak, [süper hitTest: point withEvent: event] veya nil. Geri dönüş bende herşeyi almasına sebep olur. - Noam


Tamam, biraz kazma ve test yaptım, işte nasıl? hitTest:withEvent çalışır - en azından yüksek düzeyde. Görüntü bu senaryoyu:

  • (E) EditView, tüm görünümlerin ana
  • (M) MenuView, EditView'ın bir alt görünümü
  • (B) ButtonView, MenuView'ın bir alt görünümü

Diyagram:

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

(B) (M) 'nin çerçevesinin dışında olduğundan (B) bölgesinde bir dokunma asla (M)' ye gönderilmeyecektir - aslında, (M) bu durumda dokunuşu asla analiz edemez ve dokunma hiyerarşideki bir sonraki nesne.

Ancak, eğer uygularsanız hitTest:withEvent: (M) 'de, uygulamada herhangi bir yerde bulunan musluklar (M)' ye gönderilir (ya da en azından onlar hakkında bilinir). Bu durumda dokunuşu işlemek için kod yazabilir ve dokunuş alması gereken nesneyi iade edebilirsiniz.

Daha spesifik olarak: amacı hitTest:withEvent: isabet alması gereken nesneyi döndürmektir. Yani, (M) 'de şu şekilde kod yazabilirsiniz:

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

Bu yönteme ve bu soruna hala çok yeniyim, bu yüzden kodu yazmak için daha etkili veya doğru yollar varsa, lütfen yorum yapın.

Umarım bu soruya daha sonra isabet eden başkalarına yardım eder. :)


26
2017-08-03 17:39



Teşekkürler, bu benim için çalıştı, aslında hangi hit alması gereken alt gösterimleri belirlemek için biraz daha mantık yapmak zorunda kaldım. Örnek btw'den gereksiz bir arayı kaldırdım. - Daniel Saidi
Alt görünüm çerçevesi ana görünümün ana çerçevesiyse, tıklama etkinliği yanıt vermiyordu! Çözümünüz bu sorunu çözdü. Çok teşekkürler :-) - byJeevan


Swift 4'te

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
}

18
2018-03-28 14:24





Yapacağım şey, hem ButtonView hem de MenuView, hem hiyerarşisinde hem de her ikisine de tamamen uyan bir konteynere yerleştirerek aynı seviyede var. Bu şekilde kırpılan öğenin etkileşimli bölgesi, süper görünümün sınırları nedeniyle göz ardı edilmeyecektir.


2
2017-08-02 04:07



Bu geçici çözümü de düşündüm - bu, bazı yerleştirme mantığını çoğaltmak zorunda kalacağım anlamına geliyor (ya da bazı ciddi kodları düzeltmek zorundayım!), ama aslında en iyi seçimim bu olabilir. - toblerpwn


Birinin buna ihtiyacı varsa, hızlı alternatif burada

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
2017-08-26 17:16





Ebeveyn görünümünüzde başka birçok alt görüntünüz varsa, yukarıdaki etkileşimleri kullanırsanız muhtemelen diğer etkileşimli görünümlerin çoğu işe yaramazdı, bu durumda böyle bir şeyi kullanabilirsiniz (Swift 3.2'de):

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
2017-11-03 06:51





Görünüm satır hizasında aşağıdaki kod satırlarını yerleştirin:

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

Daha fazla açıklama için, blogumda açıklandı: "Özel belirtme çizgisi: Düğme tıklanabilir bir sorun değil" ile ilgili "goaheadwithiphonetech".

Umarım bu yardımcı olur ... !!!


-1
2018-05-19 10:50



Blogunuz kaldırıldı, bu nedenle lütfen açıklamayı nereden bulabiliriz? - ishahak