I wanted to be able to have the user swipe down to dismiss a temporary Notification that comes in from the bottom.
Here's what the code looks like:
func showAnimationToast(...) {
let toastView = UIView(frame: CGRect(x: 10, y: view.frame.size.height - view.safeAreaInsets.bottom, width: view.frame.size.width - 20, height: 60))
...
toastView.tag = 1474
let animationView = AnimationView(name: animationName)
...
toastView.addSubview(animationView)
let messageLabel = UILabel(frame: CGRect(x: toastView.frame.size.height, y: 5, width: toastView.frame.size.width - toastView.frame.size.height, height: 50))
...
toastView.addSubview(messageLabel)
toastView.isUserInteractionAvailable = true
I tried to add a UISwipeGestureRecognizer to toastView
, but it never worked. I even tried the simple UITapGestureRecognizer and it STILL didn't work.
Here's what I tried:
//Let Swipe Down Dismiss. Does not work
let swipeDownGesture = UISwipeGestureRecognizer(target: self, action: #selector(dismissToast(_:)))
swipeDownGesture.direction = .down
toastView.addGestureRecognizer(swipeDownGesture)
//Show animation
UIView.animate(withDuration: 0.2, delay: 0, animations: {
toastView.frame.origin.y = self.view.frame.size.height - self.view.safeAreaInsets.bottom - 70
}, completion: {_ in
animationView.play()
})
//Remove after specified time
UIView.animate(withDuration: 0.2, delay: duration, animations: {
toastView.center.y = self.view.frame.size.height + 50
}, completion: {_ in
toastView.removeFromSuperview()
})
}
@objc func dismissToast(_ sender: UIGestureRecognizer) {
print("dismiss")
let toastView = view.subviews.filter { view in
if view.tag == 1474 /*toastView*/ tag{
return true
}
return false
}.last!
UIView.animate(withDuration: 0.2, delay: 0, animations: {
toastView.center.y = self.view.frame.size.height + 50
}, completion: {_ in
toastView.removeFromSuperview()
})
}
The issue seems to be that while a view is waiting for an animation to play (during the "delay" period), it can't receive user interactions.
One way to work around this is to not use the delay
parameter, and instead use DispatchQueue.main.asyncAfter
:
UIView.animate(withDuration: 0.2, delay: 0, options: [.allowUserInteraction], animations: {
toastView.frame.origin.y = self.view.frame.size.height - self.view.safeAreaInsets.bottom - 70
}, completion: {_ in
DispatchQueue.main.asyncAfter(deadline: .now() + duration + 0.2) {
UIView.animate(withDuration: 0.2, delay: 0, animations: {
toastView.center.y = self.view.frame.size.height + 50
}, completion: {_ in
toastView.removeFromSuperview()
})
}
})