How to use AutoLayout with transform

Viewed 1660

The apple document says:

In iOS 8.0 and later, the transform property does not affect Auto Layout. Auto layout calculates a view’s alignment rectangle based on its untransformed frame.

So if i got a view called View1 which is scaled by using transform property, and View2 wants to align to edge of View1. How can i do this using AutoLayout?

1 Answers

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:

Storyboard Setup

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


What we do in code below is:

  1. Make IBOutlet of the constraint(s).
  2. Store the constraint's original constant to a variable.
  3. Store the frame of view to be scaled before applying it.
  4. Apply transform.
  5. 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.

  1. 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.


Related