Home > Software engineering >  Swift - CGAffineTransform rotation
Swift - CGAffineTransform rotation

Time:03-12

I have an UIImageView to rotate in a draw function.

public var progress: CGFloat = 0 {
    didSet {
        setNeedsDisplay()
    }
}

override public func draw(_ rect: CGRect) {
    rotateIcon(by: progress)
}

private func rotateIcon(by angle: CGFloat) {
    if imageView == nil {
        imageView = UIImageView(image: UIImage(named: "bt_info"))
        imageView?.center = center
        imageView?.frame.size = CGSize(width: 24.0, height: 24.0)
        addSubview(imageView!)
    }
    imageView?.transform = CGAffineTransform(rotationAngle: angle)
}

Below is the output attached for the above snippet.

enter image description here

When I set the frame size, the imageView shrink and expands as it rotates. I would like to maintain the size as the imageView rotates. What am I missing?

CodePudding user response:

OK - the code you posted in your question does not match the code in your Dropbox project...

In your project, you're doing this:

private func rotateScannerIcon(by angle: CGFloat) {
    if testView == nil {
        testView = UIView()
        testView?.backgroundColor = .red
        addSubview(testView!)
    }
    
    let centerPoint = CGPoint(x: bounds.midX, y: bounds.midY)
    testView?.center = centerPoint
    testView?.frame.size = CGSize(width: 24.0, height: 24.0)

    testView?.transform = CGAffineTransform(rotationAngle: angle)
}

With that code, you are changing the frame size after a rotation transform has been applied... and the actual view is no longer "square."

If you move the position / size code inside your "create" block:

private func rotateScannerIcon(by angle: CGFloat) {
    if testView == nil {
        testView = UIView()
        testView?.backgroundColor = .red
        addSubview(testView!)

        let centerPoint = CGPoint(x: bounds.midX, y: bounds.midY)
        testView?.center = centerPoint
        testView?.frame.size = CGSize(width: 24.0, height: 24.0)
    }
    
    testView?.transform = CGAffineTransform(rotationAngle: angle)
}

The "squeezing" of the frame no longer happens.

Overriding draw() is probably not the best approach, though. That is primarily done when UIKit view updates are not sufficient to handle the layout - such as when using stroke/fill on paths.

So, I would suggest a couple changes.

Create your subview (UIView or UIImageView or whatever) when the custom view subclass is initialized, and size/position it with auto-layout constraints.

Then, when you update the progress property, apply the rotation transform.

As an example, here's a modified version of your TestView class:

public class TestView: UIView {
    
    public var progress: CGFloat = 0 {
        didSet {
            rotateScannerIcon(by: (progress * CGFloat(Double.pi)))
        }
    }
    
    internal var testView: UIView!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        // create the view (or image view)
        testView = UIView()
        testView.backgroundColor = .red
        
        // add it to self
        addSubview(testView)
        
        // let's use auto-layout constraints
        testView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            // constrain the view to 24 x 24, centered in self
            testView.widthAnchor.constraint(equalToConstant: 24.0),
            testView.heightAnchor.constraint(equalTo: testView.widthAnchor),
            testView.centerXAnchor.constraint(equalTo: centerXAnchor),
            testView.centerYAnchor.constraint(equalTo: centerYAnchor),
        ])
        
    }

    private func rotateScannerIcon(by angle: CGFloat) {
        testView.transform = CGAffineTransform(rotationAngle: angle)
    }

}
  • Related