Search code examples
swiftuiviewuikittransformscale

Scaling by transform does not work in UIView


In the following program, no matter what value I put in the argument of CGAffineTransform(scaleX:y:), I get the same drawing result.

Why is that?

import UIKit

class ViewController: UIViewController {
  
  override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
    self.view.backgroundColor = .gray
  }

  override func viewWillAppear(_ animated: Bool) {
    let scalingView = ScalingView(frame: self.view.bounds)
    self.view.addSubview(scalingView)
  }
}
import UIKit

class ScalingView: UIView {
  let subview: UIView!
  
  override init(frame: CGRect) {
    subview = UIView()
    super.init(frame: frame)
    addSubview(subview)
  }
  
  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
  
  override func layoutSubviews() {
    subview.backgroundColor = .white
    let subviewSize = 100
    subview.frame.size = CGSize(width: subviewSize, height: subviewSize)
    subview.center = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
    subview.transform = CGAffineTransform(scaleX: 1, y: 1)
  }
}

subview.transform = CGAffineTransform(scaleX: 1, y: 1)

scaleX: 1, y: 1


subview.transform = CGAffineTransform(scaleX: 2, y: 2)

scaleX: 2, y: 2


subview.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)

scaleX: 0.5, y: 0.5


https://github.com/ll3Ynxnj/Sample-Swift-ViewScaling.git


Solution

  • The issue is that every time you execute subview.transform, the layoutSubviews function also gets called, and the subview frame changes too. Without animation or delay, it actually does transform then back to the original frame. You can put some animate to check:

    UIView.animate(withDuration: 1) {
        self.subview.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
    }
    

    To avoid it, you have to move the setup size and frame to init so it will get called once. Then transform from outside, maybe from ViewController:

    class ScalingView: UIView {
        override init(frame: CGRect) {
            ...
            let subviewSize = 100
            subview.frame.size = CGSize(width: subviewSize, height: subviewSize)
            subview.center = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
        }
    
        func scaleSubView(_ scale: CGFloat = 1.5) {
            UIView.animate(withDuration: 0.3) {
                self.subview.transform = CGAffineTransform(scaleX: scale, y: scale)
            }
        }
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
    
        scalingView.scaleSubView()
    }
    

    Notice: @matt and @HangarRash advice is helpful, you should follow it.