UITableView inside UIScrollView not receiving firs

2019-01-06 14:28发布

Brief

I am having an issue with a UITableView inside a UIScrollView. When I scroll the external scrollView, the table does not receive the willSelect/didSelect event on the first touch, but it does on the second one. What is even more strange, the cell itself gets the touches and the highlighted state, even when the delegate does not.

Detailed explanation

My view hierarchy:

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

Inside the scroll view I have some extra views that get expanded/closed dynamically. The table view needs to get "fixed" on top, together with some other elements of the view, so that is why I created this layout, that allows me to easily move elements in a similar way than Apple recommends by the use of transformations when the scroll happens.

The table View is transformed with a translation effect when the outerscroll moves like this:

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

}

In my cell, if I implement these methods:

- (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 can see this output when fails (first tap):

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

And this one when works (second tap):

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

No other frame change, animation or any other view interaction is done between the first and the second tap. Also, only when scrolling large amounts the bug appears, but with scrollings of just a few pixels everything keeps working as expected.

I experimented changing some properties as well, but with no luck. Some of the things I did:

  • Remove userInteractionEnabled from views other than the scroll and table
  • Add a call to setNeedsLayout on the table, scroll and main view when scrollViewDidScroll occurs.
  • Remove the transformations from the table (still happens)

I have seen some comments about the unexpected behaviour of embedding UITableViews inside UIScrollViews but I can not see such a warn in the official documentation by Apple, so I am expecting it to work.

The app is iOS7+ only.

Questions

Has anyone experienced similar issues? Why is this and how can I solve it? I think that I could be able to intercept the tap gesture on the cell and pass it with a custom delegate or similar, but I would like the table to receive the proper events and so my UITableViewDelegate receives it as expected.

Updates

  • I tried disabling cell reuse as suggested in a comment but it still happens in the same way.

14条回答
在下西门庆
2楼-- · 2019-01-06 14:54

I ran into this same problem and figured out a solution!!

You need to set the delaysTouchesBegan to true on your scrollview so that the scrollview sends its failed scrolled-gesture (i.e. the tap) to its children.

var delaysTouchesBegan: Bool - A Boolean value determining whether the receiver delays sending touches in a begin phase to its view.

When the value of the property is YES, the window suspends delivery of touch objects in the UITouchPhaseBegan phase to the view. If the gesture recognizer subsequently recognizes its gesture, these touch objects are discarded. If the gesture recognizer, however, does not recognize its gesture, the window delivers these objects to the view in a touchesBegan:withEvent: message (and possibly a follow-up touchesMoved:withEvent: message to inform it of the touches’ current locations).

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

But there's a catch...it doesn't work if you do it directly on the scrollview!

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

Apparently this is an iOS bug where setting this property doesn't work (thank you apple). However there's a simple workaround: set the property directly on the scrollview's pan gesture. Sure enough, this worked for me perfectly:

// This works!!
self.myScrollview.panGestureRecognizer.delaysTouchesBegan = true
查看更多
Deceive 欺骗
3楼-- · 2019-01-06 14:54

I have the same issue, Then refer to "Nesting Scroll Views" as lxx said.

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

An example of cross directional scrolling can be found in the Stocks application. The top view is a table view, but the bottom view is a horizontal scroll view configured using paging mode. While two of its three subviews are custom views, the third view (that contains the news articles) is a UITableView (a subclass of UIScrollView) that is a subview of the horizontal scroll view. After you scroll horizontally to the news view, you can then scroll its contents vertically.

It is work

查看更多
我只想做你的唯一
4楼-- · 2019-01-06 14:59

The scrollview is trying to figure out whether the user's intention is to scroll or not, so it's delaying the initial touch on purpose. You can turn this off by setting delaysContentTouches to NO.

查看更多
迷人小祖宗
5楼-- · 2019-01-06 15:02

I would recommend to look for options like not letting your cell to be in highlighted state when you are actually scrolling the outer scroll view which is very easy to handle and is the recommended way. You can do this just by taking a boolean and toggling it in the below method

- (void)scrollViewDidScroll:(UIScrollView *)scrollView 
查看更多
Luminary・发光体
6楼-- · 2019-01-06 15:03

That it, if touch table view it will work properly. also with scroll view in same view controller also.

tableview.scrollEnabled = true;
查看更多
该账号已被封号
7楼-- · 2019-01-06 15:04

It seems that your UiTableView doesn't recognize your tap. Did you try to use that :

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

Note from apple:

called when the recognition of one of gestureRecognizer or otherGestureRecognizer would be blocked by the other. return YES to allow both to recognize simultaneously. the default implementation returns NO (by default no two gestures can be recognized simultaneously)

note: returning YES is guaranteed to allow simultaneous recognition. returning NO is not guaranteed to prevent simultaneous recognition, as the other gesture's delegate may return YES

Hope that will help.

查看更多
登录 后发表回答