视图控制器转换动画子视图位置

Ale*_*der 4 animation objective-c uiviewcontroller ios

我正在尝试在两个视图控制器之间创建一个简单的过渡动画,两个视图控制器都具有相同的标签.我只想将标签从第一个视图控制器中的位置动画到第二个视图控制器中的位置(见下图).

查看控制器图

我已经设置了我的视图控制器以使用自定义动画控制器,我可以通过插座访问视图控制器和标签.

在动画块中,我只是将第一个视图控制器上的标签框架设置为第二个视图控制器上的标签框架.

[UIView animateWithDuration:self.duration animations:^{
    fromViewController.label.frame = toViewController.titleLabel.frame;
} completion:^(BOOL finished) {
    [transitionContext completeTransition:finished];
}];
Run Code Online (Sandbox Code Playgroud)

而不是标签从屏幕中间移动到左上角的预期效果,一旦动画开始,标签就位于右下角,然后动画到中间.

我尝试预先打印出标签的位置,这显示了我在故事板中看到的相同框架:

fromViewController.label.frame: {{115.5, 313}, {144, 41}}
toViewController.titleLabel.frame: {{16, 12}, {144, 41}}
Run Code Online (Sandbox Code Playgroud)

我不知道为什么我没有得到预期的行为,以及它取而代之的是什么.

关于我可以改变什么以使我的动画正确运行以及为什么我看到这种行为的任何建议将不胜感激.

Rob*_*Rob 24

你提到了子视图的动画,但你没有谈论整体动画,但我倾向于使用动画的容器视图,以避免任何潜在的混淆/问题,如果你动画子视图主要同时查看.但我倾向于:

  1. 在"从"视图中创建子视图的快照,然后隐藏子视图;
  2. 在"到"视图中创建子视图的快照,然后隐藏子视图;
  3. 将所有这些frame值转换为容器的坐标空间,并将所有这些快照添加到容器视图中;
  4. alpha在零处开始"到"快照' (因此它们淡入);
  5. 动画将"到"快照更改为最终目标,将其更改alpha1.
  6. 同时将"从"快照设置动画到"到"视图最终目的地的位置,并将它们alpha设置为零(因此它们淡出,与第4点结合,产生一种交叉溶解).
  7. 完成所有操作后,删除快照并取消隐藏其快照已设置动画的子视图.

净效应是标签从一个位置滑动到另一个位置,如果初始和最终内容不同,则在它们被移动时产生交叉溶解.

例如:

在此输入图像描述

通过使用快照动画的容器视图,它独立于您可能正在对目标场景的主视图执行的任何动画.在这种情况下,我从右边滑入,但你可以做任何你想做的事.

或者,您可以使用多个子视图执行此操作:

在此输入图像描述

(就个人而言,如果是这种情况,实际上一切都在滑动,我会失去主视图的滑动动画,因为它现在变得分散注意力,但它给你基本的想法.另外,在我的动画中,我换了围绕哪个视图到另一个视图,你永远不会做,但我只是想说明灵活性和褪色.)

为了呈现上述内容,我在Swift 4中使用了以下内容:

protocol CustomTransitionOriginator {
    var fromAnimatedSubviews: [UIView] { get }
}

protocol CustomTransitionDestination {
    var toAnimatedSubviews: [UIView] { get }
}

class Animator: NSObject, UIViewControllerAnimatedTransitioning {
    enum TransitionType {
        case present
        case dismiss
    }

    let type: TransitionType

    init(type: TransitionType) {
        self.type = type
        super.init()
    }

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 1.0
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let fromVC = transitionContext.viewController(forKey: .from) as! CustomTransitionOriginator  & UIViewController
        let toVC   = transitionContext.viewController(forKey: .to)   as! CustomTransitionDestination & UIViewController

        let container = transitionContext.containerView

        // add the "to" view to the hierarchy

        toVC.view.frame = fromVC.view.frame
        if type == .present {
            container.addSubview(toVC.view)
        } else {
            container.insertSubview(toVC.view, belowSubview: fromVC.view)
        }
        toVC.view.layoutIfNeeded()

        // create snapshots of label being animated

        let fromSnapshots = fromVC.fromAnimatedSubviews.map { subview -> UIView in
            // create snapshot

            let snapshot = subview.snapshotView(afterScreenUpdates: false)!

            // we're putting it in container, so convert original frame into container's coordinate space

            snapshot.frame = container.convert(subview.frame, from: subview.superview)

            return snapshot
        }

        let toSnapshots = toVC.toAnimatedSubviews.map { subview -> UIView in
            // create snapshot

            let snapshot = subview.snapshotView(afterScreenUpdates: true)!// UIImageView(image: subview.snapshot())

            // we're putting it in container, so convert original frame into container's coordinate space

            snapshot.frame = container.convert(subview.frame, from: subview.superview)

            return snapshot
        }

        // save the "to" and "from" frames

        let frames = zip(fromSnapshots, toSnapshots).map { ($0.frame, $1.frame) }

        // move the "to" snapshots to where where the "from" views were, but hide them for now

        zip(toSnapshots, frames).forEach { snapshot, frame in
            snapshot.frame = frame.0
            snapshot.alpha = 0
            container.addSubview(snapshot)
        }

        // add "from" snapshots, too, but hide the subviews that we just snapshotted
        // associated labels so we only see animated snapshots; we'll unhide these
        // original views when the animation is done.

        fromSnapshots.forEach { container.addSubview($0) }
        fromVC.fromAnimatedSubviews.forEach { $0.alpha = 0 }
        toVC.toAnimatedSubviews.forEach { $0.alpha = 0 }

        // I'm going to push the the main view from the right and dim the "from" view a bit,
        // but you'll obviously do whatever you want for the main view, if anything

        if type == .present {
            toVC.view.transform = .init(translationX: toVC.view.frame.width, y: 0)
        } else {
            toVC.view.alpha = 0.5
        }

        // do the animation

        UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
            // animate the snapshots of the label

            zip(toSnapshots, frames).forEach { snapshot, frame in
                snapshot.frame = frame.1
                snapshot.alpha = 1
            }

            zip(fromSnapshots, frames).forEach { snapshot, frame in
                snapshot.frame = frame.1
                snapshot.alpha = 0
            }

            // I'm now animating the "to" view into place, but you'd do whatever you want here

            if self.type == .present {
                toVC.view.transform = .identity
                fromVC.view.alpha = 0.5
            } else {
                fromVC.view.transform = .init(translationX: fromVC.view.frame.width, y: 0)
                toVC.view.alpha = 1
            }
        }, completion: { _ in
            // get rid of snapshots and re-show the original labels

            fromSnapshots.forEach { $0.removeFromSuperview() }
            toSnapshots.forEach   { $0.removeFromSuperview() }
            fromVC.fromAnimatedSubviews.forEach { $0.alpha = 1 }
            toVC.toAnimatedSubviews.forEach { $0.alpha = 1 }

            // clean up "to" and "from" views as necessary, in my case, just restore "from" view's alpha

            fromVC.view.alpha = 1
            fromVC.view.transform = .identity

            // complete the transition

            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        })
    }
}

// My `UIViewControllerTransitioningDelegate` will specify this presentation 
// controller, which will clean out the "from" view from the hierarchy when
// the animation is done.

class PresentationController: UIPresentationController {
    override var shouldRemovePresentersView: Bool { return true }
}
Run Code Online (Sandbox Code Playgroud)

然后,允许所有上述的工作,如果我从转变ViewControllerSecondViewController,我会指定哪些子视图我移动,我移动到哪些:

extension ViewController: CustomTransitionOriginator {
    var fromAnimatedSubviews: [UIView] { return [label] }
}

extension SecondViewController: CustomTransitionDestination {
    var toAnimatedSubviews: [UIView] { return [label] }
}
Run Code Online (Sandbox Code Playgroud)

为了支持解雇,我将添加相反的协议一致性:

extension ViewController: CustomTransitionDestination {
    var toAnimatedSubviews: [UIView] { return [label] }
}

extension SecondViewController: CustomTransitionOriginator {
    var fromAnimatedSubviews: [UIView] { return [label] }
}
Run Code Online (Sandbox Code Playgroud)

现在,我不希望你迷失在所有这些代码中,所以我建议专注于高级设计(我在前面列举的前七个点).但希望这足以让你遵循基本的想法.