How to make flip animations from one UIView to another when using Auto Layout?

My best guess will be that animation is performed via animating layer's ~transform, and that transform is applied from the point where anchorPoint position goes. You could try to solve it in two ways:

First: Make sure you position your views via center constraints, ie. align center of the views which are being transformed (not left or top or trailing and stuff). For example, if both views will have, say "horisontaly + vertically" centered in superview this (not sure) could help.

Second: Create a wrapper view, put those views as subviews and disable autolayout for them.


From How do I animate constraint changes?:

You need to call layoutIfNeeded within the animation block. Apple actually recommends you call it once before the animation block to ensure that all pending layout operations have been completed.

From iOS: How does one animate to new autolayout constraint (height):

After updating your constraint:

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

Replace self.view with a reference to the containing view.


Swift 4 drop in solution.

Do not forget to place animated views to some container view since this is the view which is actually flipping (so placing it inside root view can cause "full-page" flip).

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
        }
    }
}

Auto Layout can only be used to position and resize views. It cannot be used to produce the kind of Core Animation transform needed to produce the flip transition effect. So the short exact answer is no, there is no way to animate this kind of flip by animating constraints.

Using the show/hide transition option

However, there is a simple way to modify the code you are already using so that it is consistent with Auto Layout. The way to do it is (1) to add both your firstView and secondView to your view hierarchy before you order the animation, (2) to ensure that you've added Auto Layout constraints that define the layout of both those views, and (3) to add an option to the animation so that you are only showing/hiding the two views, rather than tearing down and setting up a new view hierarchy.

In other words you want something like:

        // 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];

Why is this what's needed? The reason is that, without the UIViewAnimationOptionShowHideTransitionViews option, the method transitionFromView:toView:duration:options:completion: will actually manipulate the view hierarchy and add the new destination view. If Auto Layout is engaged, then it won't be laid out correctly since it won't have constraints.

You can see an example project showing this approach working here: https://github.com/algal/AutoLayoutFlipDemo

Using view hierarchy manipulation

Alternatively, you can also use your existing call to transitionFromView:toView:duration:options:completion:. But if you're not going to just show a destination view that already had constraints in place, then you need to use the completion block to add those constraints, as follows:

    [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]];
                }];

A working example of this approach is here: https://github.com/algal/AutoLayoutFlipDemo2