Search code examples
iosuiviewcgaffinetransform

Rotating view using constraints - Swift


I'm trying to rotate a uiview on top of another view using constraints. Here is what I implemented

mediaContainer.addSubview(overlayView)
keepBottomLeft()
overlayView.layoutIfNeeded()

func keepBottomLeft(overlayView: UIView) {
    NSLayoutConstraint(item: overlayView, attribute:.width, relatedBy:.equal,toItem: mediaContainer, attribute:.width, multiplier: 0.8, constant: 0).isActive = true
    NSLayoutConstraint(item: overlayView, attribute:.leading, relatedBy:.equal, toItem: mediaContainer, attribute:.leading, multiplier: 1, constant: 0).isActive = true
    NSLayoutConstraint(item: overlayView, attribute:.height, relatedBy:.equal,toItem: nil, attribute:.notAnAttribute, multiplier: 1.0, constant: 26).isActive = true
    NSLayoutConstraint(item: overlayView, attribute:.bottom, relatedBy:.equal, toItem: mediaContainer, attribute:.bottom, multiplier: 1, constant: 0).isActive = true
    overlayView.transform = CGAffineTransform(rotationAngle: CGFloat(-M_PI_2))
}

Will it be necessary to use an anchor point? This is what I'm trying to achieve?

From View A

To View B

What I get is

Can someone point me in the right direction?


Solution

  • Your anchor point is at the yellow view's center; the default. Either you want to move it to the bottom left with layer.anchorPoint = CGPoint(x: 0, y:1) [Note the anchor is in unit coordinates form 0 to 1, not in points]. You will still need an extra translate of x = height.

    Alternatively you can concatenate your transforms and move your center to the point you want to rotate about, perform the rotation, and then reverse your translation (this is actually the typical way to do it with 3d transforms).

    UPDATE: Here is a playground. Note that you concatenate the matrices in the reverse order that you want the transforms to be applied. I have an animation in here so you can decompose the transforms and see what each one does:

    1. Move the view to the origin (0,0) (this is the last transform)
    2. Rotate the view 90 degrees clockwise
    3. Move the view over by half of its current width and down so its at the bottom of the superview.

      import PlaygroundSupport
      import UIKit
      class V: UIViewController {
         var overlayView = UIView()
          override func viewDidLoad() {
              super.viewDidLoad()
              overlayView.translatesAutoresizingMaskIntoConstraints = false
              view.addSubview(overlayView)
              overlayView.backgroundColor = .yellow
              overlayView.heightAnchor.constraint(equalToConstant: 26).isActive = true
              overlayView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8).isActive = true
              overlayView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
              overlayView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
          }
          override func viewDidAppear(_ animated: Bool) {
              UIView.animate(withDuration: 1) {
                  let center = self.overlayView.center
                  let size = self.overlayView.bounds.size
                  self.overlayView.transform =
                      CGAffineTransform(translationX: self.view.bounds.size.height - size.width / 2, y: -size.height/2)
                      .concatenating(CGAffineTransform(rotationAngle: CGFloat.pi/2)
                      .concatenating(CGAffineTransform(translationX: -center.x, y: -center.y)))
              }
          }
      }
      
      let v = V()
      v.view.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
      PlaygroundPage.current.liveView = v