jsdw_ios/QuickLocation/UIKit/Alert/DLToast.swift

483 lines
16 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// DLToast.swift
// DLSDK
//
// Created by osell on 2023/8/18.
//
import UIKit
// MARK: - DLToastConfig
public class DLToastConfig {
fileprivate static var tag = NSStringFromClass(DLToastView.self).hash
public enum Style {
case none
case loading
case progress
}
///
var bgColor: UIColor = .clear
///
var containerBgColor: UIColor = .black.withAlphaComponent(0.7)
///
var containerRadius: CGFloat = 12.0
///
var textColor: UIColor = .white
///
var textFont: UIFont = .systemFont(ofSize: 16, weight: .medium)
///
var iconSize: CGSize = CGSize(width: 34, height: 34)
public init() {}
}
// MARK: - ToastImageView
class ToastImageView: UIImageView {
private var displayLink: CADisplayLink?
private var rotationAngle: CGFloat = 0.0
func startAnimation() {
displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:)))
displayLink?.add(to: .current, forMode: .common)
}
@objc private func handleDisplayLink(_ displayLink: CADisplayLink) {
rotationAngle += CGFloat(displayLink.duration) * 2.0 * .pi
transform = CGAffineTransform(rotationAngle: rotationAngle)
}
func stopAnimation() {
displayLink?.remove(from: .current, forMode: .common)
displayLink?.invalidate()
displayLink = nil
}
deinit {
stopAnimation()
}
}
// MARK: - DLToastView
class DLToastView: UIView {
///
var completion: (() -> Void)?
/// container
private lazy var containerView: UIView = {
let view = UIView()
view.backgroundColor = config.containerBgColor
view.setCornerRadius(config.containerRadius)
view.isUserInteractionEnabled = true
return view
}()
///
private lazy var iconImageView: ToastImageView = {
let view = ToastImageView()
return view
}()
/// text
private lazy var textLabel: UILabel = {
let label = UILabel()
label.font = config.textFont
label.textColor = config.textColor
label.textAlignment = .center
label.numberOfLines = 0
return label
}()
private weak var viewForPresent: UIView?
private var config: DLToastConfig = DLToastConfig()
private var style: DLToastConfig.Style = .none
private var text: String?
private var iconImage: UIImage?
private var timer: DispatchSourceTimer?
private override init(frame: CGRect) {
super.init(frame: .zero)
setupSubViews()
}
init(iconImage: UIImage?, text: String?, config: DLToastConfig, style: DLToastConfig.Style) {
self.iconImage = iconImage
self.text = text
self.config = config
self.style = style
super.init(frame: .zero)
setupSubViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
///
private func setupSubViews() {
//
addSubview(containerView)
containerView.layoutChain
.center()
.left(48, relation: .greaterThanOrEqual)
.right(48, relation: .greaterThanOrEqual)
.top(kNaviHeight, relation: .greaterThanOrEqual)
.bottom(kNaviHeight, relation: .greaterThanOrEqual)
.width(100, relation: .greaterThanOrEqual)
//
let isShowText = text?.isEmpty == false
var preView: UIView?
//
if let iconImage = iconImage {
iconImageView.image = iconImage
containerView.addSubview(iconImageView)
iconImageView.layoutChain
.centerX()
.size(config.iconSize)
.top(isShowText ? 30 : 33)
.left(33, relation: isShowText ? .greaterThanOrEqual : .equal)
.right(33, relation: isShowText ? .greaterThanOrEqual : .equal)
preView = iconImageView
}
//
if isShowText {
textLabel.text = text
containerView.addSubview(textLabel)
textLabel.layoutChain
.edgesHorzontal(16)
.width(112, relation: .greaterThanOrEqual)
if let preView = preView {
textLabel.layoutChain
.topToBottomOfView(preView, offset: 20)
} else {
textLabel.layoutChain
.top(12)
}
preView = textLabel
}
if let preView = preView as? UIImageView {
preView.layoutChain
.bottom(33)
} else if let preView = preView as? UILabel {
preView.layoutChain
.bottom(isShowText && iconImage != nil ? 30 : 12)
}
}
deinit {
NotificationCenter.default.removeObserver(self)
if timer != nil {
timer?.cancel()
timer = nil
}
}
}
extension DLToastView {
/// Toast
/// - Parameter view: toast
func show(in view: UIView) {
viewForPresent = view
guard let viewForPresent = viewForPresent else { return }
backgroundColor = .clear
isUserInteractionEnabled = style == .loading
viewForPresent.addSubview(self)
self.layoutChain
.edges()
alpha = 0
sizeToFit()
containerView.center = .init(x: viewForPresent.frame.midX, y: viewForPresent.frame.midY)
containerView.transform = transform.scaledBy(x: 0.8, y: 0.8)
UIView.animate(withDuration: 0.25) {
self.alpha = 1
self.containerView.transform = CGAffineTransform.identity
self.backgroundColor = self.config.bgColor
}
// loading
if style == .loading {
NotificationCenter.default.addObserver(self, selector: #selector(applicationWillResignActive), name: UIApplication.willResignActiveNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
//
iconImageView.startAnimation()
}
//
if style == .none {
let text = text ?? ""
var time = Double(text.count) * 0.03 + 1.25
time = time > 3.0 ? 3.0 : time
self.dismiss(delay: time, self.completion)
}
}
///
/// - Parameter completion:
func dismiss(delay: TimeInterval = 0, _ completion: (() -> Void)? = nil) {
//
if timer != nil {
timer?.cancel()
timer = nil
}
let queue = DispatchQueue(label: "com.toast.timer", qos: .background)
let timer = DispatchSource.makeTimerSource(queue: queue)
timer.schedule(deadline: .now() + delay)
timer.setEventHandler { [weak self] in
guard let this = self else { return }
DispatchQueue.main.async {
UIView.animate(withDuration: 0.25) {
this.alpha = 0
this.containerView.transform = this.containerView.transform.scaledBy(x: 0.2, y: 0.2)
} completion: { _ in
if this.style == .loading {
this.iconImageView.stopAnimation()
}
this.removeFromSuperview()
completion?()
}
}
}
timer.resume()
self.timer = timer
}
@objc func applicationWillResignActive() {
if style == .loading {
iconImageView.stopAnimation()
}
}
@objc func applicationDidBecomeActive() {
if style == .loading {
iconImageView.startAnimation()
}
}
}
// MARK: - Toast
public class DLToast {
/// loading
/// - Parameters:
/// - text:
/// - view: window
public static func showLoading(text: String? = nil,
in view: UIView? = nil) {
show(text: text, icon: UIImage(named: "loading"), style: .loading, in: view)
}
///
/// - Parameters:
/// - text:
/// - view: window
/// - completion:
public static func showSuccess(text: String? = nil,
in view: UIView? = nil,
completion: (() -> Void)? = nil) {
show(text: text, icon: UIImage(named: "success"), in: view, completion: completion)
}
///
/// - Parameters:
/// - text:
/// - view: window
/// - completion:
public static func showError(text: String? = nil,
in view: UIView? = nil,
completion: (() -> Void)? = nil) {
show(text: text, icon: UIImage(named: "fail"), in: view, completion: completion)
}
///
/// - Parameters:
/// - text:
/// - view: window
/// - completion:
public static func showInfo(text: String? = nil,
in view: UIView? = nil,
completion: (() -> Void)? = nil) {
show(text: text, icon: UIImage(named: "warning"), in: view, completion: completion)
}
/// toast
/// - Parameters:
/// - text:
/// - icon:
/// - config:
/// - style:
/// - view: window
/// - completion:
public static func show(text: String? = nil,
icon: UIImage? = nil,
config: DLToastConfig = DLToastConfig(),
style: DLToastConfig.Style = .none,
in view: UIView? = nil,
completion: (() -> Void)? = nil) {
DispatchQueue.main.async {
let superView = view ?? UIApplication.shared.windows.filter({ $0.isKeyWindow }).first
guard let superView = superView else { return }
if let toastView = superView.findSubView(with: DLToastConfig.tag) as? DLToastView {
toastView.removeFromSuperview()
}
let config = config
config.containerRadius = icon == nil ? 4 : config.containerRadius
let toastView = DLToastView(iconImage: icon, text: text, config: config, style: style)
toastView.tag = DLToastConfig.tag
toastView.completion = completion
toastView.show(in: superView)
}
}
/// toast
/// - Parameters:
/// - view:
/// - completion:
public static func dismiss(for view: UIView? = nil,
delay: TimeInterval = 0,
completion: (() -> Void)? = nil) {
DispatchQueue.main.async {
let superView = view ?? UIApplication.shared.windows.filter({ $0.isKeyWindow }).first
guard let toastView = superView?.findSubView(with: DLToastConfig.tag) as? DLToastView else {
completion?()
return
}
toastView.dismiss(delay: delay, completion)
}
}
}
// MARK: - UIView + Toast
extension DLWrapper where Base: UIView {
/// loading
/// - Parameters:
/// - text:
public func showLoading(text: String? = nil) {
DLToast.showLoading(text: text, in: base)
}
///
/// - Parameters:
/// - text:
/// - view: window
/// - completion:
public func showSuccess(text: String? = nil,
completion: (() -> Void)? = nil) {
DLToast.showSuccess(text: text, in: base, completion: completion)
}
///
/// - Parameters:
/// - text:
/// - completion:
public func showError(text: String? = nil,
completion: (() -> Void)? = nil) {
DLToast.showError(text: text, in: base, completion: completion)
}
///
/// - Parameters:
/// - text:
/// - completion:
public func showInfo(text: String? = nil,
completion: (() -> Void)? = nil) {
DLToast.showInfo(text: text, in: base, completion: completion)
}
/// toast
/// - Parameters:
/// - text:
/// - icon:
/// - config:
/// - style:
/// - completion:
public func show(text: String,
icon: UIImage? = nil,
config: DLToastConfig = DLToastConfig(),
style: DLToastConfig.Style = .none,
completion: (() -> Void)? = nil) {
DLToast.show(text: text, icon: icon, config: config, style: style, in: base, completion: completion)
}
/// toast
/// - Parameters:
/// - completion:
public func dismiss(delay: TimeInterval = 0,
completion: (() -> Void)? = nil) {
DLToast.dismiss(for: base, delay: delay, completion: completion)
}
}
// MARK: - UIViewController + Toast
extension DLWrapper where Base: UIViewController {
/// loading
/// - Parameters:
/// - text:
public func showLoading(text: String? = nil) {
DLToast.showLoading(text: text, in: base.view)
}
///
/// - Parameters:
/// - text:
/// - view: window
/// - completion:
public func showSuccess(text: String? = nil,
completion: (() -> Void)? = nil) {
DLToast.showSuccess(text: text, in: base.view, completion: completion)
}
///
/// - Parameters:
/// - text:
/// - completion:
public func showError(text: String? = nil,
completion: (() -> Void)? = nil) {
DLToast.showError(text: text, in: base.view, completion: completion)
}
///
/// - Parameters:
/// - text:
/// - completion:
public func showInfo(text: String? = nil,
completion: (() -> Void)? = nil) {
DLToast.showInfo(text: text, in: base.view, completion: completion)
}
/// toast
/// - Parameters:
/// - text:
/// - icon:
/// - config:
/// - style:
/// - completion:
public func show(text: String,
icon: UIImage? = nil,
config: DLToastConfig = DLToastConfig(),
style: DLToastConfig.Style = .none,
completion: (() -> Void)? = nil) {
DLToast.show(text: text, icon: icon, config: config, style: style, in: base.view, completion: completion)
}
/// toast
/// - Parameters:
/// - completion:
public func dismiss(delay: TimeInterval = 0,
completion: (() -> Void)? = nil) {
DLToast.dismiss(for: base.view, delay: delay, completion: completion)
}
}