When animating a change in width or height constraint .constant
on a UILabel
, the animation only works if the constant value is larger. If the value is smaller, the label "snaps" to its new size instead of animating.
Quick, clear example... two views (green) and two labels (cyan). Tapping anywhere will toggle the respective height / width constraint constants between 300 and 100.
The green views animate smoothly whether "shrinking" or "growing."
The cyan labels only animate when "growing" ... when "shrinking" they "snap" to the new constant value:
import UIKit
class ViewController: UIViewController {
let testHeightLabel: UILabel = {
let v = UILabel()
v.text = "Height"
v.backgroundColor = .cyan
return v
}()
let testHeightView: UIView = {
let v = UIView()
v.backgroundColor = .green
return v
}()
let testWidthLabel: UILabel = {
let v = UILabel()
v.text = "Width"
v.backgroundColor = .cyan
return v
}()
let testWidthView: UIView = {
let v = UIView()
v.backgroundColor = .green
return v
}()
var testViewHeightConstraint: NSLayoutConstraint!
var testLabelHeightConstraint: NSLayoutConstraint!
var testViewWidthConstraint: NSLayoutConstraint!
var testLabelWidthConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
[testHeightView, testHeightLabel, testWidthView, testWidthLabel].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
testViewHeightConstraint = testHeightView.heightAnchor.constraint(equalToConstant: 300.0)
testLabelHeightConstraint = testHeightLabel.heightAnchor.constraint(equalToConstant: 300.0)
testViewWidthConstraint = testWidthView.widthAnchor.constraint(equalToConstant: 300.0)
testLabelWidthConstraint = testWidthLabel.widthAnchor.constraint(equalToConstant: 300.0)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain testHeightView Top / Leading / Width
testHeightView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
testHeightView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 80.0),
testHeightView.widthAnchor.constraint(equalToConstant: 60.0),
// constrain testHeightLabel Top / Leading / Width
testHeightLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
testHeightLabel.leadingAnchor.constraint(equalTo: testHeightView.trailingAnchor, constant: 40.0),
testHeightLabel.widthAnchor.constraint(equalToConstant: 60.0),
// constrain testWidthView Top / Leading / Height
testWidthView.topAnchor.constraint(equalTo: g.topAnchor, constant: 360.0),
testWidthView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
testWidthView.heightAnchor.constraint(equalToConstant: 40.0),
// constrain testWidthLabel Top / Leading / Height
testWidthLabel.topAnchor.constraint(equalTo: testWidthView.bottomAnchor, constant: 20.0),
testWidthLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
testWidthLabel.heightAnchor.constraint(equalToConstant: 40.0),
// activate height and width constraints
testViewHeightConstraint, testLabelHeightConstraint,
testViewWidthConstraint, testLabelWidthConstraint,
])
let instructionLabel = UILabel()
instructionLabel.text = "Tap to toggle height and width constraint constants.\nGreen Views smoothly animate to the new height and width.\nCyan Labels only smoothly animate when \"growing\"."
instructionLabel.numberOfLines = 0
instructionLabel.textAlignment = .center
instructionLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(instructionLabel)
NSLayoutConstraint.activate([
instructionLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
instructionLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
instructionLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
])
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// toggle height constraint constants between 300 and 100
testViewHeightConstraint.constant = testViewHeightConstraint.constant == 300.0 ? 100.0 : 300.0
testLabelHeightConstraint.constant = testLabelHeightConstraint.constant == 300.0 ? 100.0 : 300.0
// toggle width constraint constants between 300 and 100
testViewWidthConstraint.constant = testViewWidthConstraint.constant == 300.0 ? 100.0 : 300.0
testLabelWidthConstraint.constant = testLabelWidthConstraint.constant == 300.0 ? 100.0 : 300.0
// animate the constraint change
UIView.animate(withDuration: 1.5, animations: { self.view.layoutIfNeeded() })
}
}
It's possible I'm missing something obvious?
Note that the "snap" does not occur if the label is embedded in a container and the constraint on the container is animated - but that would be an undesirable workaround.
So, just looking for confirmation of this being a "bug."