Search code examples
iosswiftanimationautolayoutconstraints

How to animate centered square to the top


I have a UIView in a portrait-only app.

  • The view is centered vertically and horizontally with AutoLayout ("manually" using storyboards).
  • The width equals the (main)view.width * 0.9
  • The height is the same size of the width (it is a square).

I want to tap a button inside this UIView and animate it only vertically until it reaches the top border of the screen (eg. height*0.9, 10 pts, whatever is possible).

When I click again, I want to reposition back the view to its original position (centered as it was when I first tapped).

During the transition the square should not be tappable.

After reading many posts I could not understand what's the best way to do this (I red mainly developers saying old techniques using centerX should be avoided and lamentations about some versions of the SO behaving in strange ways).

I suppose I should find a way to get the current "position" of the constraints and to assign a constraint the "final" position, but I was not able to do it.

Any help is appreciated


Solution

  • While you can use Autolayout to animate - to take the constraint constraining the centerY and set its constant to a value that would move to the top (e.g., constant = -(UIScreen.main.bounds.height / 2)), I would recommend using view's transform property.

    So to move the view to the top you can use:

    let topMargin = CGFloat(20)
    let viewHalfHeight = self.view.bounds.height / 2
    let boxHalfHeight = self.box.bounds.height / 2
    UIView.animate(withDuration: 0.2) {
        box.transform = CGAffineTransform.identity
             .translatedBy(x: 0, y: -(viewHalfHeight - (boxHalfHeight + topMargin)))
    }
    

    You are moving box.center related to the view.center - so if you want to move the box to the top, you have to move its center by half a view's height (because the view's centerY is exactly height / 2 far from the view's top). That is not enough though, because then only a bottom half of the box is visible (now the box.centerY == view.top). Therefore you have to move it back by the box.bounds.height / 2 (in my code boxHalfHeight) - to make the top half visible. And to that boxHalfHeight you add topMargin so that there is some margin to the top.

    Then, to move the box back to original position:

    UIView.animate(withDuration: 0.2) {
        box.transform = CGAffineTransform.identity
    }
    

    EDIT

    If you really want to go with autolayout, you have to have a reference to the centerY constraint, so for example if it is created this way:

    let boxCenterYConstraint = self.box.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)
    boxCenterYConstraint.isActive = true
    

    Then you can try this:

    // calculating the translation is the same
    let topMargin = CGFloat(20)
    let viewHalfHeight = self.view.bounds.height / 2
    let boxHalfHeight = self.box.bounds.height / 2
    let diff = -(viewHalfHeight - (boxHalfHeight + topMargin))
    
    boxCenterYConstraint.constant = diff
    self.view.setNeedsLayout()
    UIView.animate(withDuration: 0.2) {
        self.view.layoutIfNeeded()
    }
    

    And animation back:

    boxCenterYConstraint.constant = 0
    self.view.setNeedsLayout()
    UIView.animate(withDuration: 0.2) {
        self.view.layoutIfNeeded()
    }