Home > Software engineering >  How to animate a view to hide behind Navigation Bar
How to animate a view to hide behind Navigation Bar

Time:04-27

I'm trying to animate a view to hide behind the navigation bar. The idea is the yellow label to appear from behind the green view.

enter image description here

I tried this modifying the top constraint to a negative number, but it works but if the yellow view is bigger than the green one it ends over the safe area.

My code:

@IBAction func buttonClick(_ sender: Any) {
    UIView.animate(withDuration: 0.5) {
        if(self.topMargin.constant<0){
            self.topMargin.constant=0
        }else {
            self.topMargin.constant = -100
        }
        self.view.layoutIfNeeded()
    }
}

Thats the result when hidden:

enter image description here

How can I achieve this effect without invading the safe zone?

CodePudding user response:

setup your label under your Controller class:

let myLabel: UILabel = {
    
    let label = UILabel()
    label.text = "Label"
    label.backgroundColor = .darkYellow
    label.textAlignment = .center
    label.textColor = .black
    label.font = .systemFont(ofSize: 16, weight: .semibold)
    label.translatesAutoresizingMaskIntoConstraints = false
    
    return label
}()

After that set two variables for label animation state:

var labelUp: NSLayoutConstraint!
var labeldown: NSLayoutConstraint!

In viewDidLoad setup your nav bar (I set it with my extension), present your label and constraints:

navigationItem.leftBarButtonItem = UIBarButtonItem(title: "label UP", style: .plain, target: self, action: #selector(self.handleAnimate))
    
    view.addSubview(myLabel)
    labeldown = myLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
    labeldown.isActive = true
    
    labelUp = myLabel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
    
    myLabel.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
    myLabel.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
    myLabel.heightAnchor.constraint(equalToConstant: 100).isActive = true

Now add a variable to control state of label and animation func:

var controlStatusLabel = true

@objc fileprivate func handleAnimate() {
    
    switch controlStatusLabel {
    case true:
        UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: {
            self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "label Down", style: .plain, target: self, action: #selector(self.handleAnimate))
            self.labeldown.isActive = false
            self.labelUp.isActive = true
            self.view.layoutIfNeeded()
            self.controlStatusLabel = false
        }, completion: nil)
    default:
        UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: {
            self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "label UP", style: .plain, target: self, action: #selector(self.handleAnimate))
            self.labelUp.isActive = false
            self.labeldown.isActive = true
            self.view.layoutIfNeeded()
            self.controlStatusLabel = true
        }, completion: nil)
    }
}

The result:

enter image description here

CodePudding user response:

Embed your label (or whatever view you want to animate) in a "holder" view, constrained to the safe-area, with .clipsToBounds = true...

enter image description here

The holder view background will normally be clear -- I'm toggling it between clear and red so you can see it's frame.

Here'a quick example code for that:

class ViewController: UIViewController {

    let label = UILabel()
    let labelHolderView = UIView()
    
    var labelTop: NSLayoutConstraint!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.title = "Nav Bar"

        // configure the label
        label.textAlignment = .center
        label.text = "I'm going to slide up."
        label.backgroundColor = .systemYellow
        
        // add it to the holder view
        labelHolderView.addSubview(label)
        
        // prevents label from showing outside the bounds
        labelHolderView.clipsToBounds = true

        label.translatesAutoresizingMaskIntoConstraints = false
        labelHolderView.translatesAutoresizingMaskIntoConstraints = false

        // add holder view to self.view
        view.addSubview(labelHolderView)

        // constant height for the label
        let labelHeight: CGFloat = 160.0
        
        // setup label top constraint
        labelTop = label.topAnchor.constraint(equalTo: labelHolderView.topAnchor)
        
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([

            // activate label top constraint
            labelTop,
            
            // constrain label Leading/Trailing to holder
            //  we'll inset it by 20-points so we can see the holder view
            label.leadingAnchor.constraint(equalTo: labelHolderView.leadingAnchor, constant: 20.0),
            label.trailingAnchor.constraint(equalTo: labelHolderView.trailingAnchor, constant: -20.0),
            
            // constant height
            label.heightAnchor.constraint(equalToConstant: labelHeight),
            
            // label gets NO Bottom constraint
            
            // constrain holder to safe area
            labelHolderView.topAnchor.constraint(equalTo: g.topAnchor),
            labelHolderView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
            labelHolderView.trailingAnchor.constraint(equalTo: g.trailingAnchor),

            // constant height (same as label height)
            labelHolderView.heightAnchor.constraint(equalTo: label.heightAnchor),
            
        ])
        
        // let's add a button to animate the label
        //  and one to show/hide the holder view
        let btn1 = UIButton(type: .system)
        btn1.setTitle("Animate It", for: [])
        btn1.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(btn1)
        
        let btn2 = UIButton(type: .system)
        btn2.setTitle("Toggle Holder View Color", for: [])
        btn2.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(btn2)

        NSLayoutConstraint.activate([
            // put the first button below the holder view
            btn1.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            btn1.topAnchor.constraint(equalTo: labelHolderView.bottomAnchor, constant: 20.0),
            // second button below it
            btn2.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            btn2.topAnchor.constraint(equalTo: btn1.bottomAnchor, constant: 20.0),

        ])
        

        // give the buttons an action
        btn1.addTarget(self, action: #selector(animLabel(_:)), for: .touchUpInside)
        btn2.addTarget(self, action: #selector(toggleHolderColor(_:)), for: .touchUpInside)

    }
    
    @objc func animLabel(_ sender: Any?) {
        // animate the label up if it's down, down if it's up
        labelTop.constant = labelTop.constant == 0 ? -label.frame.height : 0
        UIView.animate(withDuration: 0.5, animations: {
            self.view.layoutIfNeeded()
        })
    }
    
    @objc func toggleHolderColor(_ sender: Any?) {
        labelHolderView.backgroundColor = labelHolderView.backgroundColor == .red ? .clear : .red
    }
    
}
  • Related