skip to Main Content

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?

2

Answers


  1. 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

    Login or Signup to reply.
  2. 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
        }
        
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search