Dileepa Chandrasekara
Dileepa Chandrasekara

Reputation: 331

Scale down UIView and it's subview simultaneously in swift 5?

I have created UIView and it has one subview and it's containing shape object which are creating using UIBezierPath, and UIView has fixed height and width (image 1). When i click the blue color button, it should be scale down to another fixed width and height. I have applied CGAffineTransform to subview and, i guess it is scale down properly, but since topAnchor and leftAchor has constant values, after scale down it is not displaying properly (image 2).I will attach my source code here and screenshots, please, analyze this and can someone suggest better approach for how to do this ? and highly appreciate ur feedback and comments.

code:

import UIKit

class ViewController: UIViewController {
    
    var screenView: UIView!
    var button: UIButton!
    
    let widthOfScaleDownView: CGFloat = 200
    let heightOfScaleDownView: CGFloat = 300
    
    override func viewDidLoad() {
        
        screenView = UIView(frame: .zero)
        screenView.translatesAutoresizingMaskIntoConstraints = false
        screenView.backgroundColor = UIColor.red
        self.view.addSubview(screenView)
        NSLayoutConstraint.activate([
            screenView.heightAnchor.constraint(equalToConstant: 800),
            screenView.widthAnchor.constraint(equalToConstant: 600),
            screenView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: 0),
            screenView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: 0)
        ])
        button = UIButton()
        button.translatesAutoresizingMaskIntoConstraints = false
        button.backgroundColor = UIColor.blue
        self.view.addSubview(button)
        NSLayoutConstraint.activate([
            button.heightAnchor.constraint(equalToConstant: 50),
            button.widthAnchor.constraint(equalToConstant: 100),
            button.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 950),
            button.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 350)
        ])
        button.setTitle("click", for: .normal)
        button.setTitleColor(UIColor.white, for: .normal)
        button.addTarget(self, action: #selector(scaleDownView(_:)), for: .touchUpInside)
        
        let shapeView = UIView(frame: .zero)
        shapeView.translatesAutoresizingMaskIntoConstraints = false
        let rect = CGRect(x: 0, y: 0, width: 300, height: 300)
        let shape = UIBezierPath(roundedRect: rect, cornerRadius: 50)
        let layer = CAShapeLayer()
        layer.path = shape.cgPath
        layer.lineWidth = 2
        shapeView.layer.addSublayer(layer)
        
        screenView.addSubview(shapeView)
        NSLayoutConstraint.activate([
            shapeView.topAnchor.constraint(equalTo: screenView.topAnchor, constant: 250),
            shapeView.leftAnchor.constraint(equalTo: screenView.leftAnchor, constant: 150)
        ])
    }
    
    @IBAction func scaleDownView(_ sender: UIButton) {
        let widthScale = widthOfScaleDownView/screenView.frame.width
        let heightScale = heightOfScaleDownView/screenView.frame.height
        screenView.subviews.forEach{ view in
            view.transform = CGAffineTransform(scaleX: widthScale, y: heightScale)
        }
        
        NSLayoutConstraint.activate([
            screenView.heightAnchor.constraint(equalToConstant: heightOfScaleDownView),
            screenView.widthAnchor.constraint(equalToConstant: widthOfScaleDownView),
            screenView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: 0),
            screenView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: 0)
        ])
        
    }
    
    
}

before to scale

after scale

Upvotes: 0

Views: 1179

Answers (1)

DonMag
DonMag

Reputation: 77482

Couple things...

1 - When you use CGAffineTransform to change a view, it will automatically affect the view's subviews. So no need for a loop.

2 - Transforms are cumulative, so when "going back" to the original scale, use .identity instead of another scale transform.

3 - You cannot set a constraint multiple times. So if you do:

screenView.heightAnchor.constraint(equalToConstant: 800)

and then you also do:

screenView.heightAnchor.constraint(equalToConstant: heightOfScaleDownView)

you will get auto-layout conflicts... the view cannot be both 800-pts tall and 300-pts tall at the same time.

Take a look at the changes I made to your code. I also added a 1-second animation to the scale transform so you can see what it's doing:

class ViewController: UIViewController {
    
    var screenView: UIView!
    var button: UIButton!
    
    let widthOfScaleDownView: CGFloat = 200
    let heightOfScaleDownView: CGFloat = 300
    
    override func viewDidLoad() {
        
        screenView = UIView(frame: .zero)
        screenView.translatesAutoresizingMaskIntoConstraints = false
        screenView.backgroundColor = UIColor.red
        self.view.addSubview(screenView)
        
        // constrain screenView
        //  width: 800 height: 600
        //  centered X and Y
        NSLayoutConstraint.activate([
            screenView.heightAnchor.constraint(equalToConstant: 800),
            screenView.widthAnchor.constraint(equalToConstant: 600),
            screenView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: 0),
            screenView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: 0)
        ])

        button = UIButton()
        button.translatesAutoresizingMaskIntoConstraints = false
        button.backgroundColor = UIColor.blue
        self.view.addSubview(button)
        NSLayoutConstraint.activate([
            button.heightAnchor.constraint(equalToConstant: 50),
            button.widthAnchor.constraint(equalToConstant: 100),
            button.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 950),
            button.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 350)
        ])
        button.setTitle("click", for: .normal)
        button.setTitleColor(UIColor.white, for: .normal)
        button.addTarget(self, action: #selector(scaleDownView(_:)), for: .touchUpInside)
        
        let shapeView = UIView(frame: .zero)
        shapeView.translatesAutoresizingMaskIntoConstraints = false
        let rect = CGRect(x: 0, y: 0, width: 300, height: 300)
        let shape = UIBezierPath(roundedRect: rect, cornerRadius: 50)
        let layer = CAShapeLayer()
        layer.path = shape.cgPath
        layer.lineWidth = 2
        shapeView.layer.addSublayer(layer)
        
        screenView.addSubview(shapeView)
        
        // constrain shapeView
        //  width: 300 height: 300
        //  centered X and Y in screenView
        NSLayoutConstraint.activate([
            shapeView.widthAnchor.constraint(equalToConstant: 300.0),
            shapeView.heightAnchor.constraint(equalToConstant: 300.0),
            shapeView.centerXAnchor.constraint(equalTo: screenView.centerXAnchor),
            shapeView.centerYAnchor.constraint(equalTo: screenView.centerYAnchor),
        ])
    }
    
    @IBAction func scaleDownView(_ sender: UIButton) {
        let widthScale = widthOfScaleDownView/screenView.frame.width
        let heightScale = heightOfScaleDownView/screenView.frame.height

        UIView.animate(withDuration: 1.0) {
            // if we have already been scaled down
            if widthScale == 1 {
                // animate the scale transform back to original (don't add another transform)
                self.screenView.transform = .identity
            } else {
                // animate the scale-down transform
                self.screenView.transform = CGAffineTransform(scaleX: widthScale, y: heightScale)
            }
        }

        // do NOT change screenView constraints!
    }
    
}

Upvotes: 1

Related Questions