Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resizing UITableView as seen in Rdio iOS app

Im attempting to replicate the resizing behaviour of a screen seen in the iOS app Rdio. The screen in question provides an overview of a selected album and contains a UIView on the top half and a UITableView on the bottom half. When the tableView is scrolled, it first resizes upwards to fill the screen, then begins to scroll through its content normally once the maximum height is reached.

After some searching I found this question: Dragging UITableView which is basically asking for the same thing, however its accepted method is the same as my initial thoughts & trial, which was to use a UIPanGestureRecognizer and resize the tableviews height according to the translation of the pan.

This does not provide the behaviour i'm looking for. Using this method only allows you to statically drag the tableviews height up or down and it has the added issue of the panGesture overriding that of the tableViews which then prevents scrolling through the content.

The resizing behaviour of the Rdio app functions and feels exactly like a UIScrollView, it has inertia. You can drag it all the way, flick it up or down, and it smoothly resizes. When the tableView has reached its full-size or original half-size, the remaining inertia is seemingly passed on the tableview causing the cells to scroll as they normally would for that amount. I know they must be manipulating UIScrollViews, I just can't figure out how.

As a final note, eventually I will be using AutoLayout on this screen so i'm wondering how that will potentially hinder or help this situation as well.

Update

This approach has gotten me closest to the behaviour i'm looking for so far. Flicking the tableView upwards behaves exactly like I wanted it to (resize with inertia & continue scrolling when max height is reached), although with less sensitivity than i'd like. Flicking downwards however, provides no inertia and instantly stops.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    CGRect scrollViewFrame = scrollView.frame;
    CGFloat scrollViewYOffset = scrollView.contentOffset.y;

    if (scrollViewFrame.origin.y <= kTableViewMaxYOrigin || scrollViewFrame.origin.y <= _originalTableViewFrame.origin.y)
    {
        scrollViewFrame.origin.y -= scrollViewYOffset;

        if(scrollViewFrame.origin.y >= kTableViewMaxYOrigin && scrollViewFrame.origin.y <= _originalTableViewFrame.origin.y)
        {
            scrollViewFrame.size.height += scrollViewYOffset;
            scrollView.frame = scrollViewFrame;
            scrollView.contentOffset = CGPointZero;
        }
    }
}
like image 362
f.perdition Avatar asked Feb 15 '23 08:02

f.perdition


2 Answers

I made a version that uses autolayout instead. It took me a while of trial and error to get this one right!

Please use the comments and ask me if the answer is unclear.

In viewDidLoad save the initial height of your layout constraint determining the lowest down you want the scrollview to be.

- (void)viewDidLoad
{
    ...
    _initialTopLayoutConstraintHeight = self.topLayoutConstraint.constant;
    ...
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    BOOL leaveScrollAlone = self.topLayoutConstraint.constant == _initialTopLayoutConstraintHeight && scrollView.contentOffset.y <= 0;
    if (leaveScrollAlone)
    {
        // This allows for bounce when swiping your finger downwards and reaching the stopping point
        return;
    }

    // Do some capping of that layout constraint to keep it from going past the range you want it to be.
    // In this case, I use self.topLayoutGuide.length so that my UICollectionView scales all the way until
    // it hits the bottom of the navigation bar
    CGFloat topLayoutConstraintLength =   _initialTopLayoutConstraintHeight - scrollView.contentInset.top;
    topLayoutConstraintLength           = MAX(topLayoutConstraintLength, self.topLayoutGuide.length);
    topLayoutConstraintLength           = MIN(topLayoutConstraintLength, _initialTopLayoutConstraintHeight);
    self.topLayoutConstraint.constant   = topLayoutConstraintLength;


    // Keep content seemingly still while the UICollectionView resizes
    if (topLayoutConstraintLength > self.topLayoutGuide.length)
    {
        scrollView.contentInset = UIEdgeInsetsMake(scrollView.contentInset.top + scrollView.contentOffset.y,
                                                   scrollView.contentInset.left,
                                                   scrollView.contentInset.bottom,
                                                   scrollView.contentInset.right);

        scrollView.contentOffset = CGPointZero;
    }

    // This helps get rid of the extraneous contentInset.top we accumulated for keeping
    // the content static while the UICollectionView resizes
    if (scrollView.contentOffset.y < 0)
    {
        self.topLayoutConstraint.constant -= scrollView.contentOffset.y;

        scrollView.contentInset = UIEdgeInsetsMake(scrollView.contentInset.top + scrollView.contentOffset.y,
                                                   scrollView.contentInset.left,
                                                   scrollView.contentInset.bottom,
                                                   scrollView.contentInset.right);
    }

    // Prevents strange jittery artifacts
    [self.view layoutIfNeeded];
}
like image 169
Nailer Avatar answered Feb 17 '23 21:02

Nailer


It turns out the key component to getting the smooth inertial resizing in both directions was to update the scrollViews contentInset.top by its contentOffset.y.

I believe this makes sense in retrospect as if the content within is already at the top it cannot scroll anymore, hence the sudden stop rather than smooth scroll. At least thats my understanding.

Another key point was to make sure the cells only started scrolling once maximum or original height was achieved. This was done simply by setting the scrollViews contentOffset to CGPointZero each time the view resized until maximum or original height was reached.

Here is the - (void)scrollViewDidScroll:(UIScrollView *)scrollView method demonstrating how to achieve this effect.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    CGRect scrollViewFrame = scrollView.frame;
    CGFloat scrollViewTopContentInset = scrollView.contentInset.top;
    CGFloat scrollViewYOffset = scrollView.contentOffset.y;

    if (scrollViewFrame.origin.y <= kTableViewMaxYOrigin || scrollViewFrame.origin.y <= _originalTableViewFrame.origin.y)
    {
        scrollViewFrame.origin.y -= scrollViewYOffset;

        if(scrollViewFrame.origin.y >= kTableViewMaxYOrigin && scrollViewFrame.origin.y <= _originalTableViewFrame.origin.y)
        {
            scrollViewFrame.size.height += scrollViewYOffset;
            scrollViewTopContentInset += scrollViewYOffset;
            scrollView.frame = scrollViewFrame;
            scrollView.contentInset = UIEdgeInsetsMake(scrollViewTopContentInset, 0, 0, 0);
            scrollView.contentOffset = CGPointZero;
        }
    }
}
like image 34
f.perdition Avatar answered Feb 17 '23 22:02

f.perdition