I'm building a custom notification system for my iOS app where temporary notification views are displayed on top of the screen. These views have a layered structure with a UIVisualEffectView for a blur effect and a content view containing icons and text.
I want users to dismiss the notification by swiping down. I have successfully found that the swipe gestures are being added to the views, but somehow, the swipes are not being detected, not even taps or pan gestures.
The swipe gesture sometimes fails to trigger consistently. Below is the relevant code:
class NotificationViewController: UIViewController, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
// Show a notification after 1 second
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.showNotification(message: "Swipe down to dismiss!")
}
}
func showNotification(message: String) {
let notificationView = createNotificationView(message: message)
view.addSubview(notificationView)
positionNotification(notificationView, above: nil)
// Automatically dismiss after 3 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
self.dismissNotification(notificationView)
}
}
private func createNotificationView(message: String) -> UIView {
// Create the notification view
let notificationView = UIView()
notificationView.backgroundColor = .clear
notificationView.layer.cornerRadius = 12
notificationView.layer.borderWidth = 1
notificationView.layer.borderColor = UIColor.darkGray.cgColor
notificationView.translatesAutoresizingMaskIntoConstraints = false
notificationView.isUserInteractionEnabled = true
// Add blur effect
let blurEffect = UIBlurEffect(style: .systemChromeMaterial)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
blurEffectView.frame = notificationView.bounds
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
blurEffectView.isUserInteractionEnabled = false
notificationView.addSubview(blurEffectView)
// Add content to the blur effect view
let label = UILabel()
label.text = message
label.textColor = .label
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
blurEffectView.contentView.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: blurEffectView.contentView.centerXAnchor),
label.centerYAnchor.constraint(equalTo: blurEffectView.contentView.centerYAnchor)
])
// Add swipe gesture recognizer
let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(_:)))
swipeGesture.direction = .down
swipeGesture.delegate = self
notificationView.addGestureRecognizer(swipeGesture)
return notificationView
}
private func positionNotification(_ notificationView: UIView, above previousNotificationView: UIView?) {
notificationView.translatesAutoresizingMaskIntoConstraints = false
if let previousView = previousNotificationView {
NSLayoutConstraint.activate([
notificationView.bottomAnchor.constraint(equalTo: previousView.topAnchor, constant: -10),
notificationView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
notificationView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.9),
notificationView.heightAnchor.constraint(equalToConstant: 60)
])
} else {
NSLayoutConstraint.activate([
notificationView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 60),
notificationView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
notificationView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.9),
notificationView.heightAnchor.constraint(equalToConstant: 60)
])
}
}
@objc private func handleSwipe(_ gesture: UISwipeGestureRecognizer) {
guard let notificationView = gesture.view else { return }
print("Swipe detected on notification!")
dismissNotification(notificationView)
}
private func dismissNotification(_ notificationView: UIView) {
UIView.animate(withDuration: 0.3, animations: {
notificationView.alpha = 0
}) { _ in
notificationView.removeFromSuperview()
}
}
}
Same approach has been working for other views, but they were declared at the parent class level, while this "notification view" is something that is initialized within a function, but the values and everything else works well. This should have worked, so I am not sure what I am missing.
Would love some advice.
Worked around the issue by changing from UIView to UIViewController.
class NotificationViewController: UIViewController {
private let message: String
private let textColor: UIColor
private let backgroundColor: UIColor
private let iconName: String?
private let iconColor: UIColor
private let iconHeight: CGFloat
private let displayDuration: Double
init(message: String, textColor: UIColor, backgroundColor: UIColor, iconName: String?, iconColor: UIColor, iconHeight: CGFloat, displayDuration: Double) {
self.message = message
self.textColor = textColor
self.backgroundColor = backgroundColor
self.iconName = iconName
self.iconColor = iconColor
self.iconHeight = iconHeight
self.displayDuration = displayDuration
super.init(nibName: nil, bundle: nil)
self.modalPresentationStyle = .overCurrentContext
self.modalTransitionStyle = .crossDissolve
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.definesPresentationContext = true
setupNotificationView()
}
private func setupNotificationView() {
self.view.backgroundColor = .clear // Make sure background is transparent
// Create the notification content view
let notificationContentView = UIView()
notificationContentView.backgroundColor = .clear
notificationContentView.layer.cornerRadius = ViewSetUpManager.mediumCornerRadius
notificationContentView.layer.borderWidth = 1
notificationContentView.layer.borderColor = UIColor.darkGray.cgColor
notificationContentView.clipsToBounds = true
notificationContentView.isUserInteractionEnabled = true
// Add blur effect
let blurEffect = UIBlurEffect(style: .systemChromeMaterial)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
notificationContentView.addSubview(blurEffectView)
blurEffectView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
// Add icon and label
let iconImageView = UIImageView()
if let iconName = iconName {
iconImageView.image = UIImage(systemName: iconName)
iconImageView.contentMode = .scaleAspectFit
iconImageView.tintColor = iconColor
iconImageView.isUserInteractionEnabled = false
blurEffectView.contentView.addSubview(iconImageView)
iconImageView.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(16)
make.centerY.equalToSuperview()
make.width.height.equalTo(iconHeight)
}
}
let label = UILabel()
label.text = message
label.font = UIFont.systemFont(ofSize: ViewSetUpManager.shared.genericTitleFontSize(), weight: .regular)
label.textColor = textColor
label.numberOfLines = 2
label.textAlignment = .center
label.adjustsFontSizeToFitWidth = true
label.isUserInteractionEnabled = false
blurEffectView.contentView.addSubview(label)
label.snp.makeConstraints { make in
make.leading.equalTo(iconImageView.snp.trailing).offset(8)
make.trailing.equalToSuperview().inset(16)
make.centerY.equalToSuperview()
}
// Add notificationContentView to self.view
self.view.addSubview(notificationContentView)
notificationContentView.snp.makeConstraints { make in
make.bottom.equalTo(self.view.safeAreaLayoutGuide).offset(-(self.tabBarHeight() + 50))
make.centerX.equalToSuperview()
make.width.equalToSuperview().multipliedBy(0.9)
make.height.greaterThanOrEqualTo(60)
}
// Add swipe gesture recognizer
let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipeGesture(_:)))
swipeGesture.direction = [.left, .down, .right]
swipeGesture.delegate = self
notificationContentView.addGestureRecognizer(swipeGesture)
// Automatically dismiss after displayDuration
DispatchQueue.main.asyncAfter(deadline: .now() + displayDuration) {
self.dismiss(animated: true, completion: nil)
}
}
private func tabBarHeight() -> CGFloat {
return self.presentingViewController?.tabBarController?.tabBar.frame.height ?? 0
}
@objc private func handleSwipeGesture(_ gesture: UISwipeGestureRecognizer) {
self.dismiss(animated: true, completion: nil)
}
}
extension NotificationViewController: UIGestureRecognizerDelegate {
// Implement delegate methods if necessary
}