I have this ToastView that will animate by expanding the height from the top of the screen. All the components are created programmatically. This is the code:
final class ToastView: UIView {
private static let instance = createContainer()
private let labelHeightOffset: CGFloat = 32
private let messageLabel: UILabel = {
let label = UILabel()
label.textAlignment = .center
label.numberOfLines = 0
return label
}()
private var heightConstraint: NSLayoutConstraint!
static func show(message: String) {
let keyWindow = UIWindow.getKeyWindow() //extension to get the kley window
show(on: keyWindow, message: message)
}
private static func show(on parentView: UIView? = nil, message: String) {
guard let parentView = parentView ?? UIViewController.getTopViewController()?.view,
instance.superview == nil,
let toast = toast else {
return
}
parentView.addSubview(instance, method: .fill)
toast.messageLabel.text = message
toast.messageLabel.textColor = .red
toast.messageLabel.textAlignment = .center
toast.messageLabel.sizeToFit()
toast.show(for: 3)
}
private func show(for duration: TimeInterval = 0) {
isHidden = false
layoutIfNeeded()
UIView.animate(withDuration: 0.4, animations: {
let labelLineHeight = self.messageLabel.getRect(maxLine: 3).size.height //getRect is an extension func to get the rect based on the content of the label
let lineSpaces = CGFloat((self.messageLabel.getLineNumber() - 1) * 2) //getLineNumber is an extension func to get the total number of lines based on the content
//Get the height by the content of the label
self.heightConstraint.constant = self.labelLineHeight +
self.labelHeightOffset +
lineSpaces
self.setNeedsUpdateConstraints()
self.superview?.layoutIfNeeded()
}, completion: { _ in
self.hide(delay: duration)
})
}
private func hide(delay: TimeInterval = 0) {
UIView.animate(withDuration: 0.4, delay: delay, animations: {
self.heightConstraint.constant = 0
self.setNeedsUpdateConstraints()
self.superview?.layoutIfNeeded()
}, completion: { _ in
self.isHidden = true
self.messageLabel.text = nil
ToastView.instance.removeFromSuperview()
})
}
}
private extension ToastView {
static var toast: ToastView? {
return instance.subviews[0] as? ToastView
}
static func createContainer() -> UIView {
let container = UIView(frame: .zero)
container.backgroundColor = .clear
let toast = ToastView(frame: .zero)
toast.backgroundColor = .white
toast.addCorner(radius: 8)
container.addSubview(toast)
toast.layoutMessageLabel()
toast.layoutToast()
return container
}
func layoutMessageLabel() {
addSubview(messageLabel)
messageLabel.center = center
messageLabel.translatesAutoresizingMaskIntoConstraints = false
let constraints = [
centerYAnchor.constraint(equalTo: messageLabel.centerYAnchor),
leftAnchor.constraint(equalTo: messageLabel.leftAnchor, constant: 16),
rightAnchor.constraint(equalTo: messageLabel.rightAnchor, constant: 16)
]
NSLayoutConstraint.activate(constraints)
messageLabel.setNeedsUpdateConstraints()
layoutIfNeeded()
}
func layoutToast() {
translatesAutoresizingMaskIntoConstraints = false
let topConstants: CGFloat = UIWindow.getKeyWindow()?.safeAreaInsets.top ?? 0 + 16
let topConstraint = topAnchor.constraint(equalTo: superview!.topAnchor,
constant: topConstants)
heightConstraint = heightAnchor.constraint(equalToConstant: 0)
let constraints = [
topConstraint,
heightConstraint!,
leftAnchor.constraint(equalTo: superview!.leftAnchor, constant: 16),
superview!.rightAnchor.constraint(equalTo: rightAnchor, constant: 16)
]
NSLayoutConstraint.activate(constraints)
setNeedsUpdateConstraints()
superview!.layoutIfNeeded()
}
}
The code is working fine EXCEPT for the first time it appears. It always animates from the bottom of the screen and rising above ot the top. But if you'll have a look at the code, I only animte the heightConstraint's constant value. Why is this happening? Can you help me fix this? thanks.