ios - View Controller Transition 动画 subview 位置

标签 ios objective-c animation uiviewcontroller

我正在尝试在两个 View Controller 之间创建一个简单的过渡动画,这两个 View Controller 具有相同的标签。我只是想将标签从它在第一个 View Controller 中的位置动画化到它在第二个 View Controller 中的位置(见下图)。

View Controller Illustration

我已将我的 View Controller 设置为使用自定义动画 Controller ,我可以通过 socket 访问 View Controller 和标签。

在动画 block 中,我只是将第一个 View Controller 上标签的框架设置为第二个 View Controller 上标签的框架。

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

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

我尝试预先打印出标签的位置,它显示了我在 Storyboard 中看到的同一帧:

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

我不知道为什么我没有得到预期的行为,也不知道发生了什么。

对于我可以更改哪些内容以使我的动画正确运行以及为什么我会看到此行为的任何建议,我们将不胜感激。

最佳答案

你提到了 subview 的动画,但你没有谈论整体动画,但我倾向于使用容器 View 来制作动画,以避免在为 subview 设置动画时出现任何潜在的混淆/问题同时显示主视图。但我倾向于:

  1. 对“来自” View 中 subview 的位置进行快照,然后隐藏 subview ;
  2. 对“to” View 中 subview 的位置进行快照,然后隐藏 subview ;
  3. 将所有这些frame值转换为容器的坐标空间,并将所有这些快照添加到容器 View ;
  4. 从零开始“到”快照的 alpha(因此它们淡入);
  5. 动画将“to”快照更改为最终目的地,将它们的 alpha 变回 1
  6. 同时将“从”快照动画化到“到” View 最终目的地的位置并将它们的 alpha 动画化为零(因此它们淡出,结合第 4 点,产生一种交叉溶解)。
  7. 完成所有操作后,删除快照并取消隐藏其快照已设置动画的 subview 。

最终效果是标签从一个位置滑动到另一个位置,如果初始内容和最终内容不同,则在它们移动时会产生交叉渐隐。

例如:

enter image description here

通过将容器 View 用于快照动画,它独立于您可能对目标场景的主视图执行的任何动画。在本例中,我将它从右侧滑入,但您可以随心所欲。

或者,您可以对多个 subview 执行此操作:

enter image description here

(就个人而言,如果是这种情况,几乎所有东西都在滑动,我会失去主视图的滑动动画,因为它现在变得分散注意力,但它给了你基本的想法。另外,在我的关闭动画中,我把哪个 View 换成另一个 View ,这是你永远不会做的,但我只是想说明灵 active 和褪色。)

为了呈现以上内容,我在 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,我会指定我要移动的 subview 和我要搬到哪些地方:

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

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

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

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

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

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

关于ios - View Controller Transition 动画 subview 位置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46596481/

相关文章:

ios - 在自定义 UIVIew 中访问属性

ios - 扩展 Swift 数组以复制 indexOfObject 函数

iphone - 从 CGAffineTransform 获取旋转

ios - 变换标签字体大小

java - 如何将 View 移动到屏幕最近的边缘?

CSS变量和动画

ios - Xcode 4.5 UITableView

ios - Xcode 在验证过程中遇到了问题?

ios - 如何在 LiveSDK-iOS 中持久化 LiveConnectSession

iphone - iOS 推特账户访问