I've implemented a popup notification in my app to show user the data was being updated successfully or not.
This popup is just a UIView instantiated from .xib. Here is screenshot of elements hierarchy in xib file: https://cln.sh/te8JhM
Also I wanted the ability to swipe out the popup if user don't want to see a full time it shows itself on the screen. To do that I have implemented a UIGestureRecognizer:
private func configureTapGesture() {
guard let window = UIApplication.shared.windows.first(where: {$0.isKeyWindow}) else { return }
let swipe = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipes(_:)))
swipe.direction = .up
window.addGestureRecognizer(swipe)
}
@objc private func handleSwipes(_ sender:UISwipeGestureRecognizer) {
if sender.direction == .up {
UIView.animate(withDuration: 0.15, delay: 0.0, options: .curveLinear) {
self.popupView.center.y -= 50
} completion: { _ in
self.popupView.removeFromSuperview()
}
self.isRemovedByTap = true
}
}
When I run the app it works only if I swipe out anywhere but not inside the popupView. Please check for the GIF: https://cln.sh/sNm5RN
If I replace a target from self
(Popup class) to popupView
(UIVisualEffectView, which is a rounded rectangle you see on GIF), then I receive an error unrecognized selector sent to instance
Here is a my full custom Popup class where I initialize the view, configure, animate and show it:
import UIKit
class PopupView: UIView {
@IBOutlet weak var popupView: UIVisualEffectView!
@IBOutlet weak var symbol: UIImageView!
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var descriptionLabel: UILabel!
private var isRemovedByTap = false
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
configure()
}
private func configure() {
if let views = Bundle.main.loadNibNamed("PopupView", owner: self) {
guard let view = views.first as? UIView else { return }
view.frame = bounds
addSubview(view)
}
}
private func configurePopup() {
guard let window = UIApplication.shared.windows.first(where: {$0.isKeyWindow}) else { return }
popupView.layer.cornerRadius = 20
popupView.clipsToBounds = true
popupView.center.x = window.frame.midX
popupView.translatesAutoresizingMaskIntoConstraints = true
window.addSubview(popupView)
}
private func animatePopup() {
UIView.animate(withDuration: 0.15, delay: 0.0, options: .curveLinear) {
self.popupView.center.y += 34
} completion: { _ in
UIView.animate(withDuration: 0.15, delay: 10.0, options: .curveLinear) {
self.popupView.center.y -= 50
} completion: { _ in
if !self.isRemovedByTap {
self.popupView.removeFromSuperview()
}
}
}
}
private func configureTapGesture() {
guard let window = UIApplication.shared.windows.first(where: {$0.isKeyWindow}) else { return }
let swipe = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipes(_:)))
swipe.direction = .up
window.addGestureRecognizer(swipe)
}
@objc private func handleSwipes(_ sender:UISwipeGestureRecognizer) {
if sender.direction == .up {
UIView.animate(withDuration: 0.15, delay: 0.0, options: .curveLinear) {
self.popupView.center.y -= 50
} completion: { _ in
self.popupView.removeFromSuperview()
}
self.isRemovedByTap = true
}
}
func showPopup(title: String, message: String, symbol: UIImage) {
titleLabel.text = title
descriptionLabel.text = message
self.symbol.image = symbol
configurePopup()
animatePopup()
configureTapGesture()
}
}
What should I change to be able to swipe out only when inside the popupView rounded rectangle?
Here is how I was able to achieve my needs:
-
Switch
popupView.isUserInteractionEnabled = false
to start swipe recognises not only outside the view -
change my didSwipe method onto this:
@objc private func didSwipe(_ sender:UISwipeGestureRecognizer) {
guard let window = UIApplication.shared.windows.first(where: {$0.isKeyWindow}) else { return }
let tappedArea = sender.location(in: popupView)
let popupFrame = CGRect(x: tappedArea.x, y: tappedArea.y, width: popupView.frame.width, height: popupView.frame.height)
if sender.direction == .up, window.frame.contains(popupFrame) {
UIView.animate(withDuration: 0.15, delay: 0.0, options: .curveLinear) {
self.popupView.center.y -= 50
} completion: { _ in
self.popupView.removeFromSuperview()
}
self.isRemovedBySwipe = true
}
}
Now I am able to swipe out the popup only when swipe inside its borders and ignore swipes outside of it.