iOS-ビューを介してすべてのタッチを転送します


141

他の多くのビューの上にオーバーレイされたビューがあります。私はオーバーリーを使用して画面上のいくつかのタッチを検出していますが、それ以外の場合、スクロールビューなど、その下にある他のビューの動作を停止させたくありません。どのようにすればすべてのタッチを転送できますか?このオーバーレイビュー?UIViewのサブカルスです。


2
問題を解決しましたか?はいの場合は、同じ問題に直面しているため、他の人が解決策を見つけやすくするために、回答を受け入れてください。
Khushbu Desai 2018

この回答は適切に機能しますstackoverflow.com/a/4010809/4833705
Lance Samaria

回答:


121

ユーザーの操作を無効にするだけで十分でした。

Objective-C:

myWebView.userInteractionEnabled = NO;

迅速:

myWebView.isUserInteractionEnabled = false

これは、ボタンのサブビューがある状況で機能しました。サブビューでの操作を無効にすると、ボタンが機能しました。
simple_code 2018

2
Swift 4でmyWebView.isUserInteractionEnabled = false
Louis 'LYRO' Dupont

117

オーバーレイビューからその下のビューにタッチを渡すには、UIViewに次のメソッドを実装します。

Objective-C:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    NSLog(@"Passing all touches to the next view (if any), in the view stack.");
    return NO;
}

スウィフト5:

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    print("Passing all touches to the next view (if any), in the view stack.")
    return false
}

3
私は同じ問題を抱えていますが、ジェスチャーでビューをズーム/回転/移動している場合、それは「はい」を返し、残りのイベントは「いいえ」を返す必要があります。どうすればこれを達成できますか?
Minakshi 2013

@Minakshiはまったく別の質問です。新しい質問をしてください。
Fattie

ただし、この単純なアプローチでは、問題のレイヤーの上にボタンなどを配置できないことに注意してください。
Fattie

56

これは古いスレッドですが、検索で見つかったので、2cを追加すると思いました。サブビューのあるUIViewをカバーしていて、サブビューの1つにヒットするタッチのみをインターセプトしたいので、PixelCloudStの回答を変更しました

-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    for (UIView* subview in self.subviews ) {
        if ( [subview hitTest:[self convertPoint:point toView:subview] withEvent:event] != nil ) {
            return YES;
        }
    }
    return NO;
}

1
カスタムUIViewサブクラス(または、UIImageViewなどのその他のUIViewサブクラスのサブクラス)を作成し、この関数をオーバーライドする必要があります。本質的に、サブクラスは.hファイルに完全に空の '@interface'を持つことができ、上の関数は '@implementation'のコンテンツ全体になります。
祝祭日2014年

おかげで、これは、背後のビューで処理するために、UIViewの空のスペースにタッチを渡すために必要なものです。+100
ダニー

40

@fresidue回答の改良版。このUIViewサブクラスは、サブビューの外側にタッチを渡す透明なビューとして使用できます。Objective-Cでの実装:

@interface PassthroughView : UIView

@end

@implementation PassthroughView

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

@end

そしてSwiftでは:

class PassthroughView: UIView {
  override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    return subviews.contains(where: {
      !$0.isHidden
      && $0.isUserInteractionEnabled
      && $0.point(inside: self.convert(point, to: $0), with: event)
    })
  }
}

ヒント:

次に、大きな「ホルダー」パネルがあり、その後ろにテーブルビューがあるとしましょう。「ホルダー」パネルを作成しPassthroughViewます。これで機能し、テーブルを「ホルダー」を介してスクロールできます。

だが!

  1. 「ホルダー」パネルの上には、いくつかのラベルまたはアイコンがあります。もちろん、ユーザーインタラクションがオフになっていることを示す必要があります。

  2. 「ホルダー」パネルの上にいくつかのボタンがあります。忘れないでください、もちろんそれらは単に有効になっているユーザー操作をマークする必要があります!

  3. 使用するビュー-やや紛らわしい、「ホルダー」自体ことPassthroughViewには-ユーザーとの対話を有効にマークされている必要がありONを!それがありますON !! (それ以外の場合、PassthroughViewのコードが呼び出されることはありません。)


1
私はSwiftのソリューションを使用しました。iOS 11で動作していますが、iOS 10で毎回クラッシュします。不正なアクセスを示しています。何か案が?
Soumen

あなたは私の日を節約しました、それはタッチを下のUIViewsに渡すためにうまくいきました。
AaoIi

1
次も確認することを忘れないでください$0.isUserInteractionEnabled
Sean Thielen

7

UIStackViewでも同様の問題がありました(他のビューでもかまいません)。私の構成は次のとおりでした: containerViewとStackViewを備えたビューコントローラー

これは、背景に配置する必要のあるコンテナがあり、側面にボタンがある古典的なケースです。レイアウトの目的で、UIStackViewにボタンを含めましたが、今では、stackViewの中央(空)の部分がタッチをインターセプトします:-(

私がしたことは、タッチ可能でなければならないsubViewを定義するプロパティを持つUIStackViewのサブクラスを作成することです。サイドボタン(* viewsWithActiveTouch *配列に含まれる)へのタッチはボタンに与えられ、スタックビューでのこれらのビュー以外のタッチはインターセプトされないため、スタックの下にあるものに渡されます見る。

/** Subclass of UIStackView that does not accept touches, except for specific subviews given in the viewsWithActiveTouch array */
class NoTouchStackView: UIStackView {

  var viewsWithActiveTouch: [UIView]?

  override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {

    if let activeViews = viewsWithActiveTouch {
        for view in activeViews {
           if CGRectContainsPoint(view.frame, point) {
                return view

           }
        }
     }
     return nil
   }
}

6

タッチの転送先のビューがサブビュー/スーパービューでない場合は、次のようにUIViewサブクラスでカスタムプロパティを設定できます。

@interface SomeViewSubclass : UIView {

    id forwardableTouchee;

}
@property (retain) id forwardableTouchee;

必ず.mで合成してください:

@synthesize forwardableTouchee;

そして、次のようなUIResponderメソッドのいずれかに以下を含めます。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    [self.forwardableTouchee touchesBegan:touches withEvent:event];

}

UIViewをインスタンス化する場合は常に、イベントの転送先となるビューにforwardableToucheeプロパティを設定します。

    SomeViewSubclass *view = [[[SomeViewSubclass alloc] initWithFrame:someRect] autorelease];
    view.forwardableTouchee = someOtherView;

4

Swift 5では

class ThroughView: UIView {
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        guard let slideView = subviews.first else {
            return false
        }

        return slideView.hitTest(convert(point, to: slideView), with: event) != nil
    }
}

4

UIStackViewを介してタッチを渡す必要がありました。内部のUIViewは透過的でしたが、UIStackViewはすべてのタッチを消費しました。これは私のために働きました:

class PassThrouStackView: UIStackView {

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let view = super.hitTest(point, with: event)
        if view == self {
            return nil
        }
        return view
    }
}

すべてのArrangedSubviewsは引き続きタッチを受けますが、UIStackView自体へのタッチは下のビュー(私にとってはmapView)を通過しました。


2

あなたがここでかなりたくさんの答えをしているように見えますが、私が必要とする迅速できれいな人は誰もいません。そこで私はここで@fresidueからの回答を受け取り、それを現在ほとんどの開発者がここで使用したいものであるため、迅速に変換しました。

ボタン付きの透明なツールバーがありますが、ツールバーがユーザーに表示されず、タッチイベントが通過するという問題が解決しました。

述べられているものは私のテストに基づくオプションではないため、isUserInteractionEnabled = false。

 override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    for subview in subviews {
        if subview.hitTest(convert(point, to: subview), with: event) != nil {
            return true
        }
    }
    return false
}

1

StackView内にいくつかのラベルがあり、上記のソリューションではあまり成功しませんでしたが、代わりに以下のコードを使用して問題を解決しました:

    let item = ResponsiveLabel()

    // Configure label

    stackView.addArrangedSubview(item)

UIStackViewのサブクラス化:

class PassThrouStackView:UIStackView{
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        for subview in self.arrangedSubviews {
            let convertedPoint = convert(point, to: subview)
            let labelPoint = subview.point(inside: convertedPoint, with: event)
            if (labelPoint){
                return subview
            }

        }
        return nil
    }

}

それからあなたは次のようなことをすることができます:

class ResponsiveLabel:UILabel{
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // Respond to touch
    }
}

0

このようなものを試してください...

for (UIView *view in subviews)
  [view touchesBegan:touches withEvent:event];

上記のコードは、たとえばtouchesBeganメソッドで、タッチをビューのすべてのサブビューに渡します。


6
このアプローチの問題は、UIKitフレームワークのクラスにタッチを転送しようとしても、ほとんどの場合効果がないことです。Appleのドキュメントによると、UIKitフレームワーククラスは、viewプロパティが他のビューに設定されているタッチを受け取るようには設計されていません。したがって、このアプローチは主にUIViewのカスタムサブクラスに対して機能します。
maciejs 2011年

0

私がやろうとしている状況は、ネストされたUIStackView内のコントロールを使用してコントロールパネルを構築することでした。一部のコントロールには、UITextFieldの他のコントロールとUIButtonのコントロールがあります。また、コントロールを識別するためのラベルもありました。私がやりたかったのは、大きな「非表示」ボタンをコントロールパネルの後ろに配置して、ユーザーがボタンまたはテキストフィールドの外側の領域をタップした場合に、それをキャッチしてアクションを実行できるようにすることです。フィールドはアクティブでした(resignFirstResponder)。ただし、コントロールパネルのラベルやその他の空白の領域をタップしても、通過しません。上記の議論は、以下の私の答えを出すのに役立ちました。

基本的に、UIStackViewをサブクラス化し、「point(inside:with)」ルーチンを上書きして、タッチが必要なコントロールのタイプを探し、ラベルなどの無視したいものを「無視」しました。また、UIStackViewの内部をチェックして、コントロールパネル構造に再帰できるようにします。

このコードは、本来あるべきものよりもおそらく少し冗長です。しかし、これはデバッグに役立ち、ルーチンが何をしているかをより明確に提供できると期待しています。インターフェイスビルダーで、UIStackViewのクラスをPassThruStackに変更してください。

class PassThruStack: UIStackView {

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {

    for view in self.subviews {
        if !view.isHidden {
            let isStack = view is UIStackView
            let isButton = view is UIButton
            let isText = view is UITextField
            if isStack || isButton || isText {
                let pointInside = view.point(inside: self.convert(point, to: view), with: event)
                if pointInside {
                    return true
                }
            }
        }
    }
    return false
}

}


-1

@PixelCloudStvによって示唆されているように、あるビューから別のビューにタッチをスローしたいが、このプロセスをさらに制御する場合-サブクラスUIView

//ヘッダ

@interface TouchView : UIView

@property (assign, nonatomic) CGRect activeRect;

@end

//実装

#import "TouchView.h"

@implementation TouchView

#pragma mark - Ovverride

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    BOOL moveTouch = YES;
    if (CGRectContainsPoint(self.activeRect, point)) {
        moveTouch = NO;
    }
    return moveTouch;
}

@end

後、interfaceBuilderでViewのクラスをTouchViewに設定し、アクティブな四角形を四角形で設定します。また、他のロジックを変更して実装することもできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.