Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UICollectionView interactive layout transition using iOS 7 APIs

I'm trying to get a handle on new iOS 7 APIs that allow for interactive, animated view controller transitions, including transitions between UICollectionViewLayouts.

I've taken and modified sample code from WWDC 2013, "iOS-CollectionViewTransition", which can be found here: https://github.com/timarnold/UICollectionView-Transition-Demo

The original demo, which was not in a working state when I found it, can be accessed with an Apple Developer account, here: https://developer.apple.com/downloads/index.action?name=WWDC%202013

My version of the app presents a collection view with two layouts, both UICollectionViewFlowLayout layouts with different properties.

Tapping on a cell in the first layout properly animates to the second, including, crucially, the tapped-on-item being scrolled to in the new layout. At first I was confused about how the new collection view knows to set its content offset so that the appropriate cell is visible, but I learned it does this based on the selected property of the presenting collection view.

Pinching on an item in the first layout should animate, using UICollectionViewTransitionLayout, UIViewControllerAnimatedTransitioning, and UIViewControllerInteractiveTransitioning, to the new layout as well. This works, but the pinched-at cell is not scrolled to in the new layout or the transition layout.

I've tried setting the selected property on the pinched-on cell at various locations (to try to mimic the behavior described when tapping on an item to push the new view controller), to no avail.

Any ideas about how to solve this problem?

like image 785
Tim Arnold Avatar asked Sep 25 '13 14:09

Tim Arnold


2 Answers

You can manipulate the contentOffset yourself during the transition, which actually gives you finer-grained control than UICollectionView's built-in animation.

For example, you can define your transition layout like this to interpolate between the "to" and "from" offsets. You just need to calculate the "to" offset yourself based on how you want things to end up:

@interface MyTransitionLayout : UICollectionViewTransitionLayout
@property (nonatomic) CGPoint fromContentOffset;
@property (nonatomic) CGPoint toContentOffset;
@end

#import "MyTransitionLayout.h"
@implementation MyTransitionLayout

- (void) setTransitionProgress:(CGFloat)transitionProgress
{
    super.transitionProgress = transitionProgress;
    CGFloat f = 1 - transitionProgress;
    CGFloat t = transitionProgress;
    CGPoint offset = CGPointMake(f * self.fromContentOffset.x + t * self.toContentOffset.x, f * self.fromContentOffset.y + t * self.toContentOffset.y);
    self.collectionView.contentOffset = offset;
}

@end

One thing to note is that the contentOffset will be reset to the "from" value when the transition completes, but you can negate that by setting it back to the "to" offset in the completion block of startInteractiveTransitionToCollectionViewLayout

CGPoint toContentOffset = ...;
[self.collectionViewController.collectionView startInteractiveTransitionToCollectionViewLayout:layout completion:^(BOOL completed, BOOL finish) {
    if (finish) {
        self.collectionView.contentOffset = toContentOffset;
    }
}];

UPDATE

I posted an implementation of this and a working example in a new GitHub library TLLayoutTransitioning. The example is non-interactive, intended to demonstrate improved animation over setCollectionViewLayout:animated:completion, but it utilizes the interactive transitioning APIs combined with the technique described above. Take a look at the TLTransitionLayout class and try running the "Resize" example in the Examples workspace.

Perhaps TLTransitionLayout can accomplish what you need.

UPDATE 2

I added an interactive example to the TLLayoutTransitioning library. Try running the "Pinch" example in the Examples workspace. This one pinches the visible cells as a group. I'm working on another example that pinches an individual cell such that the cell follows your fingers during the transition while the other cells follow the default linear path.

UPDATE 3

I've recently added more content offset placement options: Minimal, Center, Top, Left, Bottom and Right. And transitionToCollectionViewLayout: now supports 30+ easing functions courtesy of Warren Moore's AHEasing library.

like image 179
Timothy Moose Avatar answered Nov 05 '22 16:11

Timothy Moose


Thank you Timothy Moose. It works for iOS14 too. I didn't try interactions via finger but for a simple animation of changing a grid layout on list layout it works fine. You can replace

self.collectionView?.contentOffset = ...

with

setContentOffset(_ contentOffset: yourOffset, animated: false)

If you don't do this, content will bounce a bit during the animation.

Here's my example in Swift:

final class SFDocumentsManagerTransitionLayout: UICollectionViewTransitionLayout {
    var fromContentOffset: CGFloat = 0
    var toContentOffset: CGFloat = 0
    
    override var transitionProgress: CGFloat {
        didSet {
            let f = 1 - self.transitionProgress
            let t = self.transitionProgress
            self.collectionView?.setContentOffset(CGPoint(x: 0,
                                                          y: f * self.fromContentOffset + t * self.toContentOffset),
                                                  animated: false)
        }
    }
}
like image 22
Oleksii Nikolaiev Avatar answered Nov 05 '22 16:11

Oleksii Nikolaiev