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.
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:
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:
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
...
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
}
}