无法正确地为父视图中的子视图的框架设置动画

Unable to properly animate the frame of a subview within a parent view

我正在尝试为视图中子视图的帧更改设置动画。在动画块中,我将子视图的大小设置为其原始宽度和高度的一半。但是,当我 运行 代码时,子视图意外地以更大的尺寸开始,并 sh运行k 到其原始尺寸而不是成为其原始尺寸的一半。我正在使用下面的代码:

我的自定义视图:

import UIKit

class TestView: UIView {

    var testSubview: UIView!

    override init(frame: CGRect) {
        super.init(frame: frame)

        backgroundColor = UIColor.yellow

        testSubview = UIView(frame: .zero)
        testSubview.backgroundColor = UIColor.red
        addSubview(testSubview)

    }

    required init?(coder aDecoder: NSCoder) {
        fatalError()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        testSubview.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
    }

    func testAnimate() {

        UIView.animate(withDuration: 3, delay: 0, options: [], animations: { [unowned self] in

            self.testSubview.bounds.size = CGSize(width: 50, height: 50)

            }, completion: nil)

    }

}

我的视图控制器:

import UIKit

class TestViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }


    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        let testView = TestView()
        view.addSubview(testView)

        testView.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
        testView.center = view.center

        testView.layoutIfNeeded()

        testView.testAnimate()
    }

}

我做错了什么吗?

问题是您的视图布局了两次,您可以通过在 layoutSubviews() 中打印调试消息来检查。

我建议这样,设置一个大小变量并在 layoutSubviews() 和 testAnimate() 中使用它:

class TestView: UIView {
    var testSubview: UIView!
    var mySize = CGSize(width: 100, height: 100)

    override init(frame: CGRect) {
        super.init(frame: frame)

        backgroundColor = UIColor.yellow

        testSubview = UIView(frame: .zero)
        testSubview.backgroundColor = UIColor.red
        addSubview(testSubview)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        testSubview.frame = CGRect(x: 0, y: 0, width: mySize.width, height: mySize.height)
    }

    func testAnimate() {
        UIView.animate(withDuration: 3, delay: 0, options: [], animations: { [unowned self] in
            self.mySize = CGSize(width: 50, height: 50)
            self.testSubview.bounds.size = self.mySize
            }, completion: nil)
    }
}

编辑:重新制定我的整个答案,因为我觉得这里有很多地方应该改变。

首先是设置视图边界和它的框架之间的区别。

let view = UIView()
view.frame = CGRect(x: 0, y: 0, width: 200, height: 200) // This will set the position and size this view will take relative to it's future parents view
view.bounds = CGRect(x: 50, y: 50, width: 100, height: 100) // This will set position and size of it's own content (i.e. it's background) relative to it's own view

然后是您的 testView class 变量。除非您确定变量不会为零,否则不要使用 UIView! 作为类型,否则您最终会丢失一些初始化程序,因此您的 class 将无法始终如一地工作。

将 init 方法视为变量的默认值,之后您可以简单地更改它们。

import UIKit

class TestView: UIView {

    var testSubview: UIView

    override init(frame: CGRect) {
        // Initialise your variables before calling super
        testSubview = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        testSubview.backgroundColor = UIColor.red

        super.init(frame: frame)
        // Once super is called you can start using class methods or variable such as addSubview or backgroundColor
        backgroundColor = UIColor.yellow
        addSubview(testSubview)
    }

    required init?(coder aDecoder: NSCoder) {

        testSubview = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        testSubview.backgroundColor = UIColor.blue

        super.init(coder: aDecoder)

        backgroundColor = UIColor.yellow
        addSubview(testSubview)
    }

}

现在终于到了动画部分

import UIKit

class TestView: UIView {

    var testSubview: UIView

    func testAnimation() {
        UIView.animate(withDuration: 3, animations: {
            self.testSubview.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) // Simply make the view half its size over 3 seconds
        }) { (_) in
            UIView.animate(withDuration: 3, animations: {
                self.testSubview.transform = CGAffineTransform(scaleX: 1, y: 1) // Upon completion, resize the view back to it's original size
            })
        }
    }
}

或者如果你真的想使用 CGRect 在它的位置上做动画

import UIKit

class TestView: UIView {

    var testSubview: UIView

    func testAnimation() {
        UIView.animate(withDuration: 3, animations: {
            self.testSubview.frame = CGRect(x: 0, y: 0, width: 50, height: 50) // use frame not bounds to resize and reposition the frame
        }) { (_) in
            UIView.animate(withDuration: 3, animations: {
                self.testSubview.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
            })
        }
    }
}

你可以这样测试改变bounds属性的效果

import UIKit

class ViewController: UIViewController {
    var testView: TestView!
    override func viewDidLoad() {
        testView = TestView(frame: CGRect(origin: .zero, size: CGSize(width: 200, height: 200)))
        testView.bounds = CGRect(x: 50, y: 50, width: 100, height: 100)
        testView.clipsToBounds = true
        view.addSubview(testView)
        super.viewDidLoad()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        testView.testAnimation()
    }


}

只需玩转 clipsToBoundstestView.boundstestView.subView.frametestView = TestView(frame: CGRect(x:y:width:height) 即可查看所有这些