透明なUIViewの背後にあるボタンをクリックするにはどうすればよいですか?


176

1つのサブビューを持つビューコントローラーがあるとします。サブビューは画面の中央を占め、すべての側面に100 pxのマージンがあります。次に、そのサブビュー内をクリックするための小さなものをたくさん追加します。新しいフレームを利用するためだけにサブビューを使用しています(サブビュー内のx = 0、y = 0は実際には親ビューでは100,100です)。

次に、サブビューの背後にメニューなどがあると想像してください。ユーザーがサブビュー内の「小さなもの」を選択できるようにしたいのですが、そこに何もない場合は、その背後にあるボタンにタッチします(とにかく背景がクリアなので)。

これどうやってするの?touchesBeganが通過したように見えますが、ボタンは機能しません。


1
透明な(alpha 0)UIViewはタッチイベントに応答しないことになっていると思いましたか?
Evadne Wu

1
私はそのためだけに小さなクラスを書きました。(回答に例を追加)。UIButton半透明の下にあるをクリックしてUIViewも、透明部分がUIViewタッチイベントに応答するため、受け入れられる回答よりも少し良い解決策があります。
Segev 2015

回答:


314

コンテナーのカスタムビューを作成し、pointInside:メッセージをオーバーライドして、次のように、ポイントが適格な子ビュー内にない場合にfalseを返します。

迅速:

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

目的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

このビューをコンテナとして使用すると、そのすべての子がタッチを受けることができますが、ビュー自体はイベントに対して透過的です。


4
面白い。もっとレスポンダーチェーンに飛び込む必要があります。
ショーンクラークヘス

1
ビューが表示されているかどうかも確認する必要があり、テストする前にサブビューが表示されているかどうかも確認する必要があります。
レイジーコーダー

2
残念ながら、このトリックは、ビューを変更できないtabBarControllerでは機能しません。誰かがこのビューをイベントに対しても透過的にするアイデアを持っていますか?
シリルチャンピア2012

1
また、ビューのアルファを確認する必要があります。多くの場合、アルファをゼロに設定してビューを非表示にします。アルファがゼロのビューは、非表示のビューのように機能する必要があります。
James Andrews、

2
私は迅速なバージョンを行いました:多分あなたは可視性のための答えにそれを含めることができますgist.github.com/eyeballz/17945454447c7ae766cb
eyeballz

107

私も使用しています

myView.userInteractionEnabled = NO;

サブクラス化する必要はありません。正常に動作します。


76
これにより、サブビューのユーザー操作も無効になります
pixelfreak

@pixelfreakが述べたように、これはすべての場合に理想的な解決策ではありません。そのビューのサブビューでの操作が引き続き必要な場合は、そのフラグを有効にしておく必要があります。
アウグストカルモ2018年

20

アップルから:

イベント転送は、一部のアプリケーションで使用される手法です。タッチイベントを転送するには、別のレスポンダオブジェクトのイベント処理メソッドを呼び出します。これは効果的なテクニックですが、注意して使用する必要があります。UIKitフレームワークのクラスは、それらにバインドされていないタッチを受信するように設計されていません...アプリケーション内の他のレスポンダに条件付きでタッチを転送する場合、これらのレスポンダはすべて、UIViewの独自のサブクラスのインスタンスである必要があります。

りんごのベストプラクティス

イベントをレスポンダチェーンに(nextResponderを介して)明示的に送信しないでください。代わりに、スーパークラスの実装を呼び出し、UIKitにレスポンダーチェーントラバーサルを処理させます。

代わりに、オーバーライドできます。

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

UIViewサブクラスで、そのタッチをレスポンダーチェーン(つまり、何も含まれていないビューの背後にあるビューへのIE)に送りたい場合はNOを返します。


1
あなたが引用しているドキュメントへのリンクと一緒に引用することは素晴らしいことです。
morgancodes、

1
ドキュメントの関連する場所へのリンクを追加しました
jackslash '13 / 07/13

1
~~オーバーライドがどのように見える必要があるかを示していただけますか?~~ これがどのように見えるかについての別の質問回答を見つけました。文字通りただ返すだけNOです。
kontur 2015年

これはおそらく受け入れられる答えになるはずです。これが最も簡単な方法であり、推奨される方法です。
エクアドル

8

はるかに簡単な方法は、インターフェイスビルダーで有効になっているユーザー操作を「チェック解除」することです。「ストーリーボードを使用している場合」

ここに画像の説明を入力してください


6
他の人が同様の答えで指摘したように、これは基礎となるビューでタッチイベントを無効にするため、この場合は役に立ちません。つまり、この設定を有効にするか無効にするかに関係なく、そのビューの下のボタンはタップできません。
auco

6

Johnが投稿したものを基に、タッチイベントがボタンを除くビューのすべてのサブビューを通過できるようにする例を次に示します。

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

ビューが表示されているかどうか、およびサブビューが表示されているかどうかを確認する必要があります。
Lazy Coder

完璧なソリューション!さらに簡単な方法は、タッチイベントをビューの下に転送する場合にオーバーライドするだけのUIViewサブクラスを作成することです。PassThroughView-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { return YES; }
auco

6

最近、私はそれだけで私を助けるクラスを書きました。UIButtonまたはのカスタムクラスとして使用するUIView、透明なピクセルで実行されたタッチイベントが渡されます。

UIButton半透明の下にあるをクリックしてUIViewも、透明な部分がUIViewタッチイベントに応答するため、このソリューションは受け入れられた回答よりもいくらか優れています。

GIF

GIFで確認できるように、キリンボタンは単純な長方形ですが、透明な領域でのタッチイベントはUIButton下の黄色に渡されます。

クラスにリンク


1
このソリューションは機能する可能性がありますが、ソリューションの関連部分を回答内に配置することをお勧めします。
公園。

4

上位投票のソリューションは私にとって完全に機能していませんでした、TabBarControllerが階層に含まれていたため(コメントの1つが指摘しているように)、実際にはUIの一部へのタッチを渡していましたが、 tableViewのタッチイベントをインターセプトする機能。タッチを無視して、そのビューのサブビューに処理を任せるビューで、hitTestを最終的に上書きしたのは何ですか。

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

スウィフト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

「iPhoneアプリケーションプログラミングガイド」によると:

タッチイベントの配信をオフにします。デフォルトでは、ビューはタッチイベントを受け取りますが、そのuserInteractionEnabledプロパティをNOに設定して、イベントの配信をオフにすることができます。ビューが非表示の場合や透明の場合も、ビューはイベントを受け取りません

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

更新:例を削除-質問を再読...

ボタンがそれを取得する前に、タップを処理している可能性があるビューでジェスチャー処理がありますか?透明なビューがない場合、ボタンは機能しますか?

機能しないコードのコードサンプルはありますか?


はい、通常のタッチはビューの下で機能しますが、UIButtonやその他の要素は機能しないという質問を書きました。
Sean Clark Hess

1

私の知る限り、hitTest:メソッドをオーバーライドすることでこれを実行できるはずです。試しましたが、きちんと動作しませんでした。

最後に、タッチ可能なオブジェクトの周りに一連の透明なビューを作成して、それらがそれを覆わないようにしました。私の問題のちょっとしたハックはこれでうまくいきました。


1

他の回答からヒントを取り、Appleのドキュメントを読んで、問題を解決するためにこのシンプルなライブラリを作成しました:
https : //github.com/natrosoft/NATouchThroughView
タッチを渡す必要があるビューをInterface Builderで簡単に描画できます基礎となるビュー。

Appleのベース実装を直接いじってアプリケーション全体の変更を行うと意図しない結果が生じる可能性があるため、メソッドのスウィズリングは過剰であり、量産コードで行うのは非常に危険だと思います。

デモプロジェクトがあり、うまくいけば、READMEが何をすべきかを説明するのに役立ちます。OPに対処するには、ボタンを含む明確なUIViewをInterface BuilderでNATouchThroughViewクラスに変更します。次に、タップ可能にするメニューをオーバーレイする明確なUIViewを見つけます。そのUIViewをInterface BuilderでクラスNARootTouchThroughViewに変更します。これらのタッチを基になるビューコントローラーに渡す場合は、ビューコントローラーのルートUIViewにすることもできます。デモプロジェクトをチェックして、動作を確認してください。とてもシンプルで安全で非侵襲的です


1

これを行うためのカテゴリを作成しました。

少しメソッドがスウィズリングし、ビューは黄金色です。

ヘッダー

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

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

@end

実装ファイル

#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

私のSwizz.hとSwizz.mを追加する必要があります

位置ここで

その後、UIView + PassthroughParent.hを{Project} -Prefix.pchファイルにインポートするだけで、すべてのビューにこの機能が追加されます。

すべてのビューはポイントを取得しますが、空白スペースは取得しません。

クリアな背景を使用することもお勧めします。

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

編集

私は自分のプロパティバッグを作成しましたが、以前は含まれていませんでした。

ヘッダーファイル

// NSObject+PropertyBag.h

#import <Foundation/Foundation.h>

@interface NSObject (PropertyBag)

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

@end

実装ファイル

// 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メソッドは、(passthroughparentやswizzなどを使用しなくても、passthroughPointInsideをpointInsideに名前を変更するだけで)うまく機能しています。
ベンジャミンピエッテ2013年

propertyValueForKeyとは何ですか?
Skotch 2013年

1
うーん。これまで誰もそれを指摘しなかった。クラス拡張のプロパティを保持するカスタムポインターディクショナリを作成しました。ここに含めるためにそれを見つけることができるかどうかわかりません。
Lazy Coder 2013年

スウィズリングメソッドは、まれなケースと開発者の楽しみのためのデリケートなハッキングソリューションとして知られています。次のOSアップデートでアプリが壊れたりクラッシュしたりする可能性が非常に高いため、App Storeは安全ではありません。なぜオーバーライドしないのpointInside: withEvent:ですか?
auco

0

カテゴリまたはサブクラスのUIViewを使用するのが面倒な場合は、ボタンを前面に移動して、透明なビューの前に配置することもできます。これは、アプリケーションによっては常に可能であるとは限りませんが、私にとってはうまくいきました。ボタンはいつでも元に戻すか、非表示にできます。


2
こんにちは、スタックオーバーフローへようこそ。新規ユーザーとしてのあなたへのヒント:あなたは自分の口調に気をつけたいかもしれません。「気にならなければ」のようなフレーズは避けます。それは
降順の

頭を上げてくれてありがとう..それは怠惰というより怠惰な立場からだったが、ポイントを取った。
Shaky Sayag、2015年

0

これを試して

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

Aセットお試しくださいbackgroundColor、あなたのtransparentViewようにUIColor(white:0.000, alpha:0.020)。次に、touchesBegan/ touchesMovedメソッドでタッチイベントを取得できます。ビューが初期化されている場所の下にコードを配置します。

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

0

私はオーバーライドメソッドポイントの代わりにそれを使用します(内部:CGPoint、with:UIEvent)

  override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        guard self.point(inside: point, with: event) else { return nil }
        return self
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.