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)
|
snapshot.appendItems(newIdentifiers, toSection: .statuses)
|
||||||
}
|
}
|
||||||
completion(.success(snapshot))
|
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 {
|
struct ToastConfiguration {
|
||||||
var systemImageName: String?
|
var systemImageName: String?
|
||||||
|
var titleFont: UIFont = .boldSystemFont(ofSize: 14)
|
||||||
var title: String
|
var title: String
|
||||||
var subtitle: String?
|
var subtitle: String?
|
||||||
var actionTitle: String?
|
var actionTitle: String?
|
||||||
|
@ -17,6 +18,7 @@ struct ToastConfiguration {
|
||||||
var edgeSpacing: CGFloat = 8
|
var edgeSpacing: CGFloat = 8
|
||||||
var edge: Edge = .automatic
|
var edge: Edge = .automatic
|
||||||
var dismissOnScroll = true
|
var dismissOnScroll = true
|
||||||
|
var dismissAutomaticallyAfter: TimeInterval? = nil
|
||||||
|
|
||||||
init(title: String) {
|
init(title: String) {
|
||||||
self.title = title
|
self.title = title
|
||||||
|
|
|
@ -14,6 +14,8 @@ class ToastView: UIView {
|
||||||
|
|
||||||
private var shrinkAnimator: UIViewPropertyAnimator?
|
private var shrinkAnimator: UIViewPropertyAnimator?
|
||||||
private var recognizedGesture = false
|
private var recognizedGesture = false
|
||||||
|
private var shouldDismissOnScroll = false
|
||||||
|
private(set) var shouldDismissAutomatically = true
|
||||||
|
|
||||||
private var offscreenTranslation: CGFloat {
|
private var offscreenTranslation: CGFloat {
|
||||||
var translation = bounds.height + configuration.edgeSpacing
|
var translation = bounds.height + configuration.edgeSpacing
|
||||||
|
@ -62,7 +64,7 @@ class ToastView: UIView {
|
||||||
let titleLabel = UILabel()
|
let titleLabel = UILabel()
|
||||||
titleLabel.text = configuration.title
|
titleLabel.text = configuration.title
|
||||||
titleLabel.textColor = .white
|
titleLabel.textColor = .white
|
||||||
titleLabel.font = .boldSystemFont(ofSize: 14)
|
titleLabel.font = configuration.titleFont
|
||||||
titleLabel.adjustsFontSizeToFitWidth = true
|
titleLabel.adjustsFontSizeToFitWidth = true
|
||||||
|
|
||||||
if let subtitle = configuration.subtitle {
|
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)
|
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?) {
|
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
super.touchesBegan(touches, with: event)
|
super.touchesBegan(touches, with: event)
|
||||||
|
|
||||||
recognizedGesture = false
|
recognizedGesture = false
|
||||||
|
shouldDismissAutomatically = false
|
||||||
|
|
||||||
shrinkAnimator = UIViewPropertyAnimator(duration: 0.1, curve: .easeInOut) {
|
shrinkAnimator = UIViewPropertyAnimator(duration: 0.1, curve: .easeInOut) {
|
||||||
self.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
|
self.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
|
||||||
}
|
}
|
||||||
|
@ -145,6 +177,7 @@ class ToastView: UIView {
|
||||||
switch recognizer.state {
|
switch recognizer.state {
|
||||||
case .began:
|
case .began:
|
||||||
recognizedGesture = true
|
recognizedGesture = true
|
||||||
|
shouldDismissAutomatically = false
|
||||||
UIView.animate(withDuration: 0.1, delay: 0, options: [.curveEaseInOut, .allowUserInteraction]) {
|
UIView.animate(withDuration: 0.1, delay: 0, options: [.curveEaseInOut, .allowUserInteraction]) {
|
||||||
self.transform = .identity
|
self.transform = .identity
|
||||||
}
|
}
|
||||||
|
@ -199,24 +232,23 @@ class ToastView: UIView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func dismissToast(animated: Bool) {
|
@objc private func scrollViewPanGestureRecognized(_ recognizer: UIPanGestureRecognizer) {
|
||||||
guard animated else {
|
switch recognizer.state {
|
||||||
removeFromSuperview()
|
case .began:
|
||||||
return
|
shouldDismissOnScroll = true
|
||||||
}
|
|
||||||
UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseInOut) {
|
case .changed:
|
||||||
self.transform = CGAffineTransform(translationX: 0, y: self.offscreenTranslation)
|
let translation = recognizer.translation(in: recognizer.view).y
|
||||||
} completion: { (_) in
|
if shouldDismissOnScroll && abs(translation) > 50 {
|
||||||
self.removeFromSuperview()
|
dismissToast(animated: true)
|
||||||
}
|
shouldDismissOnScroll = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateAppearance() {
|
case .ended, .cancelled:
|
||||||
self.transform = CGAffineTransform(translationX: 0, y: offscreenTranslation)
|
shouldDismissOnScroll = false
|
||||||
let duration = 0.5
|
|
||||||
let velocity = 0.5
|
default:
|
||||||
UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: 0.65, initialSpringVelocity: velocity, options: []) {
|
break
|
||||||
self.transform = .identity
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import UIKit
|
||||||
protocol ToastableViewController: UIViewController {
|
protocol ToastableViewController: UIViewController {
|
||||||
|
|
||||||
var toastParentView: UIView { get }
|
var toastParentView: UIView { get }
|
||||||
|
var toastScrollView: UIScrollView? { get }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +34,7 @@ extension ToastableViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
var toastParentView: UIView { view }
|
var toastParentView: UIView { view }
|
||||||
|
var toastScrollView: UIScrollView? { view as? UIScrollView }
|
||||||
|
|
||||||
func showToast(configuration config: ToastConfiguration, animated: Bool) {
|
func showToast(configuration config: ToastConfiguration, animated: Bool) {
|
||||||
currentToast?.dismissToast(animated: false)
|
currentToast?.dismissToast(animated: false)
|
||||||
|
@ -67,6 +69,18 @@ extension ToastableViewController {
|
||||||
if animated {
|
if animated {
|
||||||
toast.animateAppearance()
|
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 {
|
private func effectiveEdge(edge: ToastConfiguration.Edge) -> ToastConfiguration.Edge {
|
||||||
|
|
Loading…
Reference in New Issue