I am trying to animate constraints in a custom UIView
. The ending layout is correct, but I cannot get UIView.animate
to work correctly, and I have seen that everyone is calling self.view.layoutIfNeeded()
from a view controller, so perhaps that is my problem. I am calling self.layoutIfNeeded()
on the custom view itself. In case the hierarchy matters, I have as follows: ViewController > TableView > Cell > View > AnimationView.
class AnimationView: UIView {
// MARK: - Properties
let ratioMultiplier: CGFloat = 0.28
var listLeadingConstraint: NSLayoutConstraint!
var listTrailingConstraint: NSLayoutConstraint!
var checkmarkLeadingConstraint: NSLayoutConstraint!
var checkmarkTrailingConstraint: NSLayoutConstraint!
// MARK: - UI Components
lazy var listImageView: UIImageView = {
let image = UIImage(named: "list.small")
let result = UIImageView(image: image)
return result
}()
lazy var checkmarkImageView: UIImageView = {
let image = UIImage(named: "checkmark.small")
let result = UIImageView(image: image)
return result
}()
// MARK: - Inits
init() {
super.init(frame: .zero)
setupView()
}
required init?(coder: NSCoder) {
return nil
}
// MARK: - Methods
func playAnimation() {
// 1. change constraints
listLeadingConstraint.constant += (listImageView.frame.size.width * ratioMultiplier)
listTrailingConstraint.constant += (listImageView.frame.size.width * ratioMultiplier)
checkmarkLeadingConstraint.constant += (checkmarkImageView.frame.size.width * ratioMultiplier)
checkmarkTrailingConstraint.constant += (checkmarkImageView.frame.size.width * ratioMultiplier)
self.layoutIfNeeded()
// 2. animate constraints
UIView.animate(withDuration: 3.0) {
self.layoutIfNeeded()
}
}
func setupView() {
// 1. add subviews
addSubview(listImageView)
addSubview(checkmarkImageView)
// 2. add constraints
ConstraintHelper.anchor(view: checkmarkImageView, options: [.top, .bottom])
checkmarkLeadingConstraint = checkmarkImageView.leadingAnchor.constraint(equalTo: leadingAnchor)
checkmarkTrailingConstraint = checkmarkImageView.trailingAnchor.constraint(equalTo: trailingAnchor)
checkmarkLeadingConstraint.constant -= checkmarkImageView.frame.size.width * ratioMultiplier
checkmarkTrailingConstraint.constant -= checkmarkImageView.frame.size.width * ratioMultiplier
NSLayoutConstraint.activate([checkmarkLeadingConstraint, checkmarkTrailingConstraint])
ConstraintHelper.anchor(view: listImageView, options: [.top, .bottom])
listLeadingConstraint = listImageView.leadingAnchor.constraint(equalTo: leadingAnchor)
listTrailingConstraint = listImageView.trailingAnchor.constraint(equalTo: trailingAnchor)
NSLayoutConstraint.activate([listLeadingConstraint, listTrailingConstraint])
}
}
Additional info: playAnimation()
is called from cellForRow
on the table view and I also tried using self.superview?.layoutIfNeeded()
calls to no success. The ConstraintHelper
calls translatesAutoresizingMaskIntoConstraints
and the constraints are working correctly, so don't worry about that. Any help would be great.
The reason why your initial post is not working is that you called your first layoutIfNeeded()
after the constraint changes. When your animation block is executed, there's no changes to be animated at all.
It does not matter whether you change the constraints in or out of the animation block.
The common steps are:
layoutIfNeeded()
on the rootest view that your constraints may affect. In your case, self
is ok because all the involving constraints are for the subviews. In some cases, you might change a constraint for the self view, then you need to call layoutIfNeeded
on its superview.layoutIfNeeded()
in a animation block on the same view as you did in the first step.Please try the following alternative:
func playAnimation() {
// Request a layout to clean pending changes for the view if there's any.
self.layoutIfNeeded()
// Make your constraint changes.
listLeadingConstraint.constant += (listImageView.frame.size.width * ratioMultiplier)
listTrailingConstraint.constant += (listImageView.frame.size.width * ratioMultiplier)
checkmarkLeadingConstraint.constant += (checkmarkImageView.frame.size.width * ratioMultiplier)
checkmarkTrailingConstraint.constant += (checkmarkImageView.frame.size.width * ratioMultiplier)
UIView.animate(withDuration: 3.0) {
// Let your constraint changes update right now.
self.layoutIfNeeded()
}
}