Move a view when scrolling in UITableView

2019-01-16 10:43发布

I have a UIView with a UITableView below it:

enter image description here

What I would like to do is to have the view above the UITableView move up (out of the way) when the user starts scrolling in the table in order to have more space for the UITableView (and come down when you scroll down again).

I know that this is normally done with a table header view, but my problem is that my table view is inside a tab (actually it is a side-scrolling page view implemented using TTSliddingPageviewcontroller). So while I only have one top UIView there are three UITableViews.

Is it possible to accomplish this manually? My first thought is to put everything in a UIScrollView, but according to Apple's documentation one should never place a UITableView inside a UIScrollView as this leads to unpredictable behavior.

8条回答
太酷不给撩
2楼-- · 2019-01-16 10:47

Solution for Swift (Works perfectly with bounce enabled for scroll view):

 var oldContentOffset = CGPointZero
 let topConstraintRange = (CGFloat(120)..<CGFloat(300))

 func scrollViewDidScroll(scrollView: UIScrollView) {

    let delta =  scrollView.contentOffset.y - oldContentOffset.y

    //we compress the top view
    if delta > 0 && topConstraint.constant > topConstraintRange.start && scrollView.contentOffset.y > 0 {
        topConstraint.constant -= delta
        scrollView.contentOffset.y -= delta
    }

    //we expand the top view
    if delta < 0 && topConstraint.constant < topConstraintRange.end && scrollView.contentOffset.y < 0{
        topConstraint.constant -= delta
        scrollView.contentOffset.y -= delta
    }

    oldContentOffset = scrollView.contentOffset
 }
查看更多
该账号已被封号
3楼-- · 2019-01-16 10:48

I added some constraints to the last solution to prevent some strange behaviours in case of fast scrolling

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let delta =  scrollView.contentOffset.y - oldContentOffset.y

    //we compress the top view
    if delta > 0 && topConstraint.constant > topConstraintRange.lowerBound && scrollView.contentOffset.y > 0 {
        searchHeaderTopConstraint.constant = max(topConstraintRange.lowerBound, topConstraint.constant - delta)
        scrollView.contentOffset.y -= delta
    }

    //we expand the top view
    if delta < 0 && topConstraint.constant < topConstraintRange.upperBound && scrollView.contentOffset.y < 0 {
        topConstraint.constant = min(searchHeaderTopConstraint.constant - delta, topConstraintRange.upperBound)
        scrollView.contentOffset.y -= delta
    }
    oldContentOffset = scrollView.contentOffset
}
查看更多
姐就是有狂的资本
4楼-- · 2019-01-16 10:49

Someone asked for the code for my solution so I am posting it here as an answer. The credit for the idea should still go to NobodyNada.

In my UITableViewController I implement this delegate method:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"TableViewScrolled" object:nil userInfo:scrollUserInfo];
}

scrollUserInfo is a NSDictionary where I put my UITableView to pass it with the notification (I do this in viewDidLoad so I only have to do it once):

scrollUserInfo = [NSDictionary dictionaryWithObject:self.tableView forKey:@"scrollView"];

Now, in the view controller that has the view I want to move off screen while scrolling I do this in viewDidLoad:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleScroll:) name:@"TableViewScrolled" object:nil];

And finally I have the method:

- (void)handleScroll:(NSNotification *)notification {
    UIScrollView *scrollView = [notification.userInfo valueForKey:@"scrollView"];
    CGFloat currentOffset = scrollView.contentOffset.y;
    CGFloat height = scrollView.frame.size.height;
    CGFloat distanceFromBottom = scrollView.contentSize.height - currentOffset;

    if (previousOffset < currentOffset && distanceFromBottom > height) {
        if (currentOffset > viewHeight)
            currentOffset = viewHeight;
        self.topVerticalConstraint.constant += previousOffset - currentOffset;
        previousOffset = currentOffset;
    }
    else {
        if (previousOffset > currentOffset) {
            if (currentOffset < 0)
                currentOffset = 0;
            self.topVerticalConstraint.constant += previousOffset - currentOffset;
            previousOffset = currentOffset;
        }
    }
}

previousOffset is an instance variable CGFloat previousOffset;. topVerticalConstraint is a NSLayoutConstraint that is set as a IBOutlet. It goes from the top of the view to the top of its superview and the initial value is 0.

It's not perfect. For instance, if the user scrolls very aggressively up the movement of the view can get a bit jerky. The issue is worse for large views; if the view is small enough there is no problem.

查看更多
何必那么认真
5楼-- · 2019-01-16 10:57

I know this post in very old. I tried above solutions but neither worked for me for tried my own, hopefully it can help you. This scenario is pretty common, as apple suggested not to use TableViewController inside any ScrollView because the compiler will confused as in whom to respond becuase it will be getting two delegate call back - one from ScrollViewDelegate and another from UITableViewDelegate.

Instead we can use ScrollViewDelegate and disable the UITableViewScrolling.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    CGFloat currentOffSetY = scrollView.contentOffset.y;
    CGFloat diffOffset = self.lastContentOffset - currentOffSetY;

    self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width, 400 + [self.tableView contentSize].height);

    if (self.lastContentOffset < scrollView.contentOffset.y) {
        tableView.frame = CGRectMake(tableView.frame.origin.x, tableView.frame.origin.y , tableView.frame.size.width,  tableView.size.height - diffOffset);
    }

    if (self.lastContentOffset > scrollView.contentOffset.y) {
        tableView.frame = CGRectMake(tableView.frame.origin.x, tableViewframe.origin.y, tableViewframe.size.width,  tableView.frame.size.height + diffOffset);
    }

    self.lastContentOffset = currentOffSetY;
}

Here lastContentOffset is CGFloat defined as property

The View Heirarchy is as follows: ViewController --> View contains ScrollView (whose delegate method is defined above) --> Contain TableView.

By the above code we are manually increasing and decreasing the height of the table view along with the content size of ScrollView.

Remember to disable the Scrolling of TableView.

查看更多
倾城 Initia
6楼-- · 2019-01-16 11:09

More simple and fast approach

- (void)scrollViewDidScroll:(UIScrollView *)scrollView 
{

    CGRect rect = self.view.frame;
    rect.origin.y =  -scrollView.contentOffset.y;
    self.view.frame = rect;

}
查看更多
等我变得足够好
7楼-- · 2019-01-16 11:11

To create like this animation,

enter image description here

lazy var redView: UIView = {

    let view = UIView(frame: CGRect(x: 0, y: 0, width: 
    self.view.frame.width, height: 100))
    view.backgroundColor = .red
    return view
}()

var pageMenu: CAPSPageMenu?

override func viewDidLoad() {
     super.viewDidLoad()
     self.view.addSubview(redView)

    let rect = CGRect(x: 0, y: self.redView.frame.maxY, width: self.view.bounds.size.width, height:(self.view.bounds.size.height - (self.redView.frame.maxY)))
    pageMenu?.view.frame = rect
    self.view.addSubview(pageMenu!.view)

  }

override func scrollViewDidScroll(_ scrollView: UIScrollView) {
  let offset = scrollView.contentOffset.y

    if(offset > 100){
        self.redView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: 0)
    }else{
        self.redView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: 100 - offset)
    }

    let rect = CGRect(x: 0, y: self.redView.frame.maxY, width: self.view.bounds.size.width, height:(self.view.bounds.size.height - (self.redView.frame.maxY)))
    pageMenu?.view.frame = rect
}

you must change pageMenu.view with your collectionView/tableView

查看更多
登录 后发表回答