I have followed this answer to instantiate a view from a XIB file:
extension UIView {
class func fromNib(named: String? = nil) -> Self {
let name = named ?? "\(Self.self)"
guard let nib = Bundle.main.loadNibNamed(name, owner: nil, options: nil) else {
fatalError("missing expected nib named: \(name)")
}
guard let view = nib.first as? Self else {
fatalError("view of type \(Self.self) not found in \(nib)")
}
return view
}
}
Now I want to create a view with an adaptive height depending on its content:
class CustomTopView: UIView {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var subtitleLabel: UILabel!
static func instance(withTitle title: NSAttributedString, subtitle: NSAttributedString) -> CustomTopView {
let customTopView = CustomTopView.fromNib()
customTopView.titleLabel.attributedText = title
customTopView.subtitleLabel.attributedText = subtitle
return customTopView
}
}
And what I would like to do is to display this view animating from the top of the screen, like a notification popup. I wrote this:
extension UIView {
func showCustomTopView(withTitle title: NSAttributedString, subtitle: NSAttributedString) {
let customTopView = CustomTopView.instance(withTitle: title, subtitle: subtitle)
addSubview(customTopView)
customTopView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
customTopView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0),
customTopView.constraint(equalTo: customTopView.trailingAnchor, constant: 20.0),
])
customTopView.layoutIfNeeded() // *
NSLayoutConstraint.activate([customTopView.topAnchor.constraint(equalTo: topAnchor, constant: -customTopView.bounds.height)])
}
}
I have two issues here. The main one is that I don't know how, from this, to perform the animation I want so the view ends up visible. I tried this after the last NSLayoutConstraint.activate(...)
:
UIView.animate(withDuration: 0.5) {
self.topAnchor.constraint(equalTo: customTopView.topAnchor, constant: 100.0).isActive = true
self.layoutIfNeeded()
}
But instead of displaying as expected, the popup starts 100px from the top of the screen, then goes up and disappears. What am I doing wrong?
Also, I am not sure of the line marked with // *
: I wrote this line to fetch the correct customTopView
height, but I'm not sure that's the right way to do it.
Thank you for your help!
CodePudding user response:
One approach:
- create one constraint setting the Bottom of
customTopView
above the Top of the view - create a second constraint setting the Top of
customTopView
below the Top of the view - add
customTopView
as a subview, activating the first constraint - animate
customTopView
into view by deactivating the first constraint and activating the second constraint
Here's a modified version of your showCustomTopView(...)
extension, using a UILabel
as customTopView
-- should work fine with your CustomTopView.instance(withTitle: ...)
:
extension UIView {
func showCustomTopView(withTitle title: NSAttributedString, subtitle: NSAttributedString) {
let customTopView = UILabel()
customTopView.text = title.string
customTopView.backgroundColor = .green
addSubview(customTopView)
customTopView.translatesAutoresizingMaskIntoConstraints = false
let g = self.safeAreaLayoutGuide
// constraint: Bottom of customTopView to Top of self Minus 8-pts
let cvBot = customTopView.bottomAnchor.constraint(equalTo: topAnchor, constant: -8.0)
// constraint: Top of customTopView to Top of self Plus 8-pts
let cvTop = customTopView.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0)
NSLayoutConstraint.activate([
customTopView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0),
customTopView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0),
// activate cvBot, so customTopView is 8-pts above the top of self
cvBot,
])
// execute this async, so the initial view position is set
DispatchQueue.main.async {
// deactivate the Bottom constraint
cvBot.isActive = false
// activate the Top constraint
cvTop.isActive = true
// animate it into view
UIView.animate(withDuration: 0.5, animations: {
self.layoutIfNeeded()
})
}
}
}