Custom view animates from bottom - up at first, but not afterwards.

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.

Accepted Reply

I've fixed it. It's actually not caused by the code above but one extension for UIView that didn't layout after changing the constraints. Thanks anyway.

Replies

I've fixed it. It's actually not caused by the code above but one extension for UIView that didn't layout after changing the constraints. Thanks anyway.