Pertanyaan Menangkap sentuhan pada subview di luar bingkai superview-nya menggunakan hitTest: withEvent:


Masalahku: Saya memiliki superview EditView yang pada dasarnya membutuhkan seluruh kerangka aplikasi, dan subview MenuView yang hanya mengambil bagian bawah ~ 20%, dan kemudian MenuView berisi subnetnya sendiri ButtonView yang sebenarnya berada di luar MenuViewbatas (sesuatu seperti ini: ButtonView.frame.origin.y = -100).

(catatan: EditView memiliki subview lain yang bukan bagian dari MenuView's tampilan hierarki, tetapi dapat mempengaruhi jawabannya.)

Anda mungkin sudah tahu masalahnya: kapan ButtonView berada dalam batasan MenuView (atau, lebih khusus lagi, ketika sentuhan saya ada di dalam MenuViewbatas-batas), ButtonView menanggapi acara sentuhan. Ketika sentuhan saya berada di luar MenuViewbatas (tapi masih dalam ButtonView's batas), tidak ada acara sentuhan yang diterima oleh ButtonView.

Contoh:

  • (E) adalah EditView, orang tua dari semua tampilan
  • (M) adalah MenuView, subview dari EditView
  • (B) adalah ButtonView, subview MenuView

Diagram: 

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

Karena (B) berada di luar bingkai (M), ketukan di wilayah (B) tidak akan pernah dikirim ke (M) - sebenarnya, (M) tidak pernah menganalisis sentuhan dalam kasus ini, dan sentuhan itu dikirim ke objek berikutnya dalam hierarki.

Tujuan: Saya mengumpulkan itu mengesampingkan hitTest:withEvent: dapat memecahkan masalah ini, tetapi saya tidak mengerti bagaimana tepatnya. Dalam kasus saya, seharusnya hitTest:withEvent: ditimpa EditView (Superview 'master' saya)? Atau haruskah itu ditimpa MenuView, Superview langsung dari tombol yang tidak menerima sentuhan? Ataukah saya memikirkan hal ini dengan tidak benar?

Jika ini memerlukan penjelasan yang panjang, sumber daya online yang baik akan sangat membantu - kecuali dokumen UIView Apple, yang belum menjelaskannya kepada saya.

Terima kasih!


76
2017-08-02 03:41


asal


Jawaban:


Saya telah memodifikasi kode jawaban yang diterima menjadi lebih generik - ia menangani kasus-kasus di mana tampilan tidak melambangkan sub-tab ke batas-batasnya, mungkin disembunyikan, dan yang lebih penting: jika subview adalah hierarki tampilan kompleks, sub-teks yang benar akan dikembalikan.

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

Saya harap ini membantu siapa pun yang mencoba menggunakan solusi ini untuk kasus penggunaan yang lebih kompleks.


123
2018-02-14 13:13



Ok, saya melakukan penggalian dan pengujian, begini caranya hitTest:withEvent bekerja - setidaknya pada level yang tinggi. Bayangkan skenario ini:

  • (E) adalah EditView, orang tua dari semua tampilan
  • (M) adalah MenuView, subview dari EditView
  • (B) adalah ButtonView, subview MenuView

Diagram:

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

Karena (B) berada di luar bingkai (M), ketukan di wilayah (B) tidak akan pernah dikirim ke (M) - sebenarnya, (M) tidak pernah menganalisis sentuhan dalam kasus ini, dan sentuhan itu dikirim ke objek berikutnya dalam hierarki.

Namun, jika Anda menerapkan hitTest:withEvent: di (M), mengetuk di mana saja di dalam aplikasi akan dikirim ke (M) (atau setidaknya dia tahu tentang mereka). Anda dapat menulis kode untuk menangani sentuhan dalam kasus itu dan mengembalikan objek yang akan menerima sentuhan.

Lebih khusus: tujuan hitTest:withEvent: adalah mengembalikan objek yang seharusnya menerima klik. Jadi, di (M) Anda mungkin menulis kode seperti ini:

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

Saya masih sangat baru dengan metode ini dan masalah ini, jadi jika ada cara yang lebih efisien atau benar untuk menulis kode, silakan komentar.

Saya harap itu membantu orang lain yang menjawab pertanyaan ini nanti. :)


26
2017-08-03 17:39



Di Swift 4

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



Apa yang akan saya lakukan adalah memiliki ButtonView dan MenuView ada pada tingkat yang sama dalam hierarki tampilan dengan menempatkan keduanya dalam sebuah wadah yang bingkainya benar-benar cocok untuk keduanya. Dengan cara ini, wilayah interaktif dari item yang terpotong tidak akan diabaikan karena itu adalah batas-batas superview.


2
2017-08-02 04:07



Jika ada yang membutuhkannya, ini adalah alternatif cepat

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



Jika Anda memiliki banyak subview lain di dalam tampilan induk Anda maka mungkin sebagian besar tampilan interaktif lainnya tidak akan berfungsi jika Anda menggunakan solusi di atas, dalam hal ini Anda dapat menggunakan sesuatu seperti ini (Dalam Swift 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
2017-11-03 06:51



Tempatkan di bawah baris kode ke dalam hierarki tampilan Anda:

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

Untuk klarifikasi lebih lanjut, itu dijelaskan di blog saya: "goaheadwithiphonetech" mengenai "Panggilan Kustom: Tombol bukanlah masalah yang dapat diklik".

Saya harap itu membantu Anda ... !!!


-1
2018-05-19 10:50