动画约束改变奇怪的结果

Animating constraints changes weird result

我有这个class:

   class Cell {
        
        /// The view that contains everything.
        ///
        /// Use this view to present this `Cell`.
        var view: UIView
        
        /// The view that contains the content of the cell without `openedView`.
        var cellView: UIView
        
        var openedView: UIView
        
        var topOpenedView: UIView
        
        var button: UIButton! = nil
        
        private var isOpenedProtected: Bool = false
        
        /// The visibility of ```openedView```.
        ///
        /// Changing this variable is the same as ```changeOpened(to:animated:)``` with animation.
        var isOpened: Bool {
            get {
                return isOpenedProtected
            }
            set {
                changeOpened(to: newValue, animated: true)
            }
        }
        
        private var openedViewHeightConstraint: NSLayoutConstraint! = nil
        private var openedViewBottomConstraint: NSLayoutConstraint! = nil
        
        lazy private var buttonHandler: UIActionHandler = { _ in
            self.isOpened.toggle()
        }
        
        init(tint color: UIColor, openedView givenOpenedView: UIView) {
            
            self.openedView = givenOpenedView
            openedView.translatesAutoresizingMaskIntoConstraints = false
            
            // VIEW
            self.view = UIView(frame: .zero)
            view.translatesAutoresizingMaskIntoConstraints = false
            view.clipsToBounds = true
            
            self.topOpenedView = UIView(frame: .zero)
            topOpenedView.clipsToBounds = true
            topOpenedView.translatesAutoresizingMaskIntoConstraints = false
            topOpenedView.addSubview(openedView)
            openedView.clipsToBounds = true

            topOpenedView.topAnchor.constraint(equalTo: openedView.topAnchor).isActive = true
            topOpenedView.leadingAnchor.constraint(equalTo: openedView.leadingAnchor).isActive = true
            topOpenedView.trailingAnchor.constraint(equalTo: openedView.trailingAnchor).isActive = true
            self.openedViewBottomConstraint = topOpenedView.bottomAnchor.constraint(equalTo: openedView.bottomAnchor)
            openedViewBottomConstraint.isActive = false
            self.openedViewHeightConstraint = topOpenedView.heightAnchor.constraint(equalToConstant: 0)
            openedViewHeightConstraint.isActive = true
            
            for constraint in openedView.constraints {
                constraint.priority -= 1
            }
            // ---
            
            self.cellView = UIView(frame: .zero)
            cellView.translatesAutoresizingMaskIntoConstraints = false
            cellView.clipsToBounds = true
            
            // Button
            self.button = UIButton(frame: .zero, primaryAction: UIAction(handler: buttonHandler))
            button.translatesAutoresizingMaskIntoConstraints = false
            
            // Add subviews
            cellView.addSubview(button)
            cellView.heightAnchor.constraint(equalToConstant: 45).isActive = true
            
            cellView.leadingAnchor.constraint(equalTo: button.leadingAnchor).isActive = true
            cellView.trailingAnchor.constraint(equalTo: button.trailingAnchor).isActive = true
            cellView.topAnchor.constraint(equalTo: button.topAnchor).isActive = true
            cellView.bottomAnchor.constraint(equalTo: button.bottomAnchor).isActive = true
            
            cellView.backgroundColor = color
            
            cellView.heightAnchor.constraint(greaterThanOrEqualToConstant: 40).isActive = true
            
            // View
            view.layer.cornerRadius = 10
            view.backgroundColor = .clear
            
            view.addSubview(cellView)
            view.addSubview(topOpenedView)
            
            view.topAnchor.constraint(equalTo: cellView.topAnchor).isActive = true
            view.leadingAnchor.constraint(equalTo: cellView.leadingAnchor).isActive = true
            view.leadingAnchor.constraint(equalTo: topOpenedView.leadingAnchor).isActive = true
            view.trailingAnchor.constraint(equalTo: cellView.trailingAnchor).isActive = true
            view.trailingAnchor.constraint(equalTo: topOpenedView.trailingAnchor).isActive = true
            view.bottomAnchor.constraint(equalTo: topOpenedView.bottomAnchor).isActive = true
            topOpenedView.topAnchor.constraint(equalTo: cellView.bottomAnchor).isActive = true
            
            cellView.bringSubviewToFront(button)
        }
        
        func changeOpened(to open: Bool, animated: Bool) {
            if isOpenedProtected == open { return }
            isOpenedProtected = open
            
            let duration: Double = animated ? 0.5 : 0.0
            
            self.view.layoutIfNeeded()
            
            print(open ? "Opening" : "Closing")
            
            self.openedViewHeightConstraint.isActive = !open
            self.openedViewBottomConstraint.isActive = open
            
            UIView.animate(withDuration: duration/*, delay: 0, options: .transitionFlipFromTop*/) {
                self.view.layoutIfNeeded()
            }
        }
    }

这个 class 基本上创建了一个视图,当按下它时另一个视图在它下面打开

这是我调用 class 的代码:(在视图中加载):

        let myView = UIView(frame: .zero)
        myView.translatesAutoresizingMaskIntoConstraints = false
        myView.backgroundColor = .systemPink
        myView.heightAnchor.constraint(equalToConstant: 120).isActive = true
        
        let cell = Cell(tint: .blue, openedView: myView)
        
        view.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(cell.view)
        cell.view.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true
        cell.view.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        cell.view.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true

但是动画结果不是我所期望的:

如您所见,动画是跳跃的,因为它是从中间而不是顶部开始的。 我希望蓝色视图在粉红色视图打开和关闭时保持原位。

提前致谢

这似乎是一种创建自定义视图的相当复杂的方法,但是...

首先,在视图控制器的视图上执行 NOT 设置 .translatesAutoresizingMaskIntoConstraints = false。所以,在你的 viewDidLoad() func:

    let cell = Cell(tint: .blue, openedView: myView)

    // DO NOT DO THIS       
    //view.translatesAutoresizingMaskIntoConstraints = false

    view.addSubview(cell.view)

奇怪的动画是因为您为错误的视图设置了动画。

changeOpened(...) 函数的末尾更改为:

    guard let sv = self.view.superview else {
        return
    }

    UIView.animate(withDuration: duration/*, delay: 0, options: .transitionFlipFromTop*/) {
        //self.view.layoutIfNeeded()
        sv.layoutIfNeeded()
    }