Album Animation Part III: Interactive UICollectionViewLayout Transition

系列重写了,这篇文章留在这里记录下错误思路。

系列前两篇文章在此:Part I, Part II。三天前还信心满满地要把 Interactive Push Transition 弄出来,到此刻不得不声明,用尽了我能尝试的办法,没能解决。囧rz。暂时找不到解决办法,先记录下思路,以便日后复盘。

在使用 pinch 手势 push toViewController 的 transition 中发生的事件顺序如下:
1.fromVC.pinch and initial.toVC
2.toVC.viewDidLoad()
3.navigationController.push(toVC)
4.ask navigation transition delegate for: animation controller, interactive controller
5.toVC.viewWillAppear()
6.AnimationController.animateTransition(contextTransition) //Transition 动画开始
7.toVC.collectionView:cellForItemAtIndexPath: //cell 开始在屏幕上
8.toVC.pinch.End and contextTransition.completeTransition() //push transitin 结束
9.toVC.viewDidAppear()

在 WWDC13 Session 218: Custom Transitions Using View Controllers 中工程师一再强调不保证 transition 中 viewDidAppear() 发生在 viewWillAppear() 之后,之前墨客的开发者也发过一篇文章讲过这个问题,不过目前来看这个顺序还算稳定。

由 Part II 中可知,若是 push transition 中有动画没有发生在 AnimationController 的- animateTransition:中,那么终止交互后无法维持这个动画的继续或是返回。将一个 UICollectionviewController push transition 变成可交互的困难在于在无法在 AnimationController 的- animateTransition:发生前获取目标视图中的 Cells,也就无法将 Cell 上的动画放入- animateTransition:以进行交互控制(在 pop 时就不存在这个问题,因此此时 Cells 可以使用 collectionView.visibleCells() 来获取,所以 pop 的交互化就很简单了)。于是思路转换为在手势控制过程中让其布局跟随手势更新,在手势结束后更新布局为需要的布局并将这个过程动画化。这个思路将 cell 的动画的控制权从由交互控制器 InteractiveController 转移到了手势中。此时有两种方案:

  1. UICollectionViewTransitionLayout: iOS 7 推出的新类,用于对布局更新进行动画,在 WWDC13 Session 218: Custom Transitions Using View Controllers 中跟着其他三种视图转换机制一起隆重推出,看起来是个合适的选择。
    Album Animation Part III: Interactive UICollectionViewLayout Transition_第1张图片
    UICollectionView layout transition

    在手势更新过程中只要更新第一个方法返回的UICollectionViewTransitionLayouttransitionProgress属性值就可以对整个过程进行控制。在一个 viewDidAppear 后的 collectionView 使用该类进行布局转换是非常方便以及简单的,一切如你想象的那样。然而,在 push transition 中,这个类无法正常工作。
  2. 手动对布局进行更新:这个听起来更靠谱一点,除了有点恼人的布局动画的性能除外,在结束手势时布局动画会播放两次,就坏在这里了,百思不得其解。手动更新布局有个很容易掉进去的坑:更新布局后,使用 toVC.collectionViewLayout 得到的仅仅是 toVC 初始化时的布局,而不是更新后的布局,必须通过 toVC.collectionView.collectionViewLayout 来获取。这个坑可坑死我了。一直纳闷为啥有时候能手势驱动布局更新,有时候又不能,因为我没注意到这两个方法的区别。

这里有个环节我的代码里可能会存在问题,就是手势的建立。下面代码做的事就是在 pinch 手势的 Begin 状态下,实例化目标控制器 toVC 并 push。

func handlePinchGestureAction(gesture:UIPinchGestureRecognizer){
    let scale = gesture.scale
    if scale >= 1.0{
        if let indexPath = collectionView?.indexPathForItemAtPoint(gesture.locationInView(collectionView)){
            switch gesture.state{
            case .Began:
                interactive = true
                let cell = collectionView?.cellForItemAtIndexPath(indexPath)
                let cellReferencePoint = cell?.convertPoint(CGPointMake(10, 10), toView: self.collectionView!)
                
                let layoutAttributes = self.collectionView!.layoutAttributesForItemAtIndexPath(indexPath)
                let imageOriginInSuperView = self.collectionView!.convertPoint((layoutAttributes?.frame.origin)!, toView: self.collectionView!.superview)
                
                if let toVC = self.storyboard?.instantiateViewControllerWithIdentifier("AlbumVC") as? SDEAlbumViewController{
                    
                    let initialLayout = PushTransitionInitialStateFlowLayout(cell!.convertPoint(CGPointMake(85, 85), toView: self.collectionView!))
                    toVC.collectionView?.setCollectionViewLayout(initialLayout, animated: true)
                    /***进行一些配置***/    
                    //必须将当前的手势添加到toView,在 toVC 自行添加的手势在 push transition 中无法被识别                    
                    gesture.addTarget(toVC, action: "handlePushTransitionActionWithPinchGesture:")
                    toVC.view.addGestureRecognizer(gesture)
                    toVC.interactivePushTransitionDelegate = interactivePushTransitionDelegate
                    
                    self.navigationController?.delegate = self
                    self.navigationController?.pushViewController(toVC, animated: true)
                }
            case .Changed:
                interactive = false
            default:
                interactive = false
            }
        }     
    }
}

上述过程中将 fromView 中的 pinch 手势复制到了 toView 中,因为在 toView 的初始化过程中自行添加的手势在 push transition 中无法被识别。另外当前的 pinch 手势在 fromView 中很难检测到 Ended 或是 Cancelled状态,大概是隔了一层 toView 的缘故,而在 toView 中却检测不到 Begin 状态,Begin 状态的缺失让UICollectionViewTransitionLayout的初始化工作也就是调用- startInteractiveTransitionToCollectionViewLayout:completion:有点困难。不过这个好解决,让这个方法只运行一次的方法很多,接下来只要在手势的 Changed, Ended, Cancelled 的状态中走一下 UICollectionViewTransitionLayout 的标准流程就可以了。但问题来了,这个类似乎很脆弱,得小心翼翼地使用 pinch,你能看到预期的效果,一旦你快速地使用,基本上就会出现以下错误,google 无果。
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'the collection was not prepared for an interactive transition. see startInteractiveTransitionToCollectionViewLayout:completion:'

Photos 和 Paper 里的 pinch 手势驱动 push transition 就是我想要的效果,前两者基本从一个 Stack 布局的 CollectionView 转换到最常见的 FlowLayout 布局的 CollectionView,这个方案在WWDC13 Session 218中演示过,demo 代码在此: ViewTransition Demo Code in WWDC13 Session 218。但我着手解决的这个问题和它们有点不一样,我想要的是从 toView 出现后进行布局转换,然而却不遂我愿,也许这条路走不通,等我有心情了再试试。

今天在微博上看到 CKWaveCollectionViewTransition 这个效果,主要是 cell 之间的时间差调节得非常惊艳,之前我也有类似的调整,但基本上看不出效果来,看人家的代码,用于调整时间差的算法还是很复杂的,反正扫一眼代码就晕菜了。虽然动画时间有点长,但这个惊艳的画面表现我忍了。动画在质量而不在数量,相比之下,我尝试做了好几种效果,却差了老远。

在摸索过程中,也许有很多地方我不了解而犯了错,就比如动画放在哪儿执行,就只能在- viewDidLoad, - viewWillAppear:, - viewWillDisappear:中挨个试;还有出现那个问题 google 无果后,我也没有手段去探知哪个环节出错,尽管从函数调用帧上看到出错的地方似乎是内部的实现问题,但这种束手无策的境地让我很不安,是时候好好学习 debug 了。前年实习的时候,我的上司面对一些问题总是会"异想天开"地在一些环节上下手,在我看来不了解系统的相关流程,这么瞎搞效率很低,但有不少时候我的上司这么瞎搞搞问题还真解决了。唉,踢一脚有时候就能解决大部分的电器问题。

另外,看了人家的代码,能直接拿来用,而我的则不行,要好好设计下代码才行。

目前这个问题我实在有点厌烦了,绞尽脑汁,就差那么一点点,然而路还是被堵死了,这种无望烦躁在长时间盯着电脑精神迷糊或是 Xcode 对 Swift 的糟糕支持下更加严重了,我要转移下注意力。

你可能感兴趣的:(Album Animation Part III: Interactive UICollectionViewLayout Transition)