添加 Lottie 全局加载层并修复加载状态卡住问题

This commit is contained in:
2026-07-06 17:43:13 +08:00
parent d79d3003e3
commit 71ab38af8d
14 changed files with 281 additions and 75 deletions

View 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 }
}
}

View 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
}
}

View 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")
}
}