forked from shadowfacts/Tusker
106 lines
3.4 KiB
Swift
106 lines
3.4 KiB
Swift
//
|
|
// ToastableViewController.swift
|
|
// ToastableViewController
|
|
//
|
|
// Created by Shadowfacts on 8/14/21.
|
|
// Copyright © 2021 Shadowfacts. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
|
|
protocol ToastableViewController: UIViewController {
|
|
|
|
var toastParentView: UIView { get }
|
|
var toastScrollView: UIScrollView? { get }
|
|
|
|
}
|
|
|
|
private var currentToastKey = "Tusker_currentToast"
|
|
|
|
extension ToastableViewController {
|
|
|
|
private(set) var currentToast: ToastView? {
|
|
get {
|
|
let holder = objc_getAssociatedObject(self, ¤tToastKey) as? WeakHolder<ToastView>
|
|
return holder?.object
|
|
}
|
|
set {
|
|
if let newValue = newValue {
|
|
objc_setAssociatedObject(self, ¤tToastKey, WeakHolder(object: newValue), .OBJC_ASSOCIATION_RETAIN)
|
|
} else {
|
|
objc_setAssociatedObject(self, ¤tToastKey, nil, .OBJC_ASSOCIATION_RETAIN)
|
|
}
|
|
}
|
|
}
|
|
|
|
var toastParentView: UIView { view }
|
|
var toastScrollView: UIScrollView? { view as? UIScrollView }
|
|
|
|
func showToast(configuration config: ToastConfiguration, animated: Bool) {
|
|
currentToast?.dismissToast(animated: false)
|
|
|
|
var config = config
|
|
config.edge = effectiveEdge(edge: config.edge)
|
|
|
|
let toast = ToastView(configuration: config)
|
|
currentToast = toast
|
|
|
|
let parentSafeArea = toastParentView.safeAreaLayoutGuide
|
|
toast.translatesAutoresizingMaskIntoConstraints = false
|
|
toastParentView.addSubview(toast)
|
|
|
|
let yConstraint: NSLayoutConstraint
|
|
switch config.edge {
|
|
case .top:
|
|
yConstraint = toast.topAnchor.constraint(equalTo: parentSafeArea.topAnchor, constant: config.edgeSpacing)
|
|
case .bottom:
|
|
yConstraint = parentSafeArea.bottomAnchor.constraint(equalTo: toast.bottomAnchor, constant: config.edgeSpacing)
|
|
case .automatic:
|
|
fatalError("unreachable")
|
|
}
|
|
|
|
NSLayoutConstraint.activate([
|
|
toast.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: parentSafeArea.leadingAnchor, multiplier: 1),
|
|
parentSafeArea.trailingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: toast.trailingAnchor, multiplier: 1),
|
|
parentSafeArea.centerXAnchor.constraint(equalTo: toast.centerXAnchor),
|
|
yConstraint,
|
|
])
|
|
|
|
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 {
|
|
guard case .automatic = edge else {
|
|
return edge
|
|
}
|
|
if UIDevice.current.userInterfaceIdiom == .phone {
|
|
return .bottom
|
|
} else {
|
|
return .top
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
fileprivate class WeakHolder<T: AnyObject> {
|
|
weak var object: T?
|
|
|
|
init(object: T) {
|
|
self.object = object
|
|
}
|
|
}
|