使用自动布局时如何将动画从一个 UIView 翻转到另一个?

Posted

技术标签:

【中文标题】使用自动布局时如何将动画从一个 UIView 翻转到另一个?【英文标题】:How to make flip animations from one UIView to another when using Auto Layout? 【发布时间】:2014-10-12 20:55:35 【问题描述】:

我一直习惯用下面的代码在一个视图和另一个视图之间制作翻转动画:

        [UIView transitionFromView:firstView
                        toView:secondView
                      duration:0.6
                       options:UIViewAnimationOptionTransitionFlipFromLeft
                    completion:^(BOOL finished) 
                        // finish code here
                    ];

效果很好,翻转看起来很自然。

但是当我在使用 Auto Layout 在情节提要上定义的视图上使用它时,事情开始变得混乱 - 视图在动画之后被调整大小和移动。

有没有办法通过动画约束来动画这种翻转?

【问题讨论】:

【参考方案1】:

自动布局只能用于定位和调整视图大小。它不能用于产生产生翻转过渡效果所需的那种核心动画变换。所以简短的确切答案是否定的,没有办法通过动画约束来动画这种翻转。

使用显示/隐藏转换选项

但是,有一种简单的方法可以修改您已在使用的代码,使其与自动布局保持一致。这样做的方法是 (1) 在您订购动画之前将 firstViewsecondView 添加到视图层次结构中,(2) 确保您添加了自动布局约束定义这两个视图的布局,以及 (3) 为动画添加一个选项,以便您只显示/隐藏两个视图,而不是拆除并设置新的视图层次结构。

换句话说,你想要这样的东西:

        // assert: secondView added to view hierarchy
        // assert: secondView.hidden == true
        // assert: secondView has correct constraints
        [UIView transitionFromView:firstView
                            toView:secondView
                           duration:0.6
                            options:UIViewAnimationOptionTransitionFlipFromLeft | UIViewAnimationOptionShowHideTransitionViews
                         completion:nil];

为什么需要这个?原因是,如果没有UIViewAnimationOptionShowHideTransitionViews 选项,transitionFromView:toView:duration:options:completion: 方法实际上将操纵视图层次结构并添加新的目标视图。如果启用了 Auto Layout,则不会正确布局,因为它没有约束。

您可以在此处查看展示此方法的示例项目:https://github.com/algal/AutoLayoutFlipDemo

使用视图层次结构操作

或者,您也可以使用现有呼叫transitionFromView:toView:duration:options:completion:。但是如果你不打算只显示一个已经有约束的目标视图,那么你需要使用完成块来添加这些约束,如下所示:

    [UIView transitionFromView:firstView
                        toView:secondView
                       duration:0.6
                        options:UIViewAnimationOptionTransitionFlipFromLeft
                     completion:^(BOOL finished) 
 [self.view addConstraint:[NSLayoutConstraint                               
  constraintWithItem:secondView
  attribute:NSLayoutAttributeCenterX
  relatedBy:NSLayoutRelationEqual
  toItem:self.view
  attribute:NSLayoutAttributeCenterX
  multiplier:1 constant:0]];

 [self.view addConstraint:[NSLayoutConstraint constraintWithItem:secondView
  attribute:NSLayoutAttributeCenterY
  relatedBy:NSLayoutRelationEqual
  toItem:self.view
  attribute:NSLayoutAttributeCenterY
  multiplier:1 constant:0]];
                ];

此方法的工作示例如下:https://github.com/algal/AutoLayoutFlipDemo2

【讨论】:

非常感谢!您的回答是救命稻草。【参考方案2】:

我最好的猜测是动画是通过动画层的 ~transform 执行的,并且该变换是从 anchorPoint 位置所在的点开始应用的。您可以尝试通过两种方式解决它:

第一: 确保通过中心约束定位视图,即。对齐正在转换的视图的中心(不是左、上或尾随等)。例如,如果两个视图都有,说“horisontaly + vertical”在超级视图中居中,这(不确定)可能会有所帮助。

第二: 创建一个包装视图,将这些视图作为子视图并禁用它们的自动布局。

【讨论】:

【参考方案3】:

来自How do I animate constraint changes?:

您需要在动画块中调用 layoutIfNeeded。 Apple actually recommends你在动画块之前调用一次,确保所有待处理的布局操作都已经完成。

来自ios: How does one animate to new autolayout constraint (height):

更新约束后:

[UIView animateWithDuration:0.5 animations:^[self.view layoutIfNeeded];];

self.view 替换为对包含视图的引用。

【讨论】:

我在同一个控制器上有两个视图。将其视为需要翻转的卡片的 2 个面。 哦,我明白了,你能在你的问题中澄清一下吗?【参考方案4】:

Swift 4 加入解决方案。

不要忘记将动画视图放置到某个容器视图中,因为这是实际翻转的视图(因此将其放置在根视图中会导致“整页”翻转)。

class ViewController: UIViewController 

    private enum Side 
        case head
        case tail
    
    private let containerView = UIView(frame: .zero)
    private let firstView = UIView(frame: .zero)
    private let secondView = UIView(frame: .zero)
    private var currentSide: Side = .head

    override func viewDidLoad() 
        super.viewDidLoad()

        // container view
        view.addSubview(containerView)
        containerView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            containerView.heightAnchor.constraint(equalToConstant: 100),
            containerView.widthAnchor.constraint(equalToConstant: 100),
            containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            containerView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
            ])
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapContainer))
        containerView.addGestureRecognizer(tapGesture)

        // first view
        containerView.addSubview(firstView)
        firstView.translatesAutoresizingMaskIntoConstraints = false
        firstView.backgroundColor = .red
        NSLayoutConstraint.activate([
            firstView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
            firstView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
            firstView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
            firstView.topAnchor.constraint(equalTo: containerView.topAnchor),
            ])

        // second view
        containerView.addSubview(secondView)
        secondView.translatesAutoresizingMaskIntoConstraints = false
        secondView.backgroundColor = .yellow
        secondView.isHidden = true
        NSLayoutConstraint.activate([
            secondView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
            secondView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
            secondView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
            secondView.topAnchor.constraint(equalTo: containerView.topAnchor),
            ])
    

    @objc
    func tapContainer() 
        switch currentSide 
        case .head:
            UIView.transition(from: firstView,
                              to: secondView,
                              duration: 1,
                              options: [.transitionFlipFromRight, .showHideTransitionViews],
                              completion: nil)
            currentSide = .tail
        case .tail:
            UIView.transition(from: secondView,
                              to: firstView,
                              duration: 1,
                              options: [.transitionFlipFromLeft, .showHideTransitionViews],
                              completion: nil)
            currentSide = .head
        
    

【讨论】:

【参考方案5】:

如果对任何人有帮助,这就是你在 Swift 上的做法,在 Swift 上,选项是静态变量,你只需要在 options 参数中的数组上传递你需要的变量。

   UIView.transition(from: firstView, to: secondView, duration: 0.5, options: [.transitionFlipFromLeft, .showHideTransitionViews])  (_) in
        

这里是选项 https://developer.apple.com/documentation/uikit/uiviewanimationoptions

【讨论】:

以上是关于使用自动布局时如何将动画从一个 UIView 翻转到另一个?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 UIView 创建自定义翻转动画

在 Swift 中,如何使用自动布局为 UIView 设置动画,就像页面滑入一样?

如何使用自动布局在 UIScrollView 中为视图高度设置动画?

如何在 iOS 中为两个 UIView 翻转设置动画?

使用自动布局动画 UIView 动画 UITableView 复选标记

如何在更改uiview的隐藏模式时添加动画?