11

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?

brainjam
  • 18,863
  • 8
  • 57
  • 82
Tim Arnold
  • 8,359
  • 8
  • 44
  • 67

2 Answers2

21

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.

Timothy Moose
  • 9,895
  • 3
  • 33
  • 44
  • This looks promising, I'm excited to really take a look and see if it solves my issues. One question: is there actually an interactive component (e.g., pinching to go back and forth between layouts, potentially canceling the transition) in your example project? (I didn't find an obvious one) – Tim Arnold Oct 18 '13 at 15:14
  • No, there isn't. I may take a stab at a second example this weekend. – Timothy Moose Oct 18 '13 at 15:26
  • The linked repository is excellent. Thanks for taking the time to create and share it. – jrturton Jan 22 '14 at 14:19
  • @jrturton Thank you. Hoping to add more soon. – Timothy Moose Jan 22 '14 at 16:43
  • Thanks for spending the time to create this! The update 2 link is broken, but other than that everything is solid! – Jack Mar 14 '14 at 16:33
  • 1
    @JackWu Thanks. Fixed links. Sample projects were merged into a single app. – Timothy Moose Mar 14 '14 at 16:43
  • @TimothyMoose your `TLTransitionLayout ` class is an absolute god send. Now I just have to try and convert it all to swift :( – random Dec 02 '15 at 18:15
1

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)
        }
    }
}
aheze
  • 24,434
  • 8
  • 68
  • 125