From ec4448f14bef27b53ce8032d657853f93cb688e3 Mon Sep 17 00:00:00 2001 From: Juan David Hurtado Date: Sat, 26 Nov 2022 10:11:52 -0500 Subject: [PATCH 1/3] feat: add new implementation of snackbar --- .../Examples/Basic/SnackbarExample.swift | 8 +- .../Extensions/UIApplication+keyWindow.swift | 18 +++ .../Extensions/UIView+NibLoadable.swift | 34 ++++++ .../Component/PuraceSnackbar.swift | 51 ++++++++ .../Snackbar v2/Component/PuraceSnackbar.xib | 47 ++++++++ .../Models/PuraceSnackbarConfig.swift | 15 +++ .../Snackbar v2/PuraceSnackbarManager.swift | 110 ++++++++++++++++++ 7 files changed, 278 insertions(+), 5 deletions(-) create mode 100644 Sources/Purace/Common/Extensions/UIApplication+keyWindow.swift create mode 100644 Sources/Purace/Common/Extensions/UIView+NibLoadable.swift create mode 100644 Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.swift create mode 100644 Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.xib create mode 100644 Sources/Purace/Views/Basic/Snackbar v2/Models/PuraceSnackbarConfig.swift create mode 100644 Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarManager.swift diff --git a/PuraceDemo/PuraceDemo/Examples/Basic/SnackbarExample.swift b/PuraceDemo/PuraceDemo/Examples/Basic/SnackbarExample.swift index 4a45930..12a0c9e 100644 --- a/PuraceDemo/PuraceDemo/Examples/Basic/SnackbarExample.swift +++ b/PuraceDemo/PuraceDemo/Examples/Basic/SnackbarExample.swift @@ -10,16 +10,14 @@ import SwiftUI import Purace struct SnackBarExample: View { - @State var showSnackbar = false - var body: some View { VStack { Text("`PuraceSnackbarView(title: ...)`") .padding() - PuraceButtonView(!showSnackbar ? "Mostrar snackbar" : "Ocultar snackbar", fontSize: 14) { - showSnackbar = !showSnackbar + PuraceButtonView("Mostrar snackbar", fontSize: 14) { + PuraceSnackbarManager.instance.show(withTitle: "Test", type: .info) } Spacer() - }.snackBar(title: "Parece que ha ocurrido un error", isVisible: $showSnackbar, type: .info, buttonTitle: "REINTENTAR", duration: .long, dismissOnDrag: true) + } } } diff --git a/Sources/Purace/Common/Extensions/UIApplication+keyWindow.swift b/Sources/Purace/Common/Extensions/UIApplication+keyWindow.swift new file mode 100644 index 0000000..bcccfb0 --- /dev/null +++ b/Sources/Purace/Common/Extensions/UIApplication+keyWindow.swift @@ -0,0 +1,18 @@ +// +// UIApplication+keyWindow.swift +// +// +// Created by Juan Hurtado on 21/11/22. +// + +import UIKit + +extension UIApplication { + var keyWindow: UIWindow? { + return UIApplication.shared.connectedScenes + .filter { $0.activationState == .foregroundActive } + .first(where: { $0 is UIWindowScene }) + .flatMap({ $0 as? UIWindowScene })?.windows + .first(where: \.isKeyWindow) + } +} diff --git a/Sources/Purace/Common/Extensions/UIView+NibLoadable.swift b/Sources/Purace/Common/Extensions/UIView+NibLoadable.swift new file mode 100644 index 0000000..df7a79f --- /dev/null +++ b/Sources/Purace/Common/Extensions/UIView+NibLoadable.swift @@ -0,0 +1,34 @@ +// +// File.swift +// +// +// Created by Juan Hurtado on 19/11/22. +// + +import Foundation +import UIKit + +public protocol NibLoadable { + static var nibName: String { get } +} + +public extension NibLoadable where Self: UIView { + static var nibName: String { + return String(describing: Self.self) + } + + static var nib: UINib { + let bundle = Bundle.module + return UINib(nibName: Self.nibName, bundle: bundle) + } + + func setupFromNib() { + guard let view = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") } + addSubview(view) + view.translatesAutoresizingMaskIntoConstraints = false + view.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true + view.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true + view.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true + view.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true + } +} diff --git a/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.swift b/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.swift new file mode 100644 index 0000000..d34f9dc --- /dev/null +++ b/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.swift @@ -0,0 +1,51 @@ +// +// PuraceSnackbar.swift +// +// +// Created by Juan Hurtado on 19/11/22. +// + +import UIKit + +class PuraceSnackbar: UIView, NibLoadable { + @IBOutlet var contentView: UIView! + @IBOutlet weak var actionButton: UIButton! + @IBOutlet weak var titleLabel: UILabel! + + override init(frame: CGRect) { + super.init(frame: frame) + setupFromNib() + translatesAutoresizingMaskIntoConstraints = false + setupSubviews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupSubviews() { + titleLabel.font = UIFont(name: "Poppins-Regular", size: 14) + titleLabel.textColor = .white + actionButton.titleLabel?.textColor = .white + actionButton.titleLabel?.font = UIFont(name: "Poppins-Medium", size: 14) + contentView.layer.cornerRadius = 10 + } + + func setup(with title: String, type: PuraceSnackbarType) { + switch type { + case .info: + contentView.backgroundColor = .init(PuraceStyle.Color.G1) + case .alert: + contentView.backgroundColor = .init(PuraceStyle.Color.B1) + case .error: + contentView.backgroundColor = .init(PuraceStyle.Color.R1) + } + titleLabel.text = title + } +} + +// MARK: - Config constants +extension PuraceSnackbar { + static let height: CGFloat = 60 + static let padding: CGFloat = 20 +} diff --git a/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.xib b/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.xib new file mode 100644 index 0000000..d79b10b --- /dev/null +++ b/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.xib @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sources/Purace/Views/Basic/Snackbar v2/Models/PuraceSnackbarConfig.swift b/Sources/Purace/Views/Basic/Snackbar v2/Models/PuraceSnackbarConfig.swift new file mode 100644 index 0000000..f891095 --- /dev/null +++ b/Sources/Purace/Views/Basic/Snackbar v2/Models/PuraceSnackbarConfig.swift @@ -0,0 +1,15 @@ +// +// PuraceSnackbarConfig.swift +// +// +// Created by Juan Hurtado on 21/11/22. +// + +import Foundation + +struct PuraceSnackbarConfig { + let title: String + let type: PuraceSnackbarType = .info + let action: (() -> Void)? + let actionTitle: String? +} diff --git a/Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarManager.swift b/Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarManager.swift new file mode 100644 index 0000000..955fc8d --- /dev/null +++ b/Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarManager.swift @@ -0,0 +1,110 @@ +// +// PuraceSnackbarManager.swift +// +// +// Created by Juan Hurtado on 18/11/22. +// + +import Foundation +import UIKit + +public class PuraceSnackbarManager { + private static var _instance: PuraceSnackbarManager? = nil + public static var instance: PuraceSnackbarManager { + if _instance == nil { + _instance = PuraceSnackbarManager() + } + return _instance! + } + + private var verbose = false + + var isPresented = false + var queue = [() -> ()]() + var snackbar: PuraceSnackbar? + + private init() {} + + private func setupSnackbarView(withTitle title: String, type: PuraceSnackbarType) { + guard let window = UIApplication.shared.keyWindow else { return } + let bottomPadding = window.safeAreaInsets.bottom + + snackbar = PuraceSnackbar(frame: .init(x: 0, y: 0, width: 100, height: 0)) + snackbar?.setup(with: title, type: type) + window.addSubview(snackbar!) + + // Height constriant + snackbar?.heightAnchor.constraint(equalToConstant: PuraceSnackbar.height).isActive = true + + // Bottom constriant + snackbar?.topAnchor.constraint(equalTo: window.safeAreaLayoutGuide.bottomAnchor, constant: bottomPadding).isActive = true + + // Left constraint + snackbar?.leadingAnchor.constraint(equalTo: window.leadingAnchor, constant: PuraceSnackbar.padding).isActive = true + // Right constraint + snackbar?.trailingAnchor.constraint(equalTo: window.trailingAnchor, constant: -PuraceSnackbar.padding).isActive = true + } + + + /// When called this function, some verbose logs will be printed on the console. + /// + /// Call this function **only** for debug purpuses. + public func debug() -> PuraceSnackbarManager { + verbose = true + return self + } + + public func show(withTitle title: String, type: PuraceSnackbarType) { + if isPresented { + enqueue(label: "show") { [weak self] in + guard let self else { return } + self.show(withTitle: title, type: type) + } + return + } + + if verbose { + print("showing snackbar") + } + + setupSnackbarView(withTitle: title, type: type) + UIView.animate(withDuration: 0.4, delay: 0, animations: { [weak self] in + guard let self else { return } + self.snackbar?.transform = .init(translationX: 0, y: -PuraceSnackbar.height - PuraceSnackbar.padding) + }) { _ in + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in + guard let self else { return } + if self.isPresented { + self.hide() + } + } + } + isPresented = true + + } + + public func hide() { + if verbose { + print("hiding snackbar") + } + guard isPresented else { return } + UIView.animate(withDuration: 0.4, delay: 0, animations: { [weak self] in + guard let self else { return } + self.snackbar?.transform = .init(translationX: 0, y: PuraceSnackbar.height) + }) { _ in + self.snackbar?.removeFromSuperview() + self.isPresented = false + guard !self.queue.isEmpty else { return } + let action = self.queue.removeFirst() + action() + } + } + + private func enqueue(label: String, action: @escaping () -> ()) { + queue.append(action) + if verbose { + print("Enqueuing action: \(label)") + print("queue length: \(queue.count)") + } + } +} From 754a9e274e0cdeab37c4e699baf9dd1ff17d813d Mon Sep 17 00:00:00 2001 From: Juan David Hurtado Date: Wed, 14 Dec 2022 11:13:17 -0500 Subject: [PATCH 2/3] fix: snackbar title label fixed --- .../Snackbar v2/Component/PuraceSnackbar.xib | 38 +++++++++++-------- .../Snackbar v2/PuraceSnackbarManager.swift | 4 +- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.xib b/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.xib index d79b10b..d82e63a 100644 --- a/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.xib +++ b/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.xib @@ -19,24 +19,32 @@ - - + + + + + + + - - - - + + + + diff --git a/Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarManager.swift b/Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarManager.swift index 955fc8d..e07ae2a 100644 --- a/Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarManager.swift +++ b/Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarManager.swift @@ -34,7 +34,9 @@ public class PuraceSnackbarManager { window.addSubview(snackbar!) // Height constriant - snackbar?.heightAnchor.constraint(equalToConstant: PuraceSnackbar.height).isActive = true + let heightConstraint = snackbar?.heightAnchor.constraint(equalToConstant: PuraceSnackbar.height) + heightConstraint?.priority = .defaultLow + heightConstraint?.isActive = true // Bottom constriant snackbar?.topAnchor.constraint(equalTo: window.safeAreaLayoutGuide.bottomAnchor, constant: bottomPadding).isActive = true From d9c25d284efaaee6e9c82c9273d36308e13087f9 Mon Sep 17 00:00:00 2001 From: Juan David Hurtado Date: Wed, 14 Dec 2022 21:15:43 -0500 Subject: [PATCH 3/3] feat: add snackbar builder --- .../Examples/Basic/SnackbarExample.swift | 45 ++++++++++++++++-- .../Component/PuraceSnackbar.swift | 27 +++++++++-- .../Snackbar v2/Component/PuraceSnackbar.xib | 3 ++ .../Models/PuraceSnackbarConfig.swift | 15 ------ .../Models/PuraceSnackbarContent.swift | 15 ++++++ .../Snackbar v2/PuraceSnackbarBuilder.swift | 47 +++++++++++++++++++ .../PuraceSnackbarDisplayableContent.swift | 20 ++++++++ .../Snackbar v2/PuraceSnackbarManager.swift | 23 +++++---- 8 files changed, 163 insertions(+), 32 deletions(-) delete mode 100644 Sources/Purace/Views/Basic/Snackbar v2/Models/PuraceSnackbarConfig.swift create mode 100644 Sources/Purace/Views/Basic/Snackbar v2/Models/PuraceSnackbarContent.swift create mode 100644 Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarBuilder.swift create mode 100644 Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarDisplayableContent.swift diff --git a/PuraceDemo/PuraceDemo/Examples/Basic/SnackbarExample.swift b/PuraceDemo/PuraceDemo/Examples/Basic/SnackbarExample.swift index 12a0c9e..83dbb8e 100644 --- a/PuraceDemo/PuraceDemo/Examples/Basic/SnackbarExample.swift +++ b/PuraceDemo/PuraceDemo/Examples/Basic/SnackbarExample.swift @@ -10,14 +10,51 @@ import SwiftUI import Purace struct SnackBarExample: View { + @State var showActionButton = false + private let types = ["info", "error", "alert"] + @State var selectedType = "info" + + func getType() -> PuraceSnackbarType { + switch selectedType { + case "info": + return .info + case "error": + return .error + case "alert": + return .alert + default: return .error + } + } + var body: some View { VStack { - Text("`PuraceSnackbarView(title: ...)`") - .padding() + HStack { + Toggle(isOn: $showActionButton) { + PuraceTextView("Mostrar el botón de acción") + } + } + + HStack { + PuraceTextView("Tipo") + Picker("", selection: $selectedType) { + ForEach(types, id: \.self) { + Text($0) + } + } + Spacer() + } + PuraceButtonView("Mostrar snackbar", fontSize: 14) { - PuraceSnackbarManager.instance.show(withTitle: "Test", type: .info) + PuraceSnackbarBuilder() + .withTitle("Hubo un error") + .withType(getType()) + .withAction(title: showActionButton ? "REINTENTAR" : nil, handler: { + print("Action button tapped") + }) + .build() + .show() } Spacer() - } + }.padding(.horizontal) } } diff --git a/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.swift b/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.swift index d34f9dc..84acad6 100644 --- a/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.swift +++ b/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.swift @@ -7,10 +7,13 @@ import UIKit +// TODO: update name to `PuraceSnackbarView` class PuraceSnackbar: UIView, NibLoadable { @IBOutlet var contentView: UIView! - @IBOutlet weak var actionButton: UIButton! - @IBOutlet weak var titleLabel: UILabel! + @IBOutlet private weak var actionButton: UIButton! + @IBOutlet private weak var titleLabel: UILabel! + + private var actionHandler: (() -> Void)? override init(frame: CGRect) { super.init(frame: frame) @@ -26,12 +29,18 @@ class PuraceSnackbar: UIView, NibLoadable { private func setupSubviews() { titleLabel.font = UIFont(name: "Poppins-Regular", size: 14) titleLabel.textColor = .white + actionButton.titleLabel?.textColor = .white actionButton.titleLabel?.font = UIFont(name: "Poppins-Medium", size: 14) + contentView.layer.cornerRadius = 10 } - func setup(with title: String, type: PuraceSnackbarType) { + func setTitle(_ title: String) { + titleLabel.text = title + } + + func setType(_ type: PuraceSnackbarType) { switch type { case .info: contentView.backgroundColor = .init(PuraceStyle.Color.G1) @@ -40,7 +49,17 @@ class PuraceSnackbar: UIView, NibLoadable { case .error: contentView.backgroundColor = .init(PuraceStyle.Color.R1) } - titleLabel.text = title + } + + func setAction(withTitle title: String?, _ handler: (() -> Void)?) { + actionButton.setTitle(title, for: .normal) + if title != nil { + actionHandler = handler + } + } + + @IBAction private func onActionButtonTap(_ sender: UIButton) { + actionHandler?() } } diff --git a/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.xib b/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.xib index d82e63a..2827d47 100644 --- a/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.xib +++ b/Sources/Purace/Views/Basic/Snackbar v2/Component/PuraceSnackbar.xib @@ -35,6 +35,9 @@ + + + diff --git a/Sources/Purace/Views/Basic/Snackbar v2/Models/PuraceSnackbarConfig.swift b/Sources/Purace/Views/Basic/Snackbar v2/Models/PuraceSnackbarConfig.swift deleted file mode 100644 index f891095..0000000 --- a/Sources/Purace/Views/Basic/Snackbar v2/Models/PuraceSnackbarConfig.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// PuraceSnackbarConfig.swift -// -// -// Created by Juan Hurtado on 21/11/22. -// - -import Foundation - -struct PuraceSnackbarConfig { - let title: String - let type: PuraceSnackbarType = .info - let action: (() -> Void)? - let actionTitle: String? -} diff --git a/Sources/Purace/Views/Basic/Snackbar v2/Models/PuraceSnackbarContent.swift b/Sources/Purace/Views/Basic/Snackbar v2/Models/PuraceSnackbarContent.swift new file mode 100644 index 0000000..55f723c --- /dev/null +++ b/Sources/Purace/Views/Basic/Snackbar v2/Models/PuraceSnackbarContent.swift @@ -0,0 +1,15 @@ +// +// PuraceSnackbarContent.swift +// +// +// Created by Juan Hurtado on 21/11/22. +// + +import Foundation + +public struct PuraceSnackbarContent { + var title: String = "" + var type: PuraceSnackbarType = .info + var action: (() -> Void)? + var actionTitle: String? +} diff --git a/Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarBuilder.swift b/Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarBuilder.swift new file mode 100644 index 0000000..300a161 --- /dev/null +++ b/Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarBuilder.swift @@ -0,0 +1,47 @@ +// +// PuraceSnackbarBuilder.swift +// +// +// Created by Juan Hurtado on 14/12/22. +// + +import Foundation + +/// Snackbar builder. +/// +/// If you want to build and show a snackbar: +/// ``` +/// let builder = PuraceSnackbarBuilder() +/// +/// builder +/// .withTitle("This is a snackbar!") +/// .build() +/// .show() +/// ``` +public class PuraceSnackbarBuilder { + private var content: PuraceSnackbarContent + + public init() { + content = PuraceSnackbarContent() + } + + public func withTitle(_ title: String) -> PuraceSnackbarBuilder { + content.title = title + return self + } + + public func withType(_ type: PuraceSnackbarType) -> PuraceSnackbarBuilder { + content.type = type + return self + } + + public func withAction(title: String?, handler: @escaping () -> Void) -> PuraceSnackbarBuilder { + content.actionTitle = title + content.action = handler + return self + } + + public func build() -> PuraceSnackbarDisplayableContent { + PuraceSnackbarDisplayableContent(content: content) + } +} diff --git a/Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarDisplayableContent.swift b/Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarDisplayableContent.swift new file mode 100644 index 0000000..b35d29c --- /dev/null +++ b/Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarDisplayableContent.swift @@ -0,0 +1,20 @@ +// +// PuraceSnackbarDisplayableContent.swift +// +// +// Created by Juan Hurtado on 14/12/22. +// + +import Foundation + +public class PuraceSnackbarDisplayableContent { + private let content: PuraceSnackbarContent + + init(content: PuraceSnackbarContent) { + self.content = content + } + + public func show() { + PuraceSnackbarManager.instance.show(using: content) + } +} diff --git a/Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarManager.swift b/Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarManager.swift index e07ae2a..95e102b 100644 --- a/Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarManager.swift +++ b/Sources/Purace/Views/Basic/Snackbar v2/PuraceSnackbarManager.swift @@ -8,7 +8,7 @@ import Foundation import UIKit -public class PuraceSnackbarManager { +class PuraceSnackbarManager { private static var _instance: PuraceSnackbarManager? = nil public static var instance: PuraceSnackbarManager { if _instance == nil { @@ -25,12 +25,18 @@ public class PuraceSnackbarManager { private init() {} - private func setupSnackbarView(withTitle title: String, type: PuraceSnackbarType) { + private func setupSnackbarView(using content: PuraceSnackbarContent) { guard let window = UIApplication.shared.keyWindow else { return } + var topController = window.rootViewController + while let presentedViewController = topController?.presentedViewController { + topController = presentedViewController + } let bottomPadding = window.safeAreaInsets.bottom snackbar = PuraceSnackbar(frame: .init(x: 0, y: 0, width: 100, height: 0)) - snackbar?.setup(with: title, type: type) + snackbar?.setTitle(content.title) + snackbar?.setType(content.type) + snackbar?.setAction(withTitle: content.actionTitle, content.action) window.addSubview(snackbar!) // Height constriant @@ -51,16 +57,16 @@ public class PuraceSnackbarManager { /// When called this function, some verbose logs will be printed on the console. /// /// Call this function **only** for debug purpuses. - public func debug() -> PuraceSnackbarManager { + func debug() -> PuraceSnackbarManager { verbose = true return self } - public func show(withTitle title: String, type: PuraceSnackbarType) { + func show(using content: PuraceSnackbarContent) { if isPresented { enqueue(label: "show") { [weak self] in guard let self else { return } - self.show(withTitle: title, type: type) + self.show(using: content) } return } @@ -69,7 +75,7 @@ public class PuraceSnackbarManager { print("showing snackbar") } - setupSnackbarView(withTitle: title, type: type) + setupSnackbarView(using: content) UIView.animate(withDuration: 0.4, delay: 0, animations: { [weak self] in guard let self else { return } self.snackbar?.transform = .init(translationX: 0, y: -PuraceSnackbar.height - PuraceSnackbar.padding) @@ -82,10 +88,9 @@ public class PuraceSnackbarManager { } } isPresented = true - } - public func hide() { + func hide() { if verbose { print("hiding snackbar") }