web-dev-qa-db-ja.com

UITapGestureRecognizerは、UITableView didSelectRowAtIndexPathを壊します

キーボードが表示されたときにテキストフィールドを上にスクロールする独自の関数を作成しました。テキストフィールドからタップしてキーボードを閉じるために、タップしたときにテキストフィールドで最初のレスポンダーを辞任するUITapGestureRecognizerを作成しました。

また、テキストフィールドのすぐ下にUITableViewを作成し、ユーザーがテキストを入力するときにアイテムを入力するテキストフィールドのオートコンプリートを作成しました。

ただし、自動完了テーブルのエントリの1つを選択すると、didSelectRowAtIndexPathは呼び出されません。代わりに、タップジェスチャレコグナイザーが呼び出され、最初のレスポンダーを辞任するようです。

タップジェスチャーレコグナイザーにUITableViewにタップメッセージを渡し続けるように指示する方法があると思いますが、それが何なのかわかりません。どんな助けでも大歓迎です。

193
Jason

OK、ジェスチャレコグナイザードキュメントを検索した結果、ようやく見つかりました。

解決策は、UIGestureRecognizerDelegateを実装し、以下を追加することでした:

////////////////////////////////////////////////////////////
// UIGestureRecognizerDelegate methods

#pragma mark UIGestureRecognizerDelegate methods

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    if ([touch.view isDescendantOfView:autocompleteTableView]) {

        // Don't let selections of auto-complete entries fire the 
        // gesture recognizer
        return NO;
    }

    return YES;
}

それはそれの世話をしました。これが他の人にも役立つことを願っています。

250
Jason

この問題を解決する最も簡単な方法は次のとおりです。

UITapGestureRecognizer *tapRec = [[UITapGestureRecognizer alloc] 
    initWithTarget:self action:@selector(tap:)];
[tapRec setCancelsTouchesInView:NO];

これにより、UIGestureRecognizerがタップを認識し、次のレスポンダーにタッチを渡すことができます。このメソッドの意図しない結果は、別のView Controllerをプッシュする画面上にUITableViewCellがある場合です。ユーザーがキーボードを閉じるために行をタップすると、キーボードとプッシュの両方が認識されます。私はこれがあなたの意図することだとは思いませんが、この方法は多くの状況に適しています。

また、ロバートの答えを拡張すると、問題のテーブルビューへのポインタがある場合、文字列に変換する代わりにクラスを直接比較でき、Appleが命名法を変更しないことを期待できます。

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer 
     shouldReceiveTouch:(UITouch *)touch
{
    if([touch.view class] == tableview.class){
        return //YES/NO
    }

    return //YES/NO

}

また、UIGestureRecognizerを宣言して、このコードを含むデリゲートを含める必要があります。

213
TMilligan

認識エンジンのcancelsTouchesInViewをfalseに設定します。それ以外の場合は、タッチを「消費」し、テーブルビューに渡しません。そのため、選択イベントは発生しません。

たとえば、Swift

let tapOnScreen: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "CheckTheTime")
tapOnScreen.cancelsTouchesInView = false
view.addGestureRecognizer(tapOnScreen)
63

Swiftの場合(@Jasonからの回答に基づく):

class MyAwesomeClass: UIViewController, UIGestureRecognizerDelegate

private var tap: UITapGestureRecognizer!

override func viewDidLoad() {
   super.viewDidLoad()

   self.tap = UITapGestureRecognizer(target: self, action: "viewTapped:")
   self.tap.delegate = self
   self.view.addGestureRecognizer(self.tap)
}

// UIGestureRecognizerDelegate method
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if touch.view?.isDescendantOfView(self.tableView) == true {
        return false
    }
    return true
}
36
Nikolaj Nielsen

テーブルビューにタップジェスチャを追加すると同時に、セルの選択を許可するより良いソリューションがある場合があります。

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if gestureRecognizer is UITapGestureRecognizer {
        let location = touch.locationInView(tableView)
        return (tableView.indexPathForRowAtPoint(location) == nil)
    }
    return true
}

ユーザーがタップしている画面のポイントでセルを探すだけです。インデックスパスが見つからない場合は、ジェスチャがタッチを受け取るようにします。そうでない場合はキャンセルします。私にとっては素晴らしい作品です。

18
bonokite

ジェスチャーオブジェクトに対してcancelsTouchesInViewfalseに設定するだけでコードのブロックを記述する必要はないと思います。デフォルトではtrueであり、単にfalseに設定するだけです。コードでUITapGestureオブジェクトを使用し、UIScrollView(tableview、collectionview)も使用している場合、このプロパティを設定しますfalse

let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
16
SHUBHAYAN KUNDU

同様の解決策は、ビューのクラスを使用してgestureRecognizer:shouldReceiveTouch:を実装し、実行するアクションを決定することです。このアプローチには、テーブルを直接囲む領域のタップをマスクしないという利点があります(これらの領域のビューはUITableViewインスタンスから派生しますが、セルを表しません)。

これには、単一のビューで複数のテーブルを使用して(余分なコードを追加することなく)動作するというボーナスもあります。

注意:Appleはクラス名を変更しないという仮定があります。

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    return ![NSStringFromClass([touch.view class]) isEqualToString:@"UITableViewCellContentView"];
}
14
Robert Altman

ユーザーがテーブルビューのoutsideをタップしたときに、タッチジェスチャ機能をonlyで呼び出す別の状況がありました。ユーザーがテーブルビュー内でタップした場合、タッチジェスチャ関数を呼び出さないでください。さらに、タッチジェスチャ関数が呼び出された場合、タッチイベントを消費するのではなく、タップされたビューに渡す必要があります。

結果のコードは、Abdulrahman Masoudの答えとNikolaj Nielsenの答えの組み合わせです。

extension MyViewController: UIGestureRecognizerDelegate {
    func addGestureRecognizer() {
        let tapOnScreen = UITapGestureRecognizer(target: self,
                                                action: #selector(functionToCallWhenUserTapsOutsideOfTableView))

        // stop the gesture recognizer from "consuming" the touch event, 
        // so that the touch event can reach other buttons on view.
        tapOnScreen.cancelsTouchesInView = false

        tapOnScreen.delegate = self

        self.view.addGestureRecognizer(tapOnScreen)
    }

    // if your tap event is on the menu, don't run the touch event.
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        if touch.view?.isDescendant(of: self.tableView) == true {
            return false
        }
        return true
    }

    @objc func functionToCallWhenUserTapsOutsideOfTableView() {

        print("user tapped outside table view")
    }
}

そして、MyViewControllerクラス、UITableViewを持つクラス、onViewDidLoad()で、addGestureRecognizer()を呼び出すようにしました:

class MyViewController: UIViewController {
    ...
    override func viewDidLoad() {
        super.viewDidLoad()
        ...
        self.addGestureRecognizer()
        ...
    }
    ...
}
4
Ali Mizan

cancels touches in viewの下のジェスチャーの(table/collection)Viewfalseに設定するだけです。

-Interfacebuilder

Interfacebuilder

-コード

<#gestureRecognizer#>.cancelsTouchesInView = false
3

シンプルなソリューションは、UITableViewCellのUIControlインスタンスを使用してタッチを取得することです。 UIControlにuserInteractionEnables == NOのビューを追加して、タップを取得できます。

1
Andrew Romanov

Swift 4.2の場合、解決策はUIGestureRecognizerDelegateを実装し、以下を追加することでした:

extension ViewController : UIGestureRecognizerDelegate {
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        if touch.view!.isDescendant(of: tblView) {
            return false
        }
        return true
    }
}

テーブルビューをクリックすると、このデリゲートメソッドはfalseを返し、テーブルビューのdidSelectRowAtIndexPathメソッドが正しく機能します。

1
Ashish Kanani

遅くなり、多くの人が上記の提案がうまくいくことを発見しましたが、私はジェイソンまたはTMilliganの方法を機能させることができませんでした。

数値キーボードのみを使用して数値入力を受け取るtextFieldsを含む複数のセルを持つStatic tableViewがあります。これは私にとって理想的でした:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    if(![touch.view isKindOfClass:[UITableViewCell class]]){

        [self.firstTF resignFirstResponder];
        [self.secondTF resignFirstResponder];
        [self.thirdTF resignFirstResponder];
        [self.fourthTF resignFirstResponder];

        NSLog(@"Touches Work ");

        return NO;
    }
    return YES;
}

この<UIGestureRecognizerDelegate>が.hファイルに実装されていることを確認してください。

この行![touch.view isKindOfClass:[UITableViewCell class]]は、tableViewCellがタップされたかどうかを確認し、アクティブなキーボードを閉じます。

1
App Dev Guy

ここに私の解決策があります。これは、レコグナイザーのshouldReceiveTouchをキーボードが表示されているかどうかに直接結び付けます。

タップジェスチャレコグナイザーデリゲートで:

#pragma mark - UIGestureRecognizerDelegate

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    if ([PFXKeyboardStateListener sharedInstance].visible) {
        return YES;
    }

    return NO;
}

そしてPFXKeyboardStateListener.h

@interface PFXKeyboardStateListener : NSObject
{
    BOOL _isVisible;
}

+ (PFXKeyboardStateListener *)sharedInstance;

@property (nonatomic, readonly, getter=isVisible) BOOL visible;

@end

そしてPFXKeyboardStateListener.m

static PFXKeyboardStateListener *sharedInstance;

@implementation PFXKeyboardStateListener

+ (PFXKeyboardStateListener *)sharedInstance
{
    return sharedInstance;
}

+ (void)load
{
    @autoreleasepool {
        sharedInstance = [[self alloc] init];
    }
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (BOOL)isVisible
{
    return _isVisible;
}

- (void)didShow
{
    _isVisible = YES;
}

- (void)didHide
{
    _isVisible = NO;
}

- (id)init
{
    if ((self = [super init])) {
        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
        [center addObserver:self selector:@selector(didShow) name:UIKeyboardDidShowNotification object:nil];
        [center addObserver:self selector:@selector(didHide) name:UIKeyboardWillHideNotification object:nil];
    }
    return self;
}

@end

キーボードリスナーのシングルトンパターンを更新したい場合がありますが、私はまだそれにしていません。これが他のすべての人にも役立つことを願っています。 ^^

0
bgfriend0

UIGestureRecognizerのデリゲートにこのメソッドを実装します。

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
       shouldReceiveTouch:(UITouch *)touch
{
  UIView *superview = touch.view;
  do {
    superview = superview.superview;
    if ([superview isKindOfClass:[UITableViewCell class]])
      return NO;
  } while (superview && ![superview isKindOfClass:[UITableView class]]);

  return superview != nil;
}
0
cxa

Swiftでは、これを内部で使用できます

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    if CheckTheTime() == true {
        // do something 
    }else{
    }
}

func CheckTheTime() -> Bool{
    return true
}
0

Uisearchbarとself.viewのuitableviewを含む私の場合は異なりました。ビューをタッチして、uisearchbarキーボードを閉じたいと思いました。

var tapGestureRecognizer:UITapGestureRecognizer?

override func viewWillAppear(animated: Bool) {
    tapGestureRecognizer = UITapGestureRecognizer(target: self, action:Selector("handleTap:"))
}

UISearchBarデリゲートメソッド:

func searchBarShouldBeginEditing(searchBar: UISearchBar) -> Bool {
    view.addGestureRecognizer(tapGestureRecognizer!)
    return true
}
func searchBarShouldEndEditing(searchBar: UISearchBar) -> Bool {
    view.removeGestureRecognizer(tapGestureRecognizer!)
    return true
}

ユーザーがself.viewをタッチすると:

func handleTap(recognizer: UITapGestureRecognizer) {
    sampleSearchBar.endEditing(true)
    sampleSearchBar.resignFirstResponder()
}
0
A.G