Şeffaf bir UIView'in arkasındaki bir düğmeyi nasıl tıklayabilirim?


177

Diyelim ki bir alt görünüme sahip bir görünüm denetleyicimiz var. alt görünüm, ekranın ortasını kaplar ve her tarafta 100 piksel kenar boşluğu bırakır. Ardından, bu alt görünümün içine tıklamak için birkaç küçük şey ekliyoruz. Alt görünümü yalnızca yeni çerçeveden yararlanmak için kullanıyoruz (alt görünüm içindeki x = 0, y = 0 aslında üst görünümde 100.100).

Ardından, alt menünün arkasında bir menü gibi bir şey olduğunu hayal edin. Kullanıcı alt görünümünde "küçük şeyler" herhangi birini seçebilmek istiyorum, ama orada hiçbir şey varsa, dokunmadan (arka plan zaten açık olduğu için) arkasındaki düğmelere geçmek istiyorum.

Bunu nasıl yapabilirim? Dokunuşlara benziyorBaşarı geçiyor, ancak düğmeler çalışmıyor.


1
Şeffaf (alfa 0) UIViews'ın dokunma olaylarına yanıt vermesi gerekmiyor muydu?
Evadne Wu

1
Sadece bunun için küçük bir sınıf yazdım. (Cevaplara bir örnek eklendi). Buradaki çözüm, kabul edilen cevaptan biraz daha iyi çünkü hala UIButtonyarı şeffaf olanı tıklayabilirken , şeffaf UIViewolmayan kısmı ise UIViewdokunma olaylarına cevap verecektir.
Segev

Yanıtlar:


315

Kapsayıcınız için özel bir görünüm oluşturun ve nokta uygun bir alt görünümde olmadığında false olarak dönmek için pointInside: iletisini geçersiz kılın, örneğin:

Swift:

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

Hedef 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

Bu görüşü bir kapsayıcı olarak kullanmak, çocuklarından herhangi birinin dokunma almasına izin verecektir, ancak görünümün kendisi olaylar için şeffaf olacaktır.


4
İlginç. Cevaplayıcı zincirine daha fazla dalmam gerekiyor.
Sean Clark Hess

1
görünümün de görünüp görünmediğini kontrol etmeniz ve ardından alt görüntülemeleri test etmeden önce görünür olup olmadığını kontrol etmeniz gerekir.
Tembel Kodlayıcı

2
ne yazık ki bu hile, görünümün değiştirilemediği bir tabBarController için çalışmaz. Bu görüşü olaylara da şeffaf hale getirme fikri olan var mı?
cyrilchampier

1
Ayrıca görünümün alfa değerini de kontrol etmelisiniz. Sıklıkla alfa sıfıra ayarlayarak bir görünümü gizlerim. Sıfır alfa içeren bir görünüm gizli görünüm gibi davranmalıdır.
James Andrews

2
Hızlı bir Sürüm yaptım: belki görünürlük cevabına
eyeballz

107

Ben de kullanıyorum

myView.userInteractionEnabled = NO;

Alt sınıfa gerek yok. İyi çalışıyor.


76
Bu ayrıca herhangi bir alt görünüm için kullanıcı etkileşimini devre dışı bırakır
pixelfreak

@ Pixelfreak'in belirttiği gibi, bu tüm durumlar için ideal bir çözüm değildir. Bu görünümün alt görünümlerindeki etkileşimin hala istendiği durumlar, bayrağın etkin kalmasını gerektirir.
Augusto Carmo

20

Apple'dan:

Olay iletme, bazı uygulamalar tarafından kullanılan bir tekniktir. Başka bir yanıtlayıcı nesnesinin olay işleme yöntemlerini çağırarak dokunma olaylarını yönlendirirsiniz. Bu etkili bir teknik olsa da, dikkatli kullanmalısınız. UIKit çerçevesinin sınıfları, kendilerine bağlı olmayan dokunuşları alacak şekilde tasarlanmamıştır ... Başvurunuzdaki diğer yanıtlayıcılara koşullu olarak dokunma iletmek istiyorsanız, bu yanıt verenlerin tümü kendi UIView alt sınıflarınızın örnekleri olmalıdır.

Elma En İyi Uygulaması :

Olayları yanıtlama zincirine açıkça göndermeyin (nextResponder aracılığıyla); bunun yerine, üst sınıf uygulamasını başlatın ve UIKit'in yanıtlayıcı zinciri geçişini işlemesine izin verin.

bunun yerine geçersiz kılabilirsiniz:

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

UIView alt sınıfınızda ve bu dokunuşun yanıtlayıcı zincirine gönderilmesini istiyorsanız NO döndürün (IE, görünümünüzün arkasındaki görünümlere hiçbir şey olmadan).


1
Tekliflerin kendileriyle birlikte teklif ettiğiniz dokümanlara bir bağlantı olması harika olurdu.
morgancodes

1
Sizin için belgelerdeki ilgili yerlere bağlantıları ekledim
jackslash

1
~~ Bu geçersiz kılmanın nasıl görünmesi gerektiğini gösterebilir misiniz? ~~ Bunun nasıl görüneceğine dair başka bir soruda bir cevap buldunuz ; kelimenin tam anlamıyla sadece geri dönüyor NO;
kontur

Bu muhtemelen kabul edilen cevap olmalıdır. Bu en basit ve önerilen yoldur.
Ekvador

8

Çok daha basit bir yol, arayüz oluşturucuda Kullanıcı Etkileşiminin Etkinleştirilmiş "İşaretini Kaldır" seçeneğidir. "Bir storyboard kullanıyorsanız"

resim açıklamasını buraya girin


6
Diğerlerinin de benzer bir cevaba işaret ettikleri gibi, bu durum da, altta yatan görünümdeki dokunma olaylarını da devre dışı bıraktığı için bu durumda yardımcı olmaz. Başka bir deyişle: bu ayarı etkinleştirmeniz veya devre dışı bırakmanız fark etmeksizin, bu görünümün altındaki düğmeye dokunamazsınız.
auco

6

John'un yayınladıklarına dayanarak, dokunma olaylarının düğmeler hariç bir görünümün tüm alt görünümlerinden geçmesine izin verecek bir örnek:

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

Görünümün görünür olup olmadığını ve alt görünümlerin görünür olup olmadığını kontrol etmeniz gerekir.
Tembel Kodlayıcı

Mükemmel çözüm! Daha da basit bir yaklaşım, herhangi bir dokunma olayını görünümlerin altına iletmek istiyorsanız geçersiz kılan bir UIViewalt sınıf oluşturmak olacaktır . PassThroughView-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { return YES; }
auco

6

Son zamanlarda bana bu konuda yardımcı olacak bir ders yazdım. Şeffaf bir piksel üzerinde yürütülen dokunma olaylarını bir UIButtonveya için özel sınıf olarak kullanmak UIView.

Bu çözüm kabul edilen yanıttan biraz daha iyidir çünkü UIButtonyarı saydam olan bir taneyi tıklatabilirken , saydam UIViewolmayan kısmı UIViewdokunma olaylarına hala yanıt verecektir.

GIF

GIF'te görebileceğiniz gibi, Zürafa düğmesi basit bir dikdörtgendir, ancak saydam alanlardaki dokunma olayları UIButtonaltındaki sarıya aktarılır .

Sınıfa bağlantı


1
Bu çözüm işe yarayabilse de, çözümünüzün ilgili kısımlarını cevaba yerleştirmek daha iyidir.
Koen.

4

En çok oy alan çözüm benim için tam olarak işe yaramadı, sanırım çünkü hiyerarşiye bir TabBarController'ım vardı (yorumlardan biri belirtildiği gibi) aslında UI'nin bazı bölümlerine dokunuşları geçiyordu ama kesişme dokunmatik olaylara tableview yeteneği ne nihayet ağır basan bırakmaktı hitTest o görüş sapı onları bir subviews dokunuşları göz ardı ederek izin vermek istiyorum görünümünde

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

3

Hızlı 3

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

2

'İPhone Uygulama Programlama Kılavuzu'na göre:

Dokunma olaylarının dağıtımını kapatma. Varsayılan olarak, bir görünüm dokunma olaylarını alır, ancak olayların dağıtımını kapatmak için userInteractionEnabled özelliğini NO olarak ayarlayabilirsiniz. Bir görünüm, gizlenmişse veya şeffafsa da olay almaz .

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

Güncellendi : Kaldırıldı örneği - soruyu tekrar okuyun ...

Düğme alınmadan önce muslukları işleyebilecek görünümler üzerinde herhangi bir hareket işlemeniz var mı? Düğme, şeffaf bir görünüme sahip olmadığınızda çalışıyor mu?

Çalışmayan kodun kod örnekleri var mı?


Evet, soruma normal dokunuşların altında çalıştığını, ancak UIButton'ların ve diğer öğelerin çalışmadığını yazdım.
Sean Clark Hess

1

Bildiğim kadarıyla, bunu hitTest: yöntemini geçersiz kılarak yapabilmeniz gerekiyor . Denedim ama düzgün çalışması için alamadım.

Sonunda dokunulabilir nesnenin etrafında, onu örtmemek için bir dizi şeffaf görünüm oluşturdum. Benim sorunum için bir parça biraz bu iyi çalıştı.


1

Diğer yanıtlardan ipuçları alarak ve Apple'ın belgelerini okurken, sorununuzu çözmek için bu basit kütüphaneyi oluşturdum:
https://github.com/natrosoft/NATouchThroughView
Arayüz Oluşturucu'da, altta yatan bir görüş.

Apple'ın temel uygulamasıyla doğrudan uğraştığınız ve istenmeyen sonuçlara neden olabilecek uygulama genelinde bir değişiklik yaptığınız için yöntem swizzling'in aşırı kilolu ve üretim kodunda yapılması çok tehlikeli olduğunu düşünüyorum.

Bir demo projesi var ve umarım README ne yapacağını açıklayan iyi bir iş çıkarır. OP'yi ele almak için, düğmeleri içeren açık UIView'i Interface Builder'da NATouchThroughView sınıfına değiştirirsiniz. Ardından, dokunmak istediğiniz menüyü kaplayan net UIView'i bulun. Arabirim Oluşturucu'da bu UIView sınıfını NARootTouchThroughView olarak değiştirin. Bu dokunuşların altında yatan görünüm denetleyicisine geçmeyi planlıyorsanız, görünüm denetleyicinizin kök UIView bile olabilir. Nasıl çalıştığını görmek için demo projesine göz atın. Gerçekten oldukça basit, güvenli ve non-invaziv


1

Bunu yapmak için bir kategori oluşturdum.

küçük bir yöntem döner ve görünümü altın.

Başlık

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

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

@end

Uygulama dosyası

#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

Benim Swizz.h ve Swizz.m eklemeniz gerekecek

bulunduğu Burada

Bundan sonra, UIView + PassthroughParent.h dosyasını {Project} -Prefix.pch dosyanıza içe aktarırsınız ve her görünümde bu özellik olur.

her görünüm puan alacak, ancak boş alan hiçbiri almayacaktır.

Ayrıca net bir arka plan kullanmanızı öneririz.

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

DÜZENLE

Kendi mülk çantamı oluşturdum ve bu daha önce dahil edilmedi.

Başlık dosyası

// NSObject+PropertyBag.h

#import <Foundation/Foundation.h>

@interface NSObject (PropertyBag)

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

@end

Uygulama Dosyası

// 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

passthroughPointInside yöntemi benim için iyi çalışıyor (düz geçişli veya swizz öğelerini kullanmadan bile - sadece passthroughPointInside'yi pointInside olarak yeniden adlandırın), çok teşekkürler.
Benjamin Piette

PropertyValueForKey nedir?
Skotch

1
Hmm. Bunu daha önce kimse işaret etmedi. Sınıf uzantısındaki özellikleri tutan özel bir işaretçi sözlüğü oluşturdum. Buraya eklemek için bulabilir miyim bakacağım.
Tembel Kodlayıcı

Swizzling yöntemlerinin nadir durumlar ve geliştirici eğlencesi için hassas bir hackleme çözümü olduğu bilinmektedir. Kesinlikle App Store güvenli değildir, çünkü bir sonraki işletim sistemi güncellemesiyle uygulamanızı kırıp çökmesi muhtemeldir. Neden sadece geçersiz kılmıyorsunuz pointInside: withEvent:?
auco

0

Bir kategori veya alt sınıf UIView kullanmaya zahmet edemiyorsanız, düğmeyi saydam görünümün önünde olacak şekilde öne getirebilirsiniz. Bu, uygulamanıza bağlı olarak her zaman mümkün olmayacaktır, ancak benim için çalıştı. Düğmeyi her zaman tekrar geri getirebilir veya gizleyebilirsiniz.


2
Merhaba, taşma yığını için hoş geldiniz. Yeni bir kullanıcı olarak size bir ipucu: tonunuza dikkat etmek isteyebilirsiniz. "Rahatsız edemezseniz" gibi ifadelerden kaçınırdım; küçümseyici gibi görünebilir
15'te

Heads up için teşekkürler .. Bu tembel bir bakış açısından küçümseyen, ama alınan nokta.
Sallanan Sayag

0

Bunu dene

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

0

Set a deneyin backgroundColorsenin içinde transparentViewolarak UIColor(white:0.000, alpha:0.020). Sonra touchesBegan/ touchesMovedyöntemlerinde dokunma olayları alabilirsiniz . Aşağıdaki kodu, görünümünüzün yazıldığı bir yere yerleştirin:

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

0

Bunu geçersiz kılma yöntem noktası yerine kullanın (inside: CGPoint, with: UIEvent)

  override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        guard self.point(inside: point, with: event) else { return nil }
        return self
    }
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.