Show toast when there are no new posts
This commit is contained in:
parent
1fda4248ec
commit
f109253bba
|
@ -236,6 +236,18 @@ class TimelineTableViewController: DiffableTimelineLikeTableViewController<Timel
|
|||
snapshot.appendItems(newIdentifiers, toSection: .statuses)
|
||||
}
|
||||
completion(.success(snapshot))
|
||||
|
||||
if statuses.isEmpty {
|
||||
DispatchQueue.main.async {
|
||||
var config = ToastConfiguration(title: "You're all caught up")
|
||||
config.edge = .top
|
||||
config.dismissAutomaticallyAfter = 2
|
||||
config.action = { (toast) in
|
||||
toast.dismissToast(animated: true)
|
||||
}
|
||||
self.showToast(configuration: config, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import UIKit
|
|||
|
||||
struct ToastConfiguration {
|
||||
var systemImageName: String?
|
||||
var titleFont: UIFont = .boldSystemFont(ofSize: 14)
|
||||
var title: String
|
||||
var subtitle: String?
|
||||
var actionTitle: String?
|
||||
|
@ -17,6 +18,7 @@ struct ToastConfiguration {
|
|||
var edgeSpacing: CGFloat = 8
|
||||
var edge: Edge = .automatic
|
||||
var dismissOnScroll = true
|
||||
var dismissAutomaticallyAfter: TimeInterval? = nil
|
||||
|
||||
init(title: String) {
|
||||
self.title = title
|
||||
|
|
|
@ -14,6 +14,8 @@ class ToastView: UIView {
|
|||
|
||||
private var shrinkAnimator: UIViewPropertyAnimator?
|
||||
private var recognizedGesture = false
|
||||
private var shouldDismissOnScroll = false
|
||||
private(set) var shouldDismissAutomatically = true
|
||||
|
||||
private var offscreenTranslation: CGFloat {
|
||||
var translation = bounds.height + configuration.edgeSpacing
|
||||
|
@ -62,7 +64,7 @@ class ToastView: UIView {
|
|||
let titleLabel = UILabel()
|
||||
titleLabel.text = configuration.title
|
||||
titleLabel.textColor = .white
|
||||
titleLabel.font = .boldSystemFont(ofSize: 14)
|
||||
titleLabel.font = configuration.titleFont
|
||||
titleLabel.adjustsFontSizeToFitWidth = true
|
||||
|
||||
if let subtitle = configuration.subtitle {
|
||||
|
@ -109,10 +111,40 @@ class ToastView: UIView {
|
|||
layer.shadowPath = CGPath(roundedRect: bounds, cornerWidth: layer.cornerRadius, cornerHeight: layer.cornerRadius, transform: nil)
|
||||
}
|
||||
|
||||
func dismissToast(animated: Bool) {
|
||||
guard animated else {
|
||||
removeFromSuperview()
|
||||
return
|
||||
}
|
||||
UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseInOut) {
|
||||
self.transform = CGAffineTransform(translationX: 0, y: self.offscreenTranslation)
|
||||
} completion: { (_) in
|
||||
self.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
func animateAppearance() {
|
||||
self.transform = CGAffineTransform(translationX: 0, y: offscreenTranslation)
|
||||
let duration = 0.5
|
||||
let velocity = 0.5
|
||||
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.65, initialSpringVelocity: velocity, options: []) {
|
||||
self.transform = .identity
|
||||
}
|
||||
}
|
||||
|
||||
func setupDismissOnScroll(connectedTo scrollView: UIScrollView) {
|
||||
guard configuration.dismissOnScroll else { return }
|
||||
scrollView.panGestureRecognizer.addTarget(self, action: #selector(scrollViewPanGestureRecognized))
|
||||
}
|
||||
|
||||
// MARK: - Interaction
|
||||
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
|
||||
recognizedGesture = false
|
||||
shouldDismissAutomatically = false
|
||||
|
||||
shrinkAnimator = UIViewPropertyAnimator(duration: 0.1, curve: .easeInOut) {
|
||||
self.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
|
||||
}
|
||||
|
@ -145,6 +177,7 @@ class ToastView: UIView {
|
|||
switch recognizer.state {
|
||||
case .began:
|
||||
recognizedGesture = true
|
||||
shouldDismissAutomatically = false
|
||||
UIView.animate(withDuration: 0.1, delay: 0, options: [.curveEaseInOut, .allowUserInteraction]) {
|
||||
self.transform = .identity
|
||||
}
|
||||
|
@ -199,24 +232,23 @@ class ToastView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
func dismissToast(animated: Bool) {
|
||||
guard animated else {
|
||||
removeFromSuperview()
|
||||
return
|
||||
}
|
||||
UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseInOut) {
|
||||
self.transform = CGAffineTransform(translationX: 0, y: self.offscreenTranslation)
|
||||
} completion: { (_) in
|
||||
self.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
func animateAppearance() {
|
||||
self.transform = CGAffineTransform(translationX: 0, y: offscreenTranslation)
|
||||
let duration = 0.5
|
||||
let velocity = 0.5
|
||||
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.65, initialSpringVelocity: velocity, options: []) {
|
||||
self.transform = .identity
|
||||
@objc private func scrollViewPanGestureRecognized(_ recognizer: UIPanGestureRecognizer) {
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
shouldDismissOnScroll = true
|
||||
|
||||
case .changed:
|
||||
let translation = recognizer.translation(in: recognizer.view).y
|
||||
if shouldDismissOnScroll && abs(translation) > 50 {
|
||||
dismissToast(animated: true)
|
||||
shouldDismissOnScroll = false
|
||||
}
|
||||
|
||||
case .ended, .cancelled:
|
||||
shouldDismissOnScroll = false
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import UIKit
|
|||
protocol ToastableViewController: UIViewController {
|
||||
|
||||
var toastParentView: UIView { get }
|
||||
var toastScrollView: UIScrollView? { get }
|
||||
|
||||
}
|
||||
|
||||
|
@ -33,6 +34,7 @@ extension ToastableViewController {
|
|||
}
|
||||
|
||||
var toastParentView: UIView { view }
|
||||
var toastScrollView: UIScrollView? { view as? UIScrollView }
|
||||
|
||||
func showToast(configuration config: ToastConfiguration, animated: Bool) {
|
||||
currentToast?.dismissToast(animated: false)
|
||||
|
@ -67,6 +69,18 @@ extension ToastableViewController {
|
|||
if animated {
|
||||
toast.animateAppearance()
|
||||
}
|
||||
|
||||
if config.dismissOnScroll,
|
||||
let scrollView = toastScrollView {
|
||||
toast.setupDismissOnScroll(connectedTo: scrollView)
|
||||
}
|
||||
|
||||
if let time = config.dismissAutomaticallyAfter {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + time) { [weak toast] in
|
||||
guard let toast = toast, toast.shouldDismissAutomatically else { return }
|
||||
toast.dismissToast(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func effectiveEdge(edge: ToastConfiguration.Edge) -> ToastConfiguration.Edge {
|
||||
|
|
Loading…
Reference in New Issue