Good question. As you quoted, the constraints are applicable to the original untransformed bounds of the view.
To be able to make your other view respect the transformed bounds, one way to do is to calculate constraint constant's offset after the transform is applied. Below is an example of a similar situation to yours, but with a top constraint, instead of a leading/trailing/align. But basically you can apply the same procedure for other cases also.
This works for now, feel free to improve on this answer.
Storyboard Setup:

Output after applying transform of (scaleX:3, scaleY:2.5).

What we do in code below is:
- Make
IBOutlet of the constraint(s).
- Store the constraint's original constant to a variable.
- Store the frame of view to be scaled before applying it.
- Apply transform.
- Store the frame of view after transform.
NOTE: According to the Apple documentation, it says:
Warning
When the value of this property is anything other than the
identity transform, the value in the frame property is undefined and
should be ignored.
But the transformed frame seems to be available right after applying the scale. So we save it to a variable for future use. If you try to access the frame else where, you will not get the correct frame. So we do this step right after applying the transform.
- By overriding
updateViewConstraints method of your viewcontroller, you have the chance to update your constraints. Now we simply take the difference of the two frames' bottom positions and apply it to the constraint constant of the IBOutlet.
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var view1: UIView! //Dark gray view
@IBOutlet weak var view2: UIView! //Light gray view
@IBOutlet weak var topConstraint: NSLayoutConstraint!
let transformScaleX:CGFloat = 3
let transformScaleY:CGFloat = 2.5
var rectBeforeTransform:CGRect = .zero
var rectAfterTransform:CGRect = .zero
var originalTopConstraintConstant:CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
//Store original constraint value
self.originalTopConstraintConstant = self.topConstraint.constant
//Store unaltered frame of the view
self.rectBeforeTransform = view1.frame
//Apply transform
self.view1.transform = .init(scaleX: transformScaleX, y: transformScaleY)
//Frame after transform.
//NOTE: When a non-identity transform is applied to a view, its frame is undefined, so we would not get proper frame info. of the view anywhere other than after applying the tranform.
self.rectAfterTransform = view1.frame
}
override func updateViewConstraints() {
DispatchQueue.main.async {
let constraintOffset = abs(self.rectAfterTransform.bottomY - self.rectBeforeTransform.bottomY)
self.topConstraint.constant = self.originalTopConstraintConstant + constraintOffset
}
super.updateViewConstraints()
}
}
extension CGRect {
var bottomY:CGFloat {
get {
return self.height + self.origin.y
}
}
}
PS: The current answer works for top constraints only, I will try to update the answer for all the leading/trailing/top/bottom constraints, and possibly a more elegant solution. As for the animation, unfortunately I’m not able to provide a way through. Will update if I find some. Thanks.