View Controller Transition动画子视图位置 [英] View Controller Transition animate subview position

查看:75
本文介绍了View Controller Transition动画子视图位置的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to create a simple transition animation between two view controllers, both of which have the same label. I simply want to animate the label from its position in the first view controller, to its position in the second (see below illustration).

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

I have set up my view controllers to use a custom animation controller, where I have access to both view controllers and the label through an outlet.

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

In the animation block, I simply set the frame of the label on the first view controller to that of the label on the second view controller.

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

标签不是从屏幕中间移动到左上角的预期效果,而是在动画开始后立即将标签放置在右下角,然后动画到中间.

Instead of the intended effect of the label moving from the middle of the screen to the upper left corner, as soon as the animation begins the label is positioned in the bottom right corner and then animates to the middle.

我尝试事先打印出标签的位置,这显示了我在情节提要中看到的同一帧:

I tried printing out the positions of the labels beforehand, which shows the same frame I see in the storyboard:

fromViewController.label.frame: {{115.5, 313}, {144, 41}}
toViewController.titleLabel.frame: {{16, 12}, {144, 41}}

我不知道为什么我没有得到预期的行为,以及发生的事情.

I have no idea as to why I'm not getting the intended behavior, and what is happening in its place.

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

Any suggestions as to what I can change to make my animation run correctly and why I'm seeing this behavior would be greatly appreciated.

推荐答案

您提到了子视图的动画,但是没有谈论整体动画,但是我倾向于将容器视图用于动画,避免在同时为子视图设置主视图动画时避免任何潜在的混乱/问题.但我倾向于:

You mention the animation of the subviews but you don't talk about the overall animation, but I'd be inclined to use the container view for the animation, to avoid any potential confusion/problems if you're animating the subview and the main view simultaneously. But I'd be inclined to:

  1. 在"from"视图中创建子视图的快照,然后隐藏子视图;
  2. 对子视图在至"视图中的位置进行快照,然后隐藏子视图;
  3. 将所有这些 frame 值转换为容器的坐标空间,并将所有这些快照添加至容器视图;
  4. 从零开始将收件人"快照的 alpha (使其淡入);
  5. 对将"to"快照更改为最终目的地进行动画处理,将其 alpha 更改回 1 .
  6. 同时对"from"快照进行动画处理,以"to"查看最终目的地的位置,并将其 alpha 动画化为零(因此它们逐渐淡出,与点4相结合,产生了一种交叉溶解).
  7. 完成所有操作后,删除快照并取消隐藏为其快照设置动画的子视图.
  1. Make snapshots of where the subviews in the "from" view and then hide the subviews;
  2. Make snapshots of where the subviews in the "to" view and then hide the subviews;
  3. Convert all of these frame values to the coordinate space of the container and add all of these snapshots to the container view;
  4. Start the "to" snapshots' alpha at zero (so they fade in);
  5. Animate the changing of the "to" snapshots to their final destination changing their alpha back to 1.
  6. Simultaneously animate the "from" snapshots to the location of the "to" view final destination and animate their alpha to zero (so they fade out, which combined with point 4, yields a sort of cross dissolve).
  7. When all done, remove the snapshots and unhide the subviews whose snapshots were animated.

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

The net effect is a sliding of the label from one location to another, and if the initial and final content were different, yielding a cross dissolve while they're getting moved.

例如:

通过使用容器视图作为快照的动画,它独立于您可能要对目标场景的主视图进行的任何动画处理.在这种情况下,我会将其从右侧滑入,但是您可以做任何您想做的事情.

By using the container view for the animation of the snapshots, it's independent of any animation you might be doing of the main view of the destination scene. In this case I'm sliding it in from the right, but you can do whatever you want.

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

Or, you can do this with multiple subviews:

(个人而言,如果是这种情况,实际上所有内容都在滑动,那么我会丢失主视图的滑动动画,因为它现在变得分散了注意力,但它为您提供了基本概念.此外,在我关闭动画时,我交换了将哪个视图换成另一个视图的方法,这是您永远都不会做的,但我只是想说明灵活性和淡入淡出.)

(Personally, if this were the case, where practically everything was sliding around, I'd lose the sliding animation of the main view because it's now becoming distracting, but it gives you the basic idea. Also, in my dismiss animation, I swapped around which view is being to another, which you'd never do, but I just wanted to illustrate the flexibility and the fading.)

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

To render the above, I used the following in 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 }
}

然后,为了使以上所有功能都能正常工作,如果我要从 ViewController 过渡到 SecondViewController ,则要指定要移出的子视图,我要去哪一个?

Then, to allow all of the above to work, if I'm transitioning from ViewController to SecondViewController, I'd specify what subviews I'm moving from and which ones I'm moving to:

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

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

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

And to support the dismiss, I'd add the converse protocol conformance:

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

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

现在,我不想让您迷失所有这些代码,因此,我建议您专注于高级设计(我在顶部列举了前七个要点).但希望这足以使您遵循基本思想.

Now, I don't want you to get lost in all of this code, so I'd suggest focusing on the high-level design (those first seven points I enumerated at the top). But hopefully this is enough for you to follow the basic idea.

这篇关于View Controller Transition动画子视图位置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆