添加 Lottie 全局加载层并修复加载状态卡住问题
This commit is contained in:
74
suixinkan/UI/Loading/GlobalLoadingManager.swift
Normal file
74
suixinkan/UI/Loading/GlobalLoadingManager.swift
Normal file
@ -0,0 +1,74 @@
|
||||
//
|
||||
// GlobalLoadingManager.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
/// 全局 Loading 管理器,对齐 Android `GlobalLoadingManager`。
|
||||
@MainActor
|
||||
final class GlobalLoadingManager {
|
||||
|
||||
static let shared = GlobalLoadingManager()
|
||||
|
||||
private var loadingCount = 0
|
||||
private weak var hostWindow: UIWindow?
|
||||
private var overlayView: GlobalLoadingOverlayView?
|
||||
|
||||
private init() {}
|
||||
|
||||
/// 当前是否正在展示 Loading。
|
||||
var isShowing: Bool {
|
||||
loadingCount > 0
|
||||
}
|
||||
|
||||
/// 增加 Loading 引用计数并展示遮罩。
|
||||
func show() {
|
||||
loadingCount += 1
|
||||
guard loadingCount == 1 else { return }
|
||||
attachOverlayIfNeeded()
|
||||
overlayView?.present()
|
||||
}
|
||||
|
||||
/// 减少 Loading 引用计数,归零后立即隐藏遮罩。
|
||||
func hide() {
|
||||
guard loadingCount > 0 else { return }
|
||||
loadingCount -= 1
|
||||
guard loadingCount == 0 else { return }
|
||||
overlayView?.dismiss()
|
||||
overlayView?.removeFromSuperview()
|
||||
}
|
||||
|
||||
/// 强制关闭所有 Loading。
|
||||
func hideAll() {
|
||||
loadingCount = 0
|
||||
overlayView?.dismiss()
|
||||
overlayView?.removeFromSuperview()
|
||||
}
|
||||
|
||||
private func attachOverlayIfNeeded() {
|
||||
guard let window = keyWindow else { return }
|
||||
hostWindow = window
|
||||
|
||||
if overlayView == nil {
|
||||
let overlay = GlobalLoadingOverlayView(frame: window.bounds)
|
||||
overlay.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
overlay.isHidden = true
|
||||
overlayView = overlay
|
||||
}
|
||||
|
||||
guard let overlayView else { return }
|
||||
if overlayView.superview !== window {
|
||||
overlayView.removeFromSuperview()
|
||||
window.addSubview(overlayView)
|
||||
}
|
||||
window.bringSubviewToFront(overlayView)
|
||||
}
|
||||
|
||||
private var keyWindow: UIWindow? {
|
||||
UIApplication.shared.connectedScenes
|
||||
.compactMap { $0 as? UIWindowScene }
|
||||
.flatMap(\.windows)
|
||||
.first { $0.isKeyWindow }
|
||||
}
|
||||
}
|
||||
53
suixinkan/UI/Loading/GlobalLoadingOverlayView.swift
Normal file
53
suixinkan/UI/Loading/GlobalLoadingOverlayView.swift
Normal file
@ -0,0 +1,53 @@
|
||||
//
|
||||
// GlobalLoadingOverlayView.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import SnapKit
|
||||
import UIKit
|
||||
|
||||
/// 全局 Loading 遮罩,对齐 Android `LoadingScrim` 样式。
|
||||
final class GlobalLoadingOverlayView: UIView {
|
||||
|
||||
private let cardView = UIView()
|
||||
private let loadingView = LottieLoadingView()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = UIColor.black.withAlphaComponent(0.2)
|
||||
isUserInteractionEnabled = true
|
||||
|
||||
cardView.backgroundColor = .white
|
||||
cardView.layer.cornerRadius = 15
|
||||
cardView.clipsToBounds = true
|
||||
|
||||
addSubview(cardView)
|
||||
cardView.addSubview(loadingView)
|
||||
|
||||
cardView.snp.makeConstraints { make in
|
||||
make.center.equalToSuperview()
|
||||
make.width.height.equalTo(100)
|
||||
}
|
||||
loadingView.snp.makeConstraints { make in
|
||||
make.center.equalToSuperview()
|
||||
make.width.height.equalTo(80)
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
/// 显示并开始动画。
|
||||
func present() {
|
||||
isHidden = false
|
||||
loadingView.startAnimating()
|
||||
}
|
||||
|
||||
/// 隐藏并停止动画。
|
||||
func dismiss() {
|
||||
loadingView.stopAnimating()
|
||||
isHidden = true
|
||||
}
|
||||
}
|
||||
51
suixinkan/UI/Loading/LottieLoadingView.swift
Normal file
51
suixinkan/UI/Loading/LottieLoadingView.swift
Normal file
@ -0,0 +1,51 @@
|
||||
//
|
||||
// LottieLoadingView.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import Lottie
|
||||
import SnapKit
|
||||
import UIKit
|
||||
|
||||
/// Lottie 循环 Loading 动画视图,使用 `Resources/Lottie/loading.json`。
|
||||
final class LottieLoadingView: UIView {
|
||||
|
||||
private let animationView: LottieAnimationView
|
||||
|
||||
override init(frame: CGRect) {
|
||||
animationView = LottieLoadingView.makeAnimationView()
|
||||
super.init(frame: frame)
|
||||
addSubview(animationView)
|
||||
animationView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
/// 开始循环播放。
|
||||
func startAnimating() {
|
||||
animationView.loopMode = .loop
|
||||
animationView.play()
|
||||
}
|
||||
|
||||
/// 停止播放。
|
||||
func stopAnimating() {
|
||||
animationView.stop()
|
||||
}
|
||||
|
||||
private static func makeAnimationView() -> LottieAnimationView {
|
||||
if let url = loadingAnimationURL() {
|
||||
return LottieAnimationView(filePath: url.path)
|
||||
}
|
||||
return LottieAnimationView()
|
||||
}
|
||||
|
||||
private static func loadingAnimationURL() -> URL? {
|
||||
Bundle.main.url(forResource: "loading", withExtension: "json", subdirectory: "Resources/Lottie")
|
||||
?? Bundle.main.url(forResource: "loading", withExtension: "json")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user