0
votes

I have a custom view , CustomLayout (blue, custom UIView), and this view contains 3 subviews vertical aligned by using constraints (Layout Anchors), each view are aligned following this order:

  • 1 view: SlideLayout (red, custom UIView)
  • 2 view: UIButton (yellow)
  • 3 view: UIView (dry gray)

I want when I clicked on the button (yellow), the height size of SlideLayout (red) increase if open or decrease if close by using animation. And other views must change position during animation and the parent view (CustomLayout) must increase/decrease his height size (animation) if SlideLayout increase/decrease.

What method is called when I use this method:

UIView.animate(withDuration, delay, options, animations, completion)

I override layoutIfNeeded() method by adding a simple print but it does not call every time during animation

I try this but It doesn’t work as I expect. How I can fix that thank you.

Bad animation in picture

Codes:

class CustomLayout: UIView {

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

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    private func setup() {
        onLayout()
    }

    public func onLayout() {
        print("\(frame.size.height)")
        let MARGIN: CGFloat = 10
        for i in 0 ..< subviews.count {
            let child = subviews[i]
                if i == 0 { // slide layout
                    child.translatesAutoresizingMaskIntoConstraints = false
                    child.topAnchor.constraint(equalTo: topAnchor, constant: MARGIN).isActive = true
                    child.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
                    child.widthAnchor.constraint(equalToConstant: frame.size.width - (MARGIN * 2)).isActive = true
                    let enchorHeight = child.heightAnchor.constraint(equalToConstant: child.frame.size.height);
                    enchorHeight.isActive = true
                    (subviews[0] as! SlideDownLayout).enchorHeight = enchorHeight
                }
                else if i == 1 { // button
                    child.translatesAutoresizingMaskIntoConstraints = false
                    child.topAnchor.constraint(equalTo: subviews[0].bottomAnchor, constant: MARGIN).isActive = true
                    child.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
                    child.widthAnchor.constraint(equalToConstant: child.frame.size.width).isActive = true
                    child.heightAnchor.constraint(equalToConstant: child.frame.size.height).isActive = true

                }
                else if i == 2 { // uiview  
                    child.translatesAutoresizingMaskIntoConstraints = false    
                    child.topAnchor.constraint(equalTo: subviews[1].bottomAnchor, constant: MARGIN).isActive = true                
                    child.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true                
                    child.widthAnchor.constraint(equalToConstant: frame.size.width - (MARGIN * 4)).isActive = true 
                    child.heightAnchor.constraint(equalToConstant: 300).isActive = true               
                    bottomAnchor.constraint(equalTo: child.bottomAnchor, constant: MARGIN).isActive = true

                }        
        }    
    }
}

class SlideDownLayout: UIView {

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

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    private var HEIGHT: CGFloat = 0
    private var isClosed: Bool = true

    private func setup() {
        HEIGHT = frame.height
        frame.size.height = 0
    }

    public func slideAnimation(view: UIView) {
        print("\(HEIGHT)")
        isClosed = !isClosed
        self.enchorHeight!.constant = self.isClosed ? 0 : self.HEIGHT
         UIView.animate(withDuration: 1, delay: 0, options: .curveEaseInOut, animations: {
            self.superview?.layoutIfNeeded()
            view.layoutIfNeeded()
         }, completion: nil)
    }

    override func layoutIfNeeded() {
        print("...")
        super.layoutIfNeeded()
    }

    var enchorHeight: NSLayoutConstraint? = nil
}

class ViewController: UIViewController {

    @IBOutlet weak var customLayout: CustomLayout!
    @IBOutlet weak var slideDownLayout: SlideDownLayout!

    override func viewDidLoad() {
        super.viewDidLoad()

        customLayout.translatesAutoresizingMaskIntoConstraints = false
        customLayout.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        customLayout.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
        customLayout.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
        customLayout.topAnchor.constraint(equalTo: view.topAnchor).isActive = true

    }

    override var prefersStatusBarHidden: Bool {
        return true
    }

    @IBAction
    func buttonListener(_ sender: Any) {
        slideDownLayout.slideAnimation(view: self.view)
    }
}
1
Try this using stackViewJogendar Choudhary

1 Answers

1
votes

When you want to make a dynamic View don't change frame but change constraint's constant as frame doesn't push the parent down , so make the height constraint of the view you want to animate as IBOutlet and control it's constant value

You can try

class CustomLayout: UIView {

   var heightCon:NSLayoutConstraint!

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

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }

    private func setup() {
        onLayout()
    }

    public func onLayout() {
        print("\(frame.size.height)")
        let MARGIN: CGFloat = 10
        for i in 0 ..< subviews.count {
            let child = subviews[i]
                if i == 0 { // slide layout
                    child.translatesAutoresizingMaskIntoConstraints = false
                    child.topAnchor.constraint(equalTo: topAnchor, constant: MARGIN).isActive = true
                    child.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
                    child.widthAnchor.constraint(equalToConstant: frame.size.width - (MARGIN * 2)).isActive = true
                   heightCon =  child.heightAnchor.constraint(equalToConstant: child.frame.size.height) 
                   heightCon.isActive = true 
                }

//

    public func slideAnimation() {
        print("\(HEIGHT)")

         isClosed = !isClosed

         let ss = self.superview as! CustomLayout

         ss.heightCon.constant = self.isClosed ? 0 : self.HEIGHT

         UIView.animate(withDuration: 1, delay: 0, options: .curveEaseInOut, animations: { 
            ss.layoutIfNeeded()
         }, completion: nil)
    }