Bagaimana saya bisa mengklik tombol di belakang UIView transparan?

176

Katakanlah kita memiliki pengontrol tampilan dengan satu tampilan sub. subview mengambil bagian tengah layar dengan margin 100 px di semua sisi. Kami kemudian menambahkan banyak hal kecil untuk diklik di dalam subview itu. Kami hanya menggunakan subview untuk memanfaatkan frame baru (x = 0, y = 0 di dalam subview sebenarnya 100.100 dalam tampilan induk).

Lalu, bayangkan kita memiliki sesuatu di balik layar, seperti menu. Saya ingin pengguna dapat memilih "hal-hal kecil" di subview, tetapi jika tidak ada, saya ingin sentuhan untuk melewatinya (karena latar belakangnya jelas) ke tombol di belakangnya.

Bagaimana saya bisa melakukan ini? Sepertinya sentuhan Mulai, tapi tombol tidak berfungsi.

Sean Clark Hess
sumber
1
Saya pikir transparan (alpha 0) UIViews tidak seharusnya merespons acara sentuh?
Evadne Wu
1
Saya menulis kelas kecil hanya untuk itu. (Menambahkan contoh dalam jawaban). Solusi di sana agak lebih baik daripada jawaban yang diterima karena Anda masih bisa mengklik UIButtonyang di bawah semi transparan UIViewsedangkan bagian yang tidak transparan UIViewmasih akan menanggapi acara sentuh.
Segev

Jawaban:

314

Buat tampilan khusus untuk wadah Anda dan timpa pesan pointInside: untuk mengembalikan false ketika titik tersebut tidak dalam tampilan anak yang memenuhi syarat, seperti ini:

Cepat:

class PassThroughView: UIView {
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        for subview in subviews {
            if !subview.isHidden && subview.isUserInteractionEnabled && subview.point(inside: convert(point, to: subview), with: event) {
                return true
            }
        }
        return false
    }
}

Sasaran C:

@interface PassthroughView : UIView
@end

@implementation PassthroughView
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    for (UIView *view in self.subviews) {
        if (!view.hidden && view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event])
            return YES;
    }
    return NO;
}
@end

Menggunakan tampilan ini sebagai wadah akan memungkinkan anak-anaknya untuk menerima sentuhan tetapi tampilan itu sendiri akan transparan untuk acara.

John Stephen
sumber
4
Menarik. Saya perlu lebih banyak terjun ke rantai responden.
Sean Clark Hess
1
Anda perlu memeriksa apakah tampilan juga terlihat, maka Anda juga perlu memeriksa apakah subview terlihat sebelum mengujinya.
The Lazy Coder
2
sayangnya trik ini tidak berfungsi untuk tabBarController, yang tampilan tidak dapat diubah. Adakah yang punya ide untuk membuat tampilan ini transparan untuk acara juga?
cyrilchampier
1
Anda juga harus memeriksa alfa tampilan. Cukup sering saya akan menyembunyikan tampilan dengan mengatur alpha ke nol. Tampilan dengan nol alfa harus bertindak seperti tampilan tersembunyi.
James Andrews
2
Saya melakukan Versi cepat: mungkin Anda dapat memasukkannya dalam jawaban untuk visibilitas gist.github.com/eyeballz/17945454447c7ae766cb
eyeballz
107

Saya juga menggunakan

myView.userInteractionEnabled = NO;

Tidak perlu subkelas. Bekerja dengan baik.

akw
sumber
76
Ini juga akan menonaktifkan interaksi pengguna untuk subview apa pun
pixelfreak
Seperti @pixelfreak sebutkan, ini bukan solusi ideal untuk semua kasus. Kasus-kasus di mana interaksi pada subview dari tampilan itu masih diinginkan mengharuskan bendera untuk tetap diaktifkan.
Augusto Carmo
20

Dari Apple:

Penerusan acara adalah teknik yang digunakan oleh beberapa aplikasi. Anda meneruskan sentuhan peristiwa dengan menerapkan metode penanganan acara dari objek responden lain. Meskipun ini bisa menjadi teknik yang efektif, Anda harus menggunakannya dengan hati-hati. Kelas-kelas kerangka kerja UIKit tidak dirancang untuk menerima sentuhan yang tidak terikat padanya .... Jika Anda ingin meneruskan sentuhan bersyarat ke responden lain dalam aplikasi Anda, semua responden ini harus merupakan contoh dari subkelas Anda sendiri dari UIView.

Praktik Terbaik Apel :

Jangan secara eksplisit mengirim peristiwa ke rantai responden (via nextResponder); sebaliknya, aktifkan implementasi superclass dan biarkan UIKit menangani traversal rantai responden.

alih-alih, Anda dapat mengganti:

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event

di subkelas UIView Anda dan kembalikan NO jika Anda ingin sentuhan itu dikirim ke rantai responden (yaitu untuk melihat di belakang pandangan Anda tanpa apa-apa di dalamnya).

jackslash
sumber
1
Akan luar biasa memiliki tautan ke dokumen yang Anda kutip, beserta kutipannya sendiri.
kode morg
1
Saya menambahkan tautan ke tempat-tempat yang relevan dalam dokumentasi untuk Anda
jackslash
1
~~ Bisakah Anda menunjukkan bagaimana penimpaan itu harus terlihat? ~~ Menemukan jawaban dalam pertanyaan lain tentang bagaimana ini akan terlihat seperti; ini benar-benar baru saja kembali NO;
kontur
Ini mungkin jawaban yang diterima. Ini cara paling sederhana dan cara yang direkomendasikan.
Ekuador
8

Cara yang jauh lebih sederhana adalah dengan "Tidak Periksa" Interaksi Pengguna Diaktifkan di pembuat antarmuka. "Jika kamu menggunakan storyboard"

masukkan deskripsi gambar di sini

Jeff
sumber
6
seperti yang ditunjukkan orang lain dalam jawaban yang serupa, ini tidak membantu dalam kasus ini, karena juga menonaktifkan acara sentuh dalam tampilan yang mendasarinya. Dengan kata lain: tombol di bawah tampilan yang tidak dapat diketuk, terlepas dari apakah Anda mengaktifkan atau menonaktifkan pengaturan ini.
auco
6

Berdasarkan apa yang diposkan John, berikut adalah contoh yang akan memungkinkan acara sentuh untuk melewati semua tampilan sub tampilan kecuali untuk tombol:

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    // Allow buttons to receive press events.  All other views will get ignored
    for( id foundView in self.subviews )
    {
        if( [foundView isKindOfClass:[UIButton class]] )
        {
            UIButton *foundButton = foundView;

            if( foundButton.isEnabled  &&  !foundButton.hidden &&  [foundButton pointInside:[self convertPoint:point toView:foundButton] withEvent:event] )
                return YES;
        }        
    }
    return NO;
}
Tod Cunningham
sumber
Perlu memeriksa apakah tampilan terlihat, dan apakah subview terlihat.
The Lazy Coder
Solusi sempurna! Pendekatan yang bahkan lebih sederhana adalah membuat UIViewsubkelas PassThroughViewyang hanya menimpa -(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { return YES; }jika Anda ingin meneruskan acara sentuh apa pun di bawah tampilan.
auco
6

Akhir-akhir ini saya menulis sebuah kelas yang akan membantu saya dengan hal itu. Menggunakannya sebagai kelas khusus untuk UIButtonatau UIViewakan melewati acara sentuh yang dieksekusi pada piksel transparan.

Solusi ini agak lebih baik daripada jawaban yang diterima karena Anda masih bisa mengklik UIButtonyang di bawah semi transparan UIViewsedangkan bagian yang tidak transparan UIViewmasih akan merespons acara sentuh.

GIF

Seperti yang dapat Anda lihat di GIF, tombol Giraffe adalah persegi panjang sederhana tetapi peristiwa sentuh pada area transparan diteruskan ke kuning di UIButtonbawahnya.

Tautan ke kelas

Segev
sumber
1
Sementara solusi ini mungkin berhasil, lebih baik untuk menempatkan bagian-bagian yang relevan dari solusi Anda dalam jawabannya.
Koen.
4

Solusi pilihan teratas tidak sepenuhnya berfungsi untuk saya, saya kira itu karena saya memiliki TabBarController ke dalam hierarki (seperti yang ditunjukkan salah satu komentar) itu sebenarnya meneruskan sentuhan ke beberapa bagian UI tetapi itu mengacaukan saya Kemampuan tableView untuk mencegat acara sentuh, yang akhirnya melakukannya adalah menabrak hitTest dalam tampilan yang ingin saya abaikan sentuhan dan biarkan subview dari tampilan itu menanganinya.

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    UIView *view = [super hitTest:point withEvent:event];
    if (view == self) {
        return nil; //avoid delivering touch events to the container view (self)
    }
    else{
        return view; //the subviews will still receive touch events
    }
}
PakitoV
sumber
3

Cepat 3

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    for subview in subviews {
        if subview.frame.contains(point) {
            return true
        }
    }
    return false
}
Barath
sumber
2

Menurut 'Panduan Pemrograman Aplikasi iPhone':

Mematikan pengiriman acara sentuh. Secara default, sebuah tampilan menerima event sentuh, tetapi Anda dapat mengatur properti userInteractionEnabled menjadi TIDAK untuk mematikan pengiriman acara. Tampilan juga tidak menerima acara jika disembunyikan atau transparan .

http://developer.apple.com/iphone/library/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/EventHandling/EventHandling.html

Diperbarui : Contoh dihapus - baca kembali pertanyaan ...

Apakah Anda memiliki gerakan memproses pada pandangan yang mungkin memproses keran sebelum tombol mendapatkannya? Apakah tombol berfungsi ketika Anda tidak memiliki tampilan transparan di atasnya?

Adakah contoh kode dari kode yang tidak berfungsi?

LK.
sumber
Ya, saya menulis dalam pertanyaan saya bahwa sentuhan normal berfungsi di bawah tampilan, tetapi UIButtons dan elemen lainnya tidak berfungsi.
Sean Clark Hess
1

Sejauh yang saya tahu, Anda seharusnya dapat melakukan ini dengan mengesampingkan metode hitTest : . Saya memang mencobanya tetapi tidak bisa berfungsi dengan benar.

Pada akhirnya saya membuat serangkaian pandangan transparan di sekitar objek yang dapat disentuh sehingga mereka tidak menutupinya. Sedikit peretasan untuk masalah saya ini bekerja dengan baik.

Liam
sumber
1

Mengambil kiat dari jawaban lain dan membaca dokumentasi Apple, saya membuat pustaka sederhana ini untuk menyelesaikan masalah Anda:
https://github.com/natrosoft/NATouchThroughView
Memudahkan untuk menggambar tampilan di Interface Builder yang harus melalui sentuhan hingga pandangan yang mendasarinya.

Saya pikir metode swizzling berlebihan dan sangat berbahaya untuk dilakukan dalam kode produksi karena Anda secara langsung mengacaukan implementasi basis Apple dan membuat perubahan di seluruh aplikasi yang dapat menyebabkan konsekuensi yang tidak diinginkan.

Ada proyek demo dan semoga README melakukan pekerjaan dengan baik menjelaskan apa yang harus dilakukan. Untuk mengatasi OP, Anda akan mengubah UIView jelas yang berisi tombol ke kelas NATouchThroughView di Interface Builder. Kemudian temukan UIView yang jelas yang menampilkan menu yang ingin Anda ketuk. Ubah UIView ke kelas NARootTouchThroughView di Interface Builder. Ia bahkan bisa menjadi root UIView pada pengontrol tampilan Anda jika Anda bermaksud sentuhan itu untuk melewati ke pengontrol tampilan yang mendasarinya. Lihat proyek demo untuk melihat cara kerjanya. Ini sangat sederhana, aman, dan non-invasif

n8tr
sumber
1

Saya membuat kategori untuk melakukan ini.

sebuah metode kecil berputar-putar dan pemandangannya berwarna emas.

Header

//UIView+PassthroughParent.h
@interface UIView (PassthroughParent)

- (BOOL) passthroughParent;
- (void) setPassthroughParent:(BOOL) passthroughParent;

@end

File implementasi

#import "UIView+PassthroughParent.h"

@implementation UIView (PassthroughParent)

+ (void)load{
    Swizz([UIView class], @selector(pointInside:withEvent:), @selector(passthroughPointInside:withEvent:));
}

- (BOOL)passthroughParent{
    NSNumber *passthrough = [self propertyValueForKey:@"passthroughParent"];
    if (passthrough) return passthrough.boolValue;
    return NO;
}
- (void)setPassthroughParent:(BOOL)passthroughParent{
    [self setPropertyValue:[NSNumber numberWithBool:passthroughParent] forKey:@"passthroughParent"];
}

- (BOOL)passthroughPointInside:(CGPoint)point withEvent:(UIEvent *)event{
    // Allow buttons to receive press events.  All other views will get ignored
    if (self.passthroughParent){
        if (self.alpha != 0 && !self.isHidden){
            for( id foundView in self.subviews )
            {
                if ([foundView alpha] != 0 && ![foundView isHidden] && [foundView pointInside:[self convertPoint:point toView:foundView] withEvent:event])
                    return YES;
            }
        }
        return NO;
    }
    else {
        return [self passthroughPointInside:point withEvent:event];// Swizzled
    }
}

@end

Anda perlu menambahkan Swizz.h dan Swizz.m saya

terletak di sini

Setelah itu, Anda cukup Mengimpor UIView + PassthroughParent.h di file {Project} -Prefix.pch Anda, dan setiap tampilan akan memiliki kemampuan ini.

setiap tampilan akan mengambil poin, tetapi tidak ada ruang kosong yang mau.

Saya juga merekomendasikan menggunakan latar belakang yang jelas.

myView.passthroughParent = YES;
myView.backgroundColor = [UIColor clearColor];

EDIT

Saya membuat tas properti saya sendiri, dan itu belum termasuk sebelumnya.

File tajuk

// NSObject+PropertyBag.h

#import <Foundation/Foundation.h>

@interface NSObject (PropertyBag)

- (id) propertyValueForKey:(NSString*) key;
- (void) setPropertyValue:(id) value forKey:(NSString*) key;

@end

File Implementasi

// NSObject+PropertyBag.m

#import "NSObject+PropertyBag.h"



@implementation NSObject (PropertyBag)

+ (void) load{
    [self loadPropertyBag];
}

+ (void) loadPropertyBag{
    @autoreleasepool {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Swizz([NSObject class], NSSelectorFromString(@"dealloc"), @selector(propertyBagDealloc));
        });
    }
}

__strong NSMutableDictionary *_propertyBagHolder; // Properties for every class will go in this property bag
- (id) propertyValueForKey:(NSString*) key{
    return [[self propertyBag] valueForKey:key];
}
- (void) setPropertyValue:(id) value forKey:(NSString*) key{
    [[self propertyBag] setValue:value forKey:key];
}
- (NSMutableDictionary*) propertyBag{
    if (_propertyBagHolder == nil) _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100];
    NSMutableDictionary *propBag = [_propertyBagHolder valueForKey:[[NSString alloc] initWithFormat:@"%p",self]];
    if (propBag == nil){
        propBag = [NSMutableDictionary dictionary];
        [self setPropertyBag:propBag];
    }
    return propBag;
}
- (void) setPropertyBag:(NSDictionary*) propertyBag{
    if (_propertyBagHolder == nil) _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100];
    [_propertyBagHolder setValue:propertyBag forKey:[[NSString alloc] initWithFormat:@"%p",self]];
}

- (void)propertyBagDealloc{
    [self setPropertyBag:nil];
    [self propertyBagDealloc];//Swizzled
}

@end
The Lazy Coder
sumber
Metode passthroughPointInside bekerja dengan baik untuk saya (bahkan tanpa menggunakan passthroughparent atau hal-hal swizz - cukup ganti nama passthroughPointInside ke pointInside), terima kasih banyak.
Benjamin Piette
Apa itu propertyValueForKey?
Skotch
1
Hmm. Tidak ada yang menunjukkan hal itu sebelumnya. Saya membuat kamus pointer khusus yang menampung properti dalam ekstensi kelas. Saya akan lihat apakah saya dapat memasukkannya di sini.
The Lazy Coder
Metode swizzling dikenal sebagai solusi peretasan yang halus untuk kasus yang jarang terjadi dan kesenangan pengembang. Ini jelas bukan App Store aman karena sangat mungkin untuk menghancurkan dan menghancurkan aplikasi Anda dengan pembaruan OS berikutnya. Kenapa tidak ditimpa saja pointInside: withEvent:?
auco
0

Jika Anda tidak dapat repot-repot menggunakan UIView kategori atau subkelas, Anda juga bisa membawa tombol ke depan sehingga berada di depan tampilan transparan. Ini tidak akan selalu mungkin tergantung pada aplikasi Anda, tetapi itu berhasil untuk saya. Anda selalu dapat mengembalikan tombol atau menyembunyikannya.

Sayag terguncang
sumber
2
Hai, selamat datang untuk menumpuk overflow. Hanya petunjuk bagi Anda sebagai pengguna baru: Anda mungkin ingin berhati-hati dengan nada suara Anda. Saya akan menghindari frasa seperti "jika Anda tidak dapat mengganggu"; itu mungkin muncul sebagai merendahkan
nomistik
Terima kasih untuk kepala atas .. Itu lebih dari sudut pandang malas daripada merendahkan, tetapi titik diambil.
Shaked Sayag
0

Coba ini

class PassthroughToWindowView: UIView {
        override func test(_ point: CGPoint, with event: UIEvent?) -> UIView? {
            var view = super.hitTest(point, with: event)
            if view != self {
                return view
            }

            while !(view is PassthroughWindow) {
                view = view?.superview
            }
            return view
        }
    } 
tBug
sumber
0

Coba atur a backgroundColortransparentView sebagai Anda UIColor(white:0.000, alpha:0.020). Kemudian Anda bisa mendapatkan acara sentuh di touchesBegan/ touchesMovedmetode. Tempatkan kode di bawah ini di tempat tampilan Anda:

self.alpha = 1
self.backgroundColor = UIColor(white: 0.0, alpha: 0.02)
self.isMultipleTouchEnabled = true
self.isUserInteractionEnabled = true
Agisight
sumber
0

Saya menggunakan itu alih-alih menimpa titik metode (di dalam: CGPoint, dengan: UIEvent)

  override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        guard self.point(inside: point, with: event) else { return nil }
        return self
    }
Wei
sumber