jsdw_ios/QuickLocation/UIKit/Alert/DLToast.swift

522 lines
17 KiB
Swift
Raw 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 = 20.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)
}
public static func showError(_ error: Error,
in view: UIView? = nil,
completion: (() -> Void)? = nil) {
guard error.isShowErrorTips else { return }
showError(text: error.gatewayMessage ?? error.localizedDescription,
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 toastView = DLToastView(iconImage: icon, text: text, config: config, style: style)
toastView.tag = DLToastConfig.tag
toastView.completion = completion
toastView.show(in: superView)
}
}
public static func show(_ error: Error,
fallbackText: String? = nil,
in view: UIView? = nil,
completion: (() -> Void)? = nil) {
guard error.isShowErrorTips else { return }
show(text: error.gatewayMessage ?? fallbackText ?? error.localizedDescription,
in: view,
completion: completion)
}
/// 隐藏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)
}
public func showError(_ error: Error,
completion: (() -> Void)? = nil) {
DLToast.showError(error, 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)
}
public func show(_ error: Error,
fallbackText: String? = nil,
completion: (() -> Void)? = nil) {
DLToast.show(error, fallbackText: fallbackText, 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)
}
public func showError(_ error: Error,
completion: (() -> Void)? = nil) {
DLToast.showError(error, 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)
}
public func show(_ error: Error,
fallbackText: String? = nil,
completion: (() -> Void)? = nil) {
DLToast.show(error, fallbackText: fallbackText, 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)
}
}