Brosef
Brosef

Reputation: 3095

Changing position of CAShapeLayer using an animation

I have a UITextField centered in my viewcontroller. I'm using a CAShapeLayer underneath the textfield that grows in width as text is typed in the textfield. Once the user presses enter, a UITableView that is hidden at the bottom of the view slides up. I'm able to get the tableview and textfield to slide up by changing their constraints.

func moveUp(){

    searchBarTopConstraint.constant = 0
    tableViewTopConstraint.constant = 0

    UIView.animate(withDuration: 2.0) {

        self.view.layoutSubviews()

    }
}

However, I can't figure out how to get the CAShapeLayer to slide up with the textfield. I tried creating a function that moves the line and putting it in a UIView animation with a completion handler, but the line will either move up after the animation or before.

    UIView.animate(withDuration: 2.00, delay: 0.0, options: [], animations: {

        self.view.layoutSubviews()
        self.moveLine()

    }, completion: { (finished: Bool) in

//            self.moveLine()

    })

The y position of the line is determined by the textfields position (this is a concise version of the moveLine()):

    let y = searchField.frame.origin.y + searchField.frame.size.height + 6
    linePath.move(to: CGPoint(x:x1, y:y))
    linePath.addLine(to: CGPoint(x:x2, y:y))

Upvotes: 1

Views: 4527

Answers (1)

Rob
Rob

Reputation: 437422

There are two issues:

  1. If moving views via constraints, you call layoutIfNeeded() in the animation block, not layoutSubviews()

    As the layoutSubviews() documentation says:

    You should not call this method directly. If you want to force a layout update, call the setNeedsLayout() method instead to do so prior to the next drawing update. If you want to update the layout of your views immediately, call the layoutIfNeeded() method.

    In your case, put layoutIfNeeded() inside your animate closure, not layoutSubviews().

  2. Note that when you do this properly, this moves the view in which you have the CAShapeLayer, not moving the CAShapeLayer itself. So, if you have a shape layer that you want to move, you'd put that shape layer with a view with the constraints, and then move the view with the above layoutIfNeeded() call.

    Theoretically, you actually can animate the moving of a CAShapeLayer within a view using CABasicAnimation:

    let position = CGPoint(...)                          // the new origin of the CAShapeLayer within its view
    
    let animation = CABasicAnimation(keyPath: "position")
    animation.fromValue = shapeLayer.position            // animate from current position ...
    animation.toValue = position                         // ... to whereever the new position is
    animation.duration = 2
    shapeLayer.position = position                       // set the shape's final position to be the new position so when the animation is done, it's at its new "home"
    shapeLayer.add(animation, forKey: nil)               // animate from `fromValue` to `toValue`
    

    Personally, I think it's a little confusing to move shape layers within a view, and I prefer to put the shape layer within its own view, add that view within your view hierarchy with the associated constraints, and then animate that view's constraints. It makes debugging of views with the Xcode view debugger easier, IMHO.

Upvotes: 2

Related Questions