web-dev-qa-db-ja.com

UIScrollView内のUITableViewがスクロール後に最初のタップを受信しない

簡単な

UITableView内のUIScrollViewに問題があります。外部scrollViewをスクロールすると、tableは最初のタッチでwillSelect/didSelectイベントを受け取りませんが、2番目のタッチでは受け取ります。さらに奇妙なことに、delegateがそうでない場合でも、セル自体がタッチと強調表示状態を取得します。

詳細な説明

私のビュー階層:

UIView
  - UIScrollView  (outerscroll)
      - Some other views and buttons
      - UITableView (tableView)

スクロールビュー内には、動的に展開/閉じられる追加のビューがあります。テーブルビューは、ビューの他の要素と一緒に上部に「固定」する必要があるため、このレイアウトを作成したのは、Appleが推奨する方法と同様の方法で要素を簡単に移動できるためです。スクロールが発生したときに変換を使用します。

テーブルビューは、outerscrollが次のように移動すると、変換効果で変換されます。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (scrollView == self.outerScrollView) {

        CGFloat tableOffset = scrollView.contentOffset.y - self.fixedHeaderFrame.Origin.y;
        if (tableOffset > 0) {
            self.tableView.contentOffset = CGPointMake(0, tableOffset);
            self.tableView.transform = CGAffineTransformMakeTranslation(0, tableOffset);
        }
        else {
            self.tableView.contentOffset = CGPointMake(0, 0);
            self.tableView.transform = CGAffineTransformIdentity;
        }

        // Other similar transformations are done here, but not involving the table

}

私のセルでは、これらのメソッドを実装すると:

- (void)setSelected:(BOOL)selected {
    [super setSelected:selected];
    if (selected) {
        NSLog(@"selected");
    }
}

- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
{
    [super setHighlighted:highlighted animated:animated];
    if (highlighted) {
        NSLog(@"highlighted");
    }
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event];
    NSLog(@"touchesBegan");
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesEnded:touches withEvent:event];
    NSLog(@"touchesEnded");
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesCancelled:touches withEvent:event];
    NSLog(@"touchesCancelled");
}

Yは、失敗したときにこの出力を見ることができます(最初のタップ):

2014-02-10 13:04:40.940 MyOrderApp[5588:70b] highlighted 
2014-02-10 13:04:40.940 MyOrderApp[5588:70b] touchesBegan 
2014-02-10 13:04:40.978 MyOrderApp[5588:70b] touchesEnded

そして、これが機能する場合(2回目のタップ):

2014-02-10 13:05:30.359 MyOrderApp[5588:70b] highlighted 
2014-02-10 13:05:30.360 MyOrderApp[5588:70b] touchesBegan 
2014-02-10 13:05:30.487 MyOrderApp[5588:70b] touchesEnded 
2014-02-10 13:05:30.498 MyOrderApp[5588:70b] expanded

最初のタップと2番目のタップの間では、他のフレームの変更、アニメーション、または他のビューの操作は行われません。また、大量にスクロールする場合にのみバグが表示されますが、わずか数ピクセルのスクロールではすべてが正常に機能し続けます。

いくつかのプロパティを変更することも試みましたが、運はありませんでした。私がやったことのいくつか:

  • スクロールとテーブル以外のビューからuserInteractionEnabledを削除します
  • setNeedsLayoutが発生したときに、テーブル、スクロール、およびメインビューでscrollViewDidScrollへの呼び出しを追加します。
  • テーブルから変換を削除します(それでも起こります)

UITableViewsUIScrollViewsに埋め込む予期しない動作に関するコメントを見ましたが、Appleの公式ドキュメントにはそのような警告が表示されないため、動作することを期待しています。

アプリはiOS7以降のみです。

ご質問

誰も同様の問題を経験した?これはなぜですかどうすれば解決できますか?セルのタップジェスチャをインターセプトし、カスタムデリゲートなどで渡すことができると思いますが、テーブルに適切なイベントを受信させたいなので、UITableViewDelegateは期待どおりに受信します。

更新

  • コメントで示唆されているように、セルの再利用を無効にしようとしましたが、それでも同じように起こります。
31

Apple Documentation から、UITableViewUIScrollViewの中に埋め込むべきではありません。

重要:UIScrollViewオブジェクトにUIWebViewまたはUITableViewオブジェクトを埋め込まないでください。これを行うと、2つのオブジェクトのタッチイベントが混同され、誤って処理される可能性があるため、予期しない動作が発生する可能性があります。

あなたの問題はあなたのUIScrollViewが何をするかに本当に関係しています。

ただし、必要に応じてテーブルビューを非表示にするだけの場合(私の場合)、スーパービューでUITableViewを移動できます。

ここに小さな例を書いた: https://github.com/rvirin/SoundCloud/

23
Rémy Virin

内側のUITableViewのscrollEnabledプロパティはYESのままにしておきます。これにより、内部のUITableViewUIScrollViewのスクロール関連のタッチを正しく処理することを認識できます。

20
Tom Abraham

私はこの同じ問題に遭遇し、解決策を見つけました!!

スクロールビューが失敗したスクロールジェスチャー(タップ)をその子に送信するように、スクロールビューでdelaysTouchesBeganをtrueに設定する必要があります。

var delaysTouchesBegan: Bool-受信側がビューへの開始フェーズでタッチの送信を遅延させるかどうかを決定するブール値。

プロパティの値がYESの場合、ウィンドウはビューへのUITouchPhaseBeganフェーズでタッチオブジェクトの配信を一時停止します。ジェスチャ認識エンジンがその後ジェスチャを認識すると、これらのタッチオブジェクトは破棄されます。ただし、ジェスチャレコグナイザがジェスチャを認識しない場合、ウィンドウはこれらのオブジェクトをtouchesBegan:withEvent:メッセージ(および場合によってはtouchesMoved:withEvent:メッセージでタッチの現在位置を通知するメッセージ)でビューに配信します。

https://developer.Apple.com/library/ios/documentation/UIKit/Reference/UIGestureRecognizer_Class/index.html#//Apple_ref/occ/instp/UIGestureRecognizer/delaysTouchesBegan

しかし、落とし穴があります...スクロールビューで直接行うと機能しません!

// Does NOT work
self.myScrollview.delaysTouchesBegan = true

どうやら、これはこのプロパティの設定が機能しないiOSのバグです(ありがとう、Apple)。ただし、簡単な回避策があります。スクロールビューのパンジェスチャでプロパティを直接設定します。案の定、これは私にとって完璧に機能しました:

// This works!!
self.myScrollview.panGestureRecognizer.delaysTouchesBegan = true
14
Oren

UiTableViewがタップを認識しないようです。あなたはそれを使用しようとしましたか:

- (BOOL)gestureRecognizer:(UIPanGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UISwipeGestureRecognizer             *)otherGestureRecognizer
{
    if ([otherGestureRecognizer.view isKindOfClass:[UITableView class]]) {
        return YES;
    }
    return NO;
}

Appleからのメモ:

gestureRecognizerまたはotherGestureRecognizerの一方の認識が他方によってブロックされる場合に呼び出されます。 YESを返すと、両方が同時に認識できるようになります。デフォルトの実装はNOを返します(デフォルトでは、2つのジェスチャを同時に認識できません)

注:YESを返すと、同時認識が保証されます。他のジェスチャーのデリゲートがYESを返す可能性があるため、NOを返すことで同時認識を防止することは保証されません。

それが役立つことを願っています。

3
Nicolas Bonnet

ジェスチャレコグナイザーは、2つの埋め込みスクロールビューまたはサブクラスに対して正しく機能しません。

回避策を試してください。

  1. 透明、カスタム、およびセルUIButtonのすべてを適切なタグでオーバーレイするか、サブクラスUIButtonを使用して、インデックスパスプロパティを追加し、再利用セルで毎回上書きします。

  2. このボタンをプロパティとしてカスタムセルに追加します。

  3. クラスを採用しているUITableViewDelegateプロトコルを指す目的のUIControlEvent(1つ以上)のターゲットを追加します。

  4. IBでの選択を無効にし、コードから選択を手動で管理します。

このソリューションでは、単一/複数選択の場合に注意が必要です。

3
Wladek Surala

レガシーコードのUIScrollView内でscrollEnabledがNOのUITableViewに遭遇しました。既存の階層を簡単に変更したり、スクロールを有効にしたりすることはできませんでしたが、最初のタップの問題に対して次の回避策を考え出しました。

@interface YourOwnTableView : UITableView
@end

@implementation YourOwnTableView

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

    [super touchesCancelled:touches withEvent:event];

    // Note that this is a hack and it can stop working at some point.
    // Looks like a table view with scrollEnabled being NO does not handle cancellation cleanly,
    // so let's repeat begin/end touch sequence here hoping it'll reset its own internal state properly
    // but won't trigger cell selection (the touch passed is in its cancelled phase, perhaps there is a part
    // of code inside which actually checks it)
    [super touchesBegan:touches withEvent:event];
    [super touchesEnded:touches withEvent:event];
}

@end

繰り返しますが、これは私の特定のケースで動作する回避策です。スクロールビュー内にテーブルビューを表示することは、依然として間違っています。

2
aleh

ネストされたUITableViewでも同じ問題があり、これに対する回避策が見つかりました。

innerTableView.scrollEnabled = YES;
innerTableView.alwaysBounceVertical = NO;

ユーザーが外側のビューをスクロールしたときにスクロールしないように、内側のテーブルビューの高さをセルの合計の高さと一致するように設定する必要があります。

お役に立てれば。

1
Duc Minh Trinh

私の間違いはデリゲートメソッドを実装することでした:

- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath

私が実装するつもりだったものの代わりに:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

したがって、最初のセルがdeが選択されたときに、タップされた2番目のセルでのみ呼び出されます。オートコンプリートの助けを借りて行われた愚かな間違い。ここをさまよい、あなたも同じ過ちを犯したことに気づかないかもしれないあなたのための考えです。

1
LunaCodeGirl
  1. UITableViewCellにUIButtonをドロップし、アウトレットを「btnRowSelect」として作成します。
  2. View Controllerで、このコードをcellForRowAtIndexPathに配置します

    cell.btnRowSelect.tag = indexPath.row
    cell.btnRowSelect.addTarget(self, action: Selector("rowSelect:"), forControlEvents: .TouchUpInside)
    
  3. この関数をviewControllerにも追加します。

    func rowSelect (sender:UIButton) {
    // "sendet.tag" give you the selected row 
    // do whatever you want to do in didSelectRowAtIndexPath
    }
    

この関数「rowSelect」は、「sender.tag」として行「indexPath.row」を取得するdidSelectRowAtIndexPathとして機能します。

1
shubham

外側のスクロールビューを実際にスクロールするときに、セルを強調表示状態にしないなどのオプションを探すことをお勧めします。これは非常に扱いやすく、推奨される方法です。これを行うには、ブール値を取得し、以下のメソッドで切り替えます。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView 
1
Rajesh

Scrollviewは、ユーザーの意図がスクロールするかどうかを判断しようとしているため、意図的に最初のタッチを遅らせています。 delaysContentTouchesNOに設定すると、これをオフにできます。

1
Stepan Hruda

それは、タッチテーブルビューの場合、適切に機能することです。また、同じView Controllerのスクロールビューも使用できます。

tableview.scrollEnabled = true;
0
Ravi Kumar

他の回答が示すように、スクロールビューにテーブルビューを配置するべきではありません。とにかくUITableViewはUIScrollViewを継承するので、そこから混乱が生じると思います。この状況で私がいつもしていることは:

1)UITableViewControllerをサブクラス化し、プロパティUIView * headViewを含めます。

2)親UIViewControllerで、コンテナUIViewにすべての上位のものを作成します

3)カスタムUITableViewを初期化し、tableViewのビューをView Controllerのフルサイズに追加します

[self.view addSubview: self.myTableView.view];

4)headViewをUIViewガビンに設定します

self.tableView.headView = myHeadViewGubbins.

5)tableViewControllerメソッド内

-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger *)section;

行う:

if ( section == 0 ) {
    return self.headView;
}

これで、上部に他のシズルがたくさんあるテーブルビューができました。

楽しい!

0
Martin

私は同じ問題を抱えています。それから、lxxが言ったように「スクロールビューのネスト」を参照してください。

https://developer.Apple.com/library/ios/documentation/WindowsViews/Conceptual/UIScrollView_pg/NestedScrollViews/NestedScrollViews.html

クロススクロールの例は、Stocksアプリケーションにあります。上部のビューはテーブルビューですが、下部のビューはページングモードを使用して構成された水平スクロールビューです。 3つのサブビューのうち2つはカスタムビューですが、3番目のビュー(ニュース記事を含む)は、水平スクロールビューのサブビューであるUITableView(UIScrollViewのサブクラス)です。ニュースビューまで水平にスクロールした後、そのコンテンツを垂直にスクロールできます。

仕事です

0
Davin Kao