在 swift 5 中同时缩小 UIView 及其子视图?

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

我创建了 UIView,它有一个子视图,它包含使用 UIBezierPath 创建的形状对象,并且 UIView 具有固定的高度和宽度(图片 1)。当我单击蓝色按钮时,它应该按比例缩小到另一个固定的宽度和高度。我已经将 CGAffineTransform 应用到子视图,我猜它是正确缩小的,但是由于 topAnchorleftAchor 具有恒定值,缩小后它无法正确显示(image 2)。我会在这里附上我的源代码和屏幕截图,请分析一下,有人可以建议更好的方法吗?非常感谢您的反馈和评论。

代码:

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)
        ])
        
    }
    
    
}

两件事...

1 - 当您使用 CGAffineTransform 更改视图时,它会自动影响视图的子视图。所以不需要循环。

2 - 变换是累积的,因此当“回到”原始比例时,使用 .identity 而不是另一个比例变换。

3 - 不能多次设置约束。所以如果你这样做:

screenView.heightAnchor.constraint(equalToConstant: 800)

然后你做:

screenView.heightAnchor.constraint(equalToConstant: heightOfScaleDownView)

你会遇到自动布局冲突...视图不能同时为 800 点高 同时身高 300 磅。

看看我对您的代码所做的更改。我还在缩放变换中添加了一个 1 秒的动画,这样您就可以看到它在做什么:

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!
    }
    
}