UIView sınırlarının ötesinde etkileşim


94

Bir UIButton'un (veya bu konuyla ilgili başka herhangi bir kontrolün), UIButton'un çerçevesi ebeveynin çerçevesinin dışında olduğunda dokunma olaylarını alması mümkün müdür? Çünkü bunu denediğimde, UIButton'um herhangi bir olay alamıyor gibi görünüyor. Bunu nasıl aşarım?


1
Bu benim için mükemmel çalışıyor. developer.apple.com/library/ios/qa/qa2013/qa1812.html En İyi
Frank Li

2
Bu da iyi ve alakalı (veya belki de bir dupe): stackoverflow.com/a/31420820/8047
Dan Rosenstark

Yanıtlar:


94

Evet. Bu hitTest:withEvent:görünümün içerdiğinden daha büyük bir nokta kümesi için bir görünüm döndürme yöntemini geçersiz kılabilirsiniz . UIView Sınıf Referansına bakın .

Düzenleme: Örnek:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat radius = 100.0;
    CGRect frame = CGRectMake(-radius, -radius,
                              self.frame.size.width + radius,
                              self.frame.size.height + radius);

    if (CGRectContainsPoint(frame, point)) {
        return self;
    }
    return nil;
}

Düzenleme 2: (Açıklamadan sonra :) Düğmenin üst öğenin sınırları dahilinde olduğu düşünüldüğünden emin olmak için, düğmenin çerçevesini dahil etmek üzere üst öğede geçersiz kılmanız gerekir pointInside:withEvent:.

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    if (CGRectContainsPoint(self.view.bounds, point) ||
        CGRectContainsPoint(button.view.frame, point))
    {
        return YES;
    }
    return NO;
}

Not pointInside geçersiz kılma oldukça doğru değil sadece orada kodu. Summon'ın aşağıda açıkladığı gibi, şunu yapın:

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
    {
    if ( CGRectContainsPoint(self.oversizeButton.frame, point) )
        return YES;

    return [super pointInside:point withEvent:event];
    }

Bunu büyük olasılıkla self.oversizeButtonbu UIView alt sınıfında bir IBOutlet olarak yapacağınızı unutmayın ; daha sonra söz konusu "büyük boy düğmesini" söz konusu özel görünüme sürükleyebilirsiniz. (Ya da, herhangi bir nedenle bunu bir projede çok yapıyor olsaydınız, özel bir UIButton alt sınıfınız olur ve bu sınıflar için alt görünüm listenize bakabilirsiniz.) Umarım yardımcı olur.


Bazı örnek kodlarla bunu doğru bir şekilde uygulamama daha fazla yardımcı olabilir misiniz? Ayrıca referansı okudum ve şu satırdan dolayı kafam karıştı: "Alıcının sınırlarının dışında kalan noktalar, alıcının alt görünümlerinden birinin içinde olsalar bile asla isabet olarak raporlanmaz. Alt görünümler görsel olarak sınırların ötesine geçebilir üst görünümün clipsToBounds özelliği HAYIR olarak ayarlandıysa, üst görünümün üst öğesi. Ancak, vuruş testi her zaman üst görünümün sınırları dışındaki noktaları yok sayar. "
ThomasM

Sanki ihtiyacım olan çözüm bu değilmiş gibi görünmesini sağlayan bu kısım özellikle: "Ancak, vuruş testi her zaman ebeveyn görüşünün sınırları dışındaki noktaları göz ardı eder" .. Ama tabii ki yanılıyor olabilirim, elma referanslarını bazen gerçekten kafa karıştırıcı buluyorum ..
ThomasM

Ah, şimdi anlıyorum. pointInside:withEvent:Düğmenin çerçevesini eklemek için üst öğenin üzerine yazmanız gerekir . Yukarıdaki cevabıma bazı örnek kodlar ekleyeceğim.
jnic

Bunu UIView yöntemlerini geçersiz kılmadan yapmanın bir yolu var mı? Örneğin, görünümleriniz tamamen UIKit için jenerik ise ve bir Uç aracılığıyla başlatılmışsa, bu yöntemleri UIViewController'ınızdan geçersiz kılabilir misiniz?
RLH

1
hitTest:withEvent:ve pointInside:withEvent:ebeveynin sınırları dışında kalan bir düğmeye bastığımda benim için çağrılmayan alan. Ne yanlış olabilir?
iosdude

24

@jnic, iOS SDK 5.0 üzerinde çalışıyorum ve kodunuzun doğru çalışmasını sağlamak için bunu yapmam gerekiyordu:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
if (CGRectContainsPoint(button.frame, point)) {
    return YES;
}
return [super pointInside:point withEvent:event]; }

Benim durumumdaki konteyner görünümü bir UIButton'dur ve tüm alt elemanlar aynı zamanda ana UIButton'un sınırları dışında hareket edebilen UIB butonlarıdır.

En iyi


20

Üst görünümde, vuruş testi yöntemini geçersiz kılabilirsiniz:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    CGPoint translatedPoint = [_myButton convertPoint:point fromView:self];

    if (CGRectContainsPoint(_myButton.bounds, translatedPoint)) {
        return [_myButton hitTest:translatedPoint withEvent:event];
    }
    return [super hitTest:point withEvent:event];

}

Bu durumda, nokta düğmenizin sınırları içindeyse, aramayı oraya yönlendirirsiniz; değilse, orijinal uygulamaya geri dönün.


18

Benim durumumda, a UICollectionViewCelliçeren bir alt sınıfım vardı UIButton. clipsToBoundsHücrede devre dışı bıraktım ve düğme hücre sınırlarının dışında görünüyordu. Ancak, düğme dokunma olaylarını almıyordu. Jack'in cevabını kullanarak düğmedeki dokunma olaylarını tespit edebildim: https://stackoverflow.com/a/30431157/3344977

İşte bir Swift versiyonu:

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

    let translatedPoint = button.convertPoint(point, fromView: self)

    if (CGRectContainsPoint(button.bounds, translatedPoint)) {
        print("Your button was pressed")
        return button.hitTest(translatedPoint, withEvent: event)
    }
    return super.hitTest(point, withEvent: event)
}

Swift 4:

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

    let translatedPoint = button.convert(point, from: self)

    if (button.bounds.contains(translatedPoint)) {
        print("Your button was pressed")
        return button.hitTest(translatedPoint, with: event)
    }
    return super.hitTest(point, with: event)
}

Bu tam olarak benim senaryomdu. Diğer bir nokta da bu yöntemi CollectionViewCell Sınıfının içine koymanız gerektiğidir.
Hamid Rıza Ansari

Ama neden düğmeyi geçersiz kılınmış bir yönteme enjekte edesiniz ???
rommex

10

Bu neden oluyor?

Bunun nedeni, alt görünümünüz süpervizörünüzün sınırları dışında kaldığında, o alt görünümde fiilen gerçekleşen dokunma olaylarının bu alt görünüme teslim edilmemesidir. Ancak, OLACAK onun Superview teslim edilmesi.

Alt görünümlerin görsel olarak kırpılıp kırpılmadığına bakılmaksızın, dokunma olayları her zaman hedef görünümün süpervizörünün sınır dikdörtgenine uyar. Diğer bir deyişle, bir görünümün denetleme görünümünün sınır dikdörtgeninin dışında kalan bir bölümünde meydana gelen dokunma olayları, bu görünüme teslim edilmez. Bağlantı

Ne yapmak gerekiyor?

Süpervizörünüz yukarıda belirtilen dokunma olayını aldığında, UIKit'e bu dokunma olayını alacak olanın benim alt görünümüm olması gerektiğini açıkça söylemeniz gerekir.

Ya kod?

Gözetiminiz sırasında uygulayın func hitTest(_ point: CGPoint, with event: UIEvent?)

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        if isHidden || alpha == 0 || clipsToBounds { return super.hitTest(point, with: event) }
        // convert the point into subview's coordinate system
        let subviewPoint = self.convert(point, to: subview)
        // if the converted point lies in subview's bound, tell UIKit that subview should be the one that receives this event
        if !subview.isHidden && subview.bounds.contains(subviewPoint) { return subview }
        return super.hitTest(point, with: event)
    }

Büyüleyici Gotchya: "En yüksek çok küçük gözetim" e gitmelisiniz

Problem görünümünün dışarıda olduğu "en yüksek" görünüme "yukarı" gitmelisiniz.

Tipik örnek:

Konteynır görünümü C olan bir ekranınız olduğunu varsayalım. Konteynır görünümü kontrolörünün görünümü V. (Geri Çağırma V, C'nin içinde oturacak ve aynı boyutta olacaktır.) V'nin bir alt görünümü var (belki bir düğme) B. Sorun B'dir aslında V.

Ancak B'nin de C'nin dışında olduğuna dikkat edin.

Bu örnekte çözümü V'ye değil,override hitTest aslında C'ye uygulamanız gerekiyor . V'ye uygularsanız - hiçbir şey yapmaz.


4
Bunun için teşekkür ederim! Bundan kaynaklanan bir sorun için saatler harcadım. Bunu göndermek için zaman ayırdığınız için gerçekten minnettarım. :)
ClayJ

@ScottZhu: Cevabınıza önemli bir soru ekledim. Elbette, eklememi silmekte veya değiştirmekte özgürsünüz! Tekrar teşekkürler!
Şişko

9

Swift:

TargetView, olayı almak istediğiniz görünümdür (tamamen veya kısmen üst görünümünün çerçevesinin dışında yer alabilir).

override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
        let pointForTargetView: CGPoint = targetView.convertPoint(point, fromView: self)
        if CGRectContainsPoint(targetView.bounds, pointForTargetView) {
            return closeButton
        }
        return super.hitTest(point, withEvent: event)
}

5

Benim için (diğerleri için olduğu gibi) cevap, yöntemi geçersiz kılmak değil , hitTestyöntemi geçersiz kılmaktı pointInside. Bunu sadece iki yerde yapmak zorunda kaldım.

Gözlem Bu, eğer clipsToBoundsdoğru olursa, tüm bu sorunu ortadan kaldıracağı görüşüdür . İşte UIViewSwift 3'teki basitliğim :

class PromiscuousView : UIView {
    override func point(inside point: CGPoint,
               with event: UIEvent?) -> Bool {
        return true
    }
} 

Alt görünüm Sizin kilometreniz değişebilir, ancak benim durumumda pointInside, dokunmanın sınırların dışında olduğuna karar verilen alt görünüm için yöntemin üzerine yazmak zorunda kaldım. Benimki Objective-C'de, yani:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    WEPopoverController *controller = (id) self.delegate;
    if (self.takeHitsOutside) {
        return YES;
    } else {
        return [super pointInside:point withEvent:event];
    }
}

Bu cevap (ve aynı soru üzerine birkaç başka cevap) anlayışınızı netleştirmenize yardımcı olabilir.


2

swift 4.1 güncellendi

Dokunma olaylarını aynı UIView'de almak için sadece aşağıdaki geçersiz kılma yöntemini eklemeniz gerekir, bu, UIView'de dokunulan belirli görünümü tanımlayacak ve sınırın dışına yerleştirilecektir.

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    let pointforTargetview:CGPoint = self.convert(point, to: btnAngle)
    if  btnAngle.bounds.contains(pointforTargetview) {
        return  self
    }
    return super.hitTest(point, with: event)
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.