How to use UIViewPropertyAnimator with auto layout?

Viewed 5401

All the examples I could find online for UIViewPropertyAnimator use views that are laid out by setting frame rather than using auto layout, which is how views are commonly laid out. When using auto layout, the view animates to the position I want, but afterwards I am not sure of how to set it back to its "model" position.

// Move a view that is constrained to the center of its superview
UIViewPropertyAnimator(duration: 1, curve: .linear) {
    testView.center = CGPoint(x: 10, y: 10)
}.startAnimation()

It apparently adds some kind of constraint? I logged the container view's constraints before and after performing the animation:

CONSTRAINTS BEFORE:
view: [<NSLayoutConstraint:0x618000094f00 UIView:0x7fd764004050.centerX == UIView:0x7fd75fd00000.centerX   (active)>, <NSLayoutConstraint:0x618000092de0 UIView:0x7fd764004050.centerY == UIView:0x7fd75fd00000.centerY   (active)>]

CONSTRAINTS AFTER:
view: [<NSLayoutConstraint:0x618000094f00 UIView:0x7fd764004050.centerX == UIView:0x7fd75fd00000.centerX   (active)>, <NSLayoutConstraint:0x618000092de0 UIView:0x7fd764004050.centerY == UIView:0x7fd75fd00000.centerY   (active)>, <NSLayoutConstraint:0x6100000922a0 'UIView-Encapsulated-Layout-Height' UIView:0x7fd75fd00000.height == 667   (active)>, <NSAutoresizingMaskLayoutConstraint:0x610000092570 h=-&- v=-&- 'UIView-Encapsulated-Layout-Left' UIView:0x7fd75fd00000.minX == 0   (active, names: '|':UIWindow:0x7fd75fc07110 )>, <NSAutoresizingMaskLayoutConstraint:0x610000092890 h=-&- v=-&- 'UIView-Encapsulated-Layout-Top' UIView:0x7fd75fd00000.minY == 0   (active, names: '|':UIWindow:0x7fd75fc07110 )>, <NSLayoutConstraint:0x610000091670 'UIView-Encapsulated-Layout-Width' UIView:0x7fd75fd00000.width == 375   (active)>]

What's going on here? How should I handle the animation of views that are positioned with auto layout? Is Apple encouraging users to go back to frame-based layouts?

2 Answers

I made a tutorial for swift 4.1

Example app via github and full tutorial:
http://eon.codes/blog/2018/05/15/Animating-with-constraints/

class AnimVC:UIViewController{


 lazy var square:Square = createSquare()
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .green
        _ = square
    }
}
extension AnimVC{
    func createSquare() -> Square{
        let square = Square()
        square.backgroundColor = .orange
        self.view.addSubview(square)
        _ = {//Define constraints for Square
            square.translatesAutoresizingMaskIntoConstraints = false
            let anchor = Constraint.anchor(square, to: view, align: .centerCenter, alignTo: .centerCenter)
            let size = Constraint.size(square, size: CGSize(width:100,height:100))
            square.anchor = anchor
            square.size = size
            NSLayoutConstraint.activate([anchor.x,anchor.y,size.w,size.h])
        }()

        let tap = UITapGestureRecognizer(target: self, action:  #selector(handleTap))
        square.addGestureRecognizer(tap)
        return square
    }
    @objc func handleTap(_ sender:UITapGestureRecognizer){
        let newConstraint = {
            guard let oldAnchorConstraint = self.square.anchor else {fatalError("err posConstraint not available")}
            NSLayoutConstraint.deactivate([oldAnchorConstraint.x])
            let newAnchorConstraint = Constraint.anchor(self.square, to: self.view, align: .topLeft, alignTo: .topLeft, offset: CGPoint(x:0,y:0))
            NSLayoutConstraint.activate([newAnchorConstraint.x])
            self.square.anchor?.x = newAnchorConstraint.x
        }
        let anim = UIViewPropertyAnimator(duration: 0.3, curve: .easeOut, animations: {
            newConstraint()// ⚠️️ Set the new constraint goal
            self.view.layoutIfNeeded()//⚠️️  Ask the parent view to update its layout
        })
        anim.startAnimation()
    }
}
class Square:UIView{
    var anchor:(x:NSLayoutConstraint,y:NSLayoutConstraint)?
    var size:(w:NSLayoutConstraint,h:NSLayoutConstraint)?
}
Related