Interaksi di luar batas UIView

94

Apakah mungkin bagi UIButton (atau kontrol lainnya dalam hal ini) untuk menerima kejadian sentuh ketika bingkai UIButton berada di luar bingkai induknya? Penyebab ketika saya mencoba ini, UIButton saya sepertinya tidak dapat menerima kejadian apa pun. Bagaimana cara mengatasi ini?

ThomasM
sumber
1
Ini bekerja dengan sempurna untuk saya. developer.apple.com/library/ios/qa/qa2013/qa1812.html Terbaik
Frank Li
2
Ini juga bagus dan relevan (atau mungkin penipuan): stackoverflow.com/a/31420820/8047
Dan Rosenstark

Jawaban:

94

Iya. Anda dapat mengganti hitTest:withEvent:metode untuk mengembalikan tampilan untuk sekumpulan poin yang lebih besar dari isi tampilan tersebut. Lihat Referensi Kelas UIView .

Edit: Contoh:

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

Edit 2: (Setelah klarifikasi :) Untuk memastikan bahwa tombol diperlakukan sebagai dalam batas induk, Anda perlu menimpa pointInside:withEvent:induk untuk menyertakan bingkai tombol.

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

Perhatikan bahwa kode di sana untuk menimpa pointInside tidak sepenuhnya benar. Seperti yang dijelaskan oleh Summon di bawah, lakukan ini:

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

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

Perhatikan bahwa Anda kemungkinan besar akan melakukannya self.oversizeButtonsebagai IBOutlet di subclass UIView ini; lalu Anda cukup menyeret "tombol besar" yang dimaksud, ke, tampilan khusus yang dimaksud. (Atau, jika karena alasan tertentu Anda sering melakukan ini dalam sebuah proyek, Anda akan memiliki subkelas UIButton khusus, dan Anda dapat melihat daftar subview Anda untuk kelas-kelas itu.) Semoga membantu.

jnic
sumber
Dapatkah Anda membantu saya lebih jauh dalam menerapkan ini dengan benar dengan beberapa kode contoh? Saya juga membaca referensi dan karena baris berikut, saya bingung: "Titik yang berada di luar batas penerima tidak pernah dilaporkan sebagai hit, meskipun sebenarnya berada dalam salah satu subview penerima. Subview dapat meluas secara visual melampaui batas dari induknya jika properti clipsToBounds tampilan induk disetel ke NO. Namun, pengujian klik selalu mengabaikan titik di luar batas tampilan induk. "
ThomasM
Terutama bagian inilah yang membuatnya tampak seolah-olah ini bukan solusi yang saya butuhkan: "Namun, pengujian hit selalu mengabaikan poin di luar batas tampilan induk" .. Tapi saya bisa saja salah, saya menemukan referensi apel terkadang sangat membingungkan ..
ThomasM
Ah, saya mengerti sekarang. Anda perlu mengganti pointInside:withEvent:kerangka induk untuk memasukkan bingkai tombol. Saya akan menambahkan beberapa kode contoh ke jawaban saya di atas.
jnic
Apakah ada cara untuk melakukan ini tanpa mengganti metode UIView? Misalnya, jika tampilan Anda sepenuhnya umum untuk UIKit dan diinisialisasi melalui Nib, dapatkah Anda mengganti metode ini dari dalam UIViewController Anda?
RLH
1
hitTest:withEvent:dan pointInside:withEvent:area yang tidak dipanggil untuk saya ketika saya menekan tombol yang berada di luar batas orang tua. Apa yang salah?
iosdude
24

@jnic, Saya sedang mengerjakan iOS SDK 5.0 dan agar kode Anda berfungsi dengan benar, saya harus melakukan ini:

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

Tampilan wadah dalam kasus saya adalah UIButton dan semua elemen anak juga merupakan UIButtons yang dapat bergerak di luar batas UIButton induk.

Terbaik

Memanggil
sumber
20

Dalam tampilan induk, Anda dapat mengganti metode uji klik:

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

}

Dalam hal ini, jika titik tersebut berada dalam batas-batas tombol Anda, Anda meneruskan panggilan ke sana; jika tidak, kembalikan ke implementasi awal.

Mendongkrak
sumber
18

Dalam kasus saya, saya memiliki UICollectionViewCellsubclass yang berisi file UIButton. Saya menonaktifkan clipsToBoundsdi sel dan tombolnya terlihat di luar batas sel. Namun, tombol tersebut tidak menerima peristiwa sentuh. Saya dapat mendeteksi peristiwa sentuhan pada tombol dengan menggunakan jawaban Jack: https://stackoverflow.com/a/30431157/3344977

Ini versi Swift:

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

Cepat 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)
}
pengguna3344977
sumber
Itu persis skenario saya. Poin lainnya adalah Anda harus meletakkan metode ini di dalam CollectionViewCell Class.
Hamid Reza Ansari
Tetapi mengapa Anda menyuntikkan tombol ke metode yang diganti ???
rommex
10

Mengapa ini terjadi?

Ini karena jika subview Anda berada di luar batas tampilan super, peristiwa sentuh yang sebenarnya terjadi di subview tersebut tidak akan dikirimkan ke subview tersebut. Namun, AKAN dikirim ke superview-nya.

Terlepas dari apakah sub-tampilan dipotong secara visual atau tidak, peristiwa sentuh selalu mengikuti persegi panjang batas tampilan target tampilan. Dengan kata lain, peristiwa sentuh yang terjadi di bagian tampilan yang berada di luar persegi panjang batas superview tidak akan ditayangkan ke tampilan tersebut. Tautan

apa yang kamu butuhkan?

Ketika superview Anda menerima event touch yang disebutkan di atas, Anda harus memberitahu UIKit secara eksplisit bahwa subview saya yang akan menerima event touch ini.

Bagaimana dengan kodenya?

Dalam superview Anda, terapkan 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)
    }

Gotchya yang menarik: Anda harus pergi ke "superview tertinggi terlalu kecil"

Anda harus "naik" ke tampilan "tertinggi" di mana tampilan masalahnya ada di luar.

Contoh umum:

Katakanlah Anda memiliki layar S, dengan tampilan wadah C. Tampilan pengontrol wadah adalah V. (Ingat V akan duduk di dalam C dan menjadi ukuran yang identik.) V memiliki subview (mungkin tombol) B. B adalah masalahnya tampilan yang sebenarnya di luar V.

Tetapi perhatikan bahwa B juga berada di luar C.

Dalam contoh ini Anda harus menerapkan solusi override hitTestpada kenyataannya untuk C, tidak V . Jika Anda menerapkannya ke V - itu tidak melakukan apa-apa.

Scott Zhu
sumber
4
Terima kasih untuk ini! Saya telah menghabiskan waktu berjam-jam untuk membahas masalah yang berasal dari ini. Saya sangat menghargai Anda meluangkan waktu untuk memposting ini. :)
ClayJ
@ ScottZhu: Saya memang menambahkan gotchya penting ke jawaban Anda. Tentu saja, Anda bebas untuk menghapus atau mengubah penambahan saya! Terima kasih lagi!
Fattie
9

Cepat:

Di mana targetView adalah tampilan yang Anda inginkan untuk menerima acara (yang mungkin seluruhnya atau sebagian berada di luar bingkai tampilan induknya).

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)
}
Luke Bartolomeo
sumber
5

Bagi saya (seperti untuk orang lain), jawabannya bukanlah untuk mengesampingkan hitTestmetode, melainkan pointInsidemetode. Saya harus melakukan ini hanya di dua tempat.

Superview Ini adalah pandangan bahwa jika clipsToBoundsdisetel ke true, itu akan membuat seluruh masalah ini hilang. Jadi, inilah kesederhanaan saya UIViewdi Swift 3:

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

The Subview Jarak tempuh Anda mungkin berbeda, tetapi dalam kasus saya, saya juga harus menimpa pointInsidemetode untuk subview yang telah memutuskan sentuhan itu di luar batas. Milik saya ada di Objective-C, jadi:

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

Jawaban ini (dan beberapa orang lain pada pertanyaan yang sama) dapat membantu menjernihkan pemahaman Anda.

Dan Rosenstark
sumber
2

swift 4.1 diperbarui

Untuk mendapatkan kejadian sentuh di UIView yang sama Anda hanya perlu menambahkan metode penggantian berikut, ini akan mengidentifikasi tampilan spesifik yang diketuk di UIView yang ditempatkan di luar batas

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)
}
Ankit Kushwah
sumber